From 4f8c5a432a632b8e9d0453e94a3b87c8ebf5a586 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 12 Nov 2024 14:09:29 +0100 Subject: [PATCH 01/85] modules/zstd/BUILD: increase pipeline_stages for DecoderMux proc Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 3ac0a4c322..0a973f2eac 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -504,7 +504,7 @@ xls_dslx_verilog( codegen_args = { "module_name": "DecoderMux", "delay_model": "asap7", - "pipeline_stages": "2", + "pipeline_stages": "3", "reset": "rst", "use_system_verilog": "false", }, @@ -518,7 +518,7 @@ xls_benchmark_ir( name = "dec_mux_opt_ir_benchmark", src = ":dec_mux_verilog.opt.ir", benchmark_ir_args = { - "pipeline_stages": "2", + "pipeline_stages": "10", "delay_model": "asap7", }, tags = ["manual"], From 4ca0a3fb51090c23757ca2cf938045aeb93febbf Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 8 Oct 2024 15:59:05 +0200 Subject: [PATCH 02/85] modules/zstd: Remove MagicNumberDecoder Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 18 -------- xls/modules/zstd/magic.x | 89 ---------------------------------------- 2 files changed, 107 deletions(-) delete mode 100644 xls/modules/zstd/magic.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 0a973f2eac..6d85509278 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -128,23 +128,6 @@ place_and_route( target_die_utilization_percentage = "10", ) -xls_dslx_library( - name = "magic_dslx", - srcs = [ - "magic.x", - ], - deps = [ - ":buffer_dslx", - ], -) - -xls_dslx_test( - name = "magic_dslx_test", - dslx_test_args = {"compare": "jit"}, - library = ":magic_dslx", - tags = ["manual"], -) - cc_library( name = "data_generator", srcs = ["data_generator.cc"], @@ -947,7 +930,6 @@ xls_dslx_library( ":common_dslx", ":frame_header_dslx", ":frame_header_test_dslx", - ":magic_dslx", ":ram_printer_dslx", ":repacketizer_dslx", ":sequence_executor_dslx", diff --git a/xls/modules/zstd/magic.x b/xls/modules/zstd/magic.x deleted file mode 100644 index 196f2f528f..0000000000 --- a/xls/modules/zstd/magic.x +++ /dev/null @@ -1,89 +0,0 @@ -// 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 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 7f003153ccfecccd9963666f092e1de06eb6c61b Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 8 Oct 2024 16:07:13 +0200 Subject: [PATCH 03/85] modules/zstd: Remove BlockDecoder proc Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 90 ------------------- xls/modules/zstd/block_dec.x | 170 ----------------------------------- 2 files changed, 260 deletions(-) delete mode 100644 xls/modules/zstd/block_dec.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 6d85509278..af281da3db 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -622,95 +622,6 @@ place_and_route( target_die_utilization_percentage = "5", ) -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", - dslx_test_args = {"compare": "jit"}, - library = ":block_dec_dslx", - tags = ["manual"], -) - -xls_dslx_verilog( - name = "block_dec_verilog", - codegen_args = { - "module_name": "BlockDecoder", - "delay_model": "asap7", - "pipeline_stages": "2", - "reset": "rst", - "use_system_verilog": "false", - "fifo_module": "", - "materialize_internal_fifos": "true", - }, - dslx_top = "BlockDecoder", - library = ":block_dec_dslx", - opt_ir_args = { - "top": "__xls_modules_zstd_dec_mux__BlockDecoder__DecoderMux_0_next", - }, - tags = ["manual"], - verilog_file = "block_dec.v", -) - -xls_benchmark_ir( - name = "block_dec_opt_ir_benchmark", - src = ":block_dec_verilog.opt.ir", - benchmark_ir_args = { - "pipeline_stages": "2", - "delay_model": "asap7", - "fifo_module": "", - "materialize_internal_fifos": "true", - }, - 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", - tags = ["manual"], - top_module = "BlockDecoder", - deps = [ - ":block_dec_verilog_lib", - ], -) - -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", - tags = ["manual"], - target_die_utilization_percentage = "10", -) - xls_dslx_library( name = "ram_printer_dslx", srcs = ["ram_printer.x"], @@ -924,7 +835,6 @@ xls_dslx_library( "zstd_dec.x", ], deps = [ - ":block_dec_dslx", ":block_header_dslx", ":buffer_dslx", ":common_dslx", diff --git a/xls/modules/zstd/block_dec.x b/xls/modules/zstd/block_dec.x deleted file mode 100644 index f068a8e8b6..0000000000 --- a/xls/modules/zstd/block_dec.x +++ /dev/null @@ -1,170 +0,0 @@ -// 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 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 │ └─────┘│ -// │ │ ┌───────────────────┐│ │ -// │ └─► ├┘ │ -// │ └───────────────────┘ │ -// └───────────────────────────────────────┘ - -pub 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(antmicro): 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 1a2863de786d07e7c1db6ac36c7a71a317d86398 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 8 Oct 2024 16:09:42 +0200 Subject: [PATCH 04/85] modules/zstd: Remove DecDemux proc Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 79 ------------------------------------------ 1 file changed, 79 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index af281da3db..520e664be3 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -543,85 +543,6 @@ place_and_route( target_die_utilization_percentage = "10", ) -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", - dslx_test_args = {"compare": "jit"}, - library = ":dec_demux_dslx", - tags = ["manual"], -) - -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", - tags = ["manual"], - verilog_file = "dec_demux.v", -) - -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", - tags = ["manual"], - top_module = "DecoderDemux", - deps = [ - ":dec_demux_verilog_lib", - ], -) - -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", - tags = ["manual"], - target_die_utilization_percentage = "5", -) - xls_dslx_library( name = "ram_printer_dslx", srcs = ["ram_printer.x"], From bf6183d81219c6dd44858765179ad48589924e1b Mon Sep 17 00:00:00 2001 From: Maciej Torhan Date: Thu, 3 Oct 2024 14:16:47 +0200 Subject: [PATCH 05/85] modules/zstd/block_header: Specify new type for the block size Signed-off-by: Maciej Torhan --- xls/modules/zstd/block_header.x | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/xls/modules/zstd/block_header.x b/xls/modules/zstd/block_header.x index 455b3295e1..a0b52491b8 100644 --- a/xls/modules/zstd/block_header.x +++ b/xls/modules/zstd/block_header.x @@ -23,6 +23,7 @@ import xls.modules.zstd.common as common; type Buffer = buff::Buffer; type BufferStatus = buff::BufferStatus; type BlockType = common::BlockType; +type BlockSize = common::BlockSize; // Status values reported by the block header parsing function pub enum BlockHeaderStatus: u2 { @@ -35,7 +36,7 @@ pub enum BlockHeaderStatus: u2 { pub struct BlockHeader { last: bool, btype: BlockType, - size: u21, + size: BlockSize, } // Structure for returning results of block header parsing @@ -86,7 +87,7 @@ fn test_parse_block_header() { let result = parse_block_header(buffer); assert_eq(result, BlockHeaderResult { status: BlockHeaderStatus::OK, - header: BlockHeader { last: u1:1, btype: BlockType::RAW, size: u21:0x1000 }, + header: BlockHeader { last: u1:1, btype: BlockType::RAW, size: BlockSize:0x1000 }, buffer: Buffer { content: u32:0, length: u32:0 } }); @@ -94,7 +95,7 @@ fn test_parse_block_header() { let result = parse_block_header(buffer); assert_eq(result, BlockHeaderResult { status: BlockHeaderStatus::OK, - header: BlockHeader { last: u1:0, btype: BlockType::RLE, size: u21:0x1234 }, + header: BlockHeader { last: u1:0, btype: BlockType::RLE, size: BlockSize:0x1234 }, buffer: Buffer { content: u32:0, length: u32:0 } }); From 9618166075421e9d7e4c39b7bd5eb3d7c7f7a0df Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 8 Oct 2024 16:04:11 +0200 Subject: [PATCH 06/85] modules/zstd: Cleanup BlockHeader Remove references to buffer structs as those are not used anywhere Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 35 ++++++++++---------- xls/modules/zstd/block_header.x | 58 --------------------------------- 2 files changed, 17 insertions(+), 76 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 520e664be3..ab3befa0a2 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -280,6 +280,23 @@ place_and_route( target_die_utilization_percentage = "10", ) +xls_dslx_library( + name = "block_header_dslx", + srcs = [ + "block_header.x", + ], + deps = [ + ":common_dslx", + ], +) + +xls_dslx_test( + name = "block_header_dslx_test", + dslx_test_args = {"compare": "jit"}, + library = ":block_header_dslx", + tags = ["manual"], +) + xls_dslx_library( name = "raw_block_dec_dslx", srcs = [ @@ -447,24 +464,6 @@ place_and_route( target_die_utilization_percentage = "10", ) -xls_dslx_library( - name = "block_header_dslx", - srcs = [ - "block_header.x", - ], - deps = [ - ":buffer_dslx", - ":common_dslx", - ], -) - -xls_dslx_test( - name = "block_header_dslx_test", - dslx_test_args = {"compare": "jit"}, - library = ":block_header_dslx", - tags = ["manual"], -) - xls_dslx_library( name = "dec_mux_dslx", srcs = [ diff --git a/xls/modules/zstd/block_header.x b/xls/modules/zstd/block_header.x index a0b52491b8..9e8679a72d 100644 --- a/xls/modules/zstd/block_header.x +++ b/xls/modules/zstd/block_header.x @@ -17,11 +17,8 @@ // 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; type BlockSize = common::BlockSize; @@ -39,13 +36,6 @@ pub struct BlockHeader { size: BlockSize, } -// 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!(); @@ -59,51 +49,3 @@ pub fn extract_block_header(data:u24) -> BlockHeader { 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: BlockSize: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: BlockSize: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 49162f24c635bea21eee19755d37329455fd2d2a Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Wed, 9 Oct 2024 11:05:59 +0200 Subject: [PATCH 07/85] modules/zstd/BUILD: Introduce common codegen args Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 144 +++++++++++++++++++++-------------------- 1 file changed, 74 insertions(+), 70 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index ab3befa0a2..28fb70013c 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -33,6 +33,19 @@ package( licenses = ["notice"], ) +CLOCK_PERIOD_PS = "750" +# Clock periods for modules that exceed the 750ps critical path in IR benchmark + +common_codegen_args = { + "delay_model": "asap7", + "reset": "rst", + "worst_case_throughput": "1", + "use_system_verilog": "false", + "clock_period_ps": CLOCK_PERIOD_PS, + "clock_margin_percent": "20", + "multi_proc": "true", +} + xls_dslx_library( name = "buffer_dslx", srcs = [ @@ -64,20 +77,17 @@ xls_dslx_test( tags = ["manual"], ) +window_buffer_codegen_args = common_codegen_args | { + "module_name": "WindowBuffer64", + "clock_period_ps": "0", + "pipeline_stages": "1", +} + xls_dslx_verilog( name = "window_buffer_verilog", - codegen_args = { - "module_name": "WindowBuffer64", - "delay_model": "asap7", - "pipeline_stages": "2", - "reset": "rst", - "use_system_verilog": "false", - }, + codegen_args = window_buffer_codegen_args, dslx_top = "WindowBuffer64", library = ":window_buffer_dslx", - opt_ir_args = { - "top": "__window_buffer__WindowBuffer64__WindowBuffer_0__64_32_48_next", - }, tags = ["manual"], verilog_file = "window_buffer.v", ) @@ -85,9 +95,9 @@ xls_dslx_verilog( xls_benchmark_ir( name = "window_buffer_opt_ir_benchmark", src = ":window_buffer_verilog.opt.ir", - benchmark_ir_args = { - "pipeline_stages": "2", - "delay_model": "asap7", + benchmark_ir_args = window_buffer_codegen_args | { + "pipeline_stages": "10", + "top": "__window_buffer__WindowBuffer64__WindowBuffer_0__64_32_48_next", }, tags = ["manual"], ) @@ -118,7 +128,7 @@ benchmark_synth( place_and_route( name = "window_buffer_place_and_route", - clock_period = "750", + clock_period = CLOCK_PERIOD_PS, core_padding_microns = 2, min_pin_distance = "0.5", placement_density = "0.30", @@ -481,15 +491,15 @@ xls_dslx_test( tags = ["manual"], ) +dec_mux_codegen_args = common_codegen_args | { + "module_name": "DecoderMux", + "clock_period_ps": "0", + "pipeline_stages": "3", +} + xls_dslx_verilog( name = "dec_mux_verilog", - codegen_args = { - "module_name": "DecoderMux", - "delay_model": "asap7", - "pipeline_stages": "3", - "reset": "rst", - "use_system_verilog": "false", - }, + codegen_args = dec_mux_codegen_args, dslx_top = "DecoderMux", library = ":dec_mux_dslx", tags = ["manual"], @@ -499,9 +509,8 @@ xls_dslx_verilog( xls_benchmark_ir( name = "dec_mux_opt_ir_benchmark", src = ":dec_mux_verilog.opt.ir", - benchmark_ir_args = { + benchmark_ir_args = dec_mux_codegen_args | { "pipeline_stages": "10", - "delay_model": "asap7", }, tags = ["manual"], ) @@ -532,7 +541,7 @@ benchmark_synth( place_and_route( name = "dec_mux_place_and_route", - clock_period = "750", + clock_period = CLOCK_PERIOD_PS, core_padding_microns = 2, min_pin_distance = "0.5", placement_density = "0.30", @@ -578,36 +587,35 @@ xls_dslx_test( tags = ["manual"], ) +sequence_executor_codegen_args = common_codegen_args | { + "module_name": "sequence_executor", + "clock_period_ps": "0", + "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": "6", + "reset": "rst", + "reset_data_path": "true", + "reset_active_low": "false", + "reset_asynchronous": "true", + "flop_inputs": "false", + "flop_single_value_channels": "false", + "flop_outputs": "false", +} + 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", - "fifo_module": "", - "materialize_internal_fifos": "true", - }, + codegen_args = sequence_executor_codegen_args, dslx_top = "SequenceExecutorZstd", library = ":sequence_executor_dslx", opt_ir_args = { @@ -618,13 +626,10 @@ xls_dslx_verilog( ) xls_benchmark_ir( - name = "sequence_executor_ir_benchmark", + name = "sequence_executor_opt_ir_benchmark", src = ":sequence_executor_verilog.opt.ir", - benchmark_ir_args = { - "pipeline_stages": "8", - "delay_model": "asap7", - "fifo_module": "", - "materialize_internal_fifos": "true", + benchmark_ir_args = sequence_executor_codegen_args | { + "pipeline_stages": "10", }, tags = ["manual"], ) @@ -661,7 +666,7 @@ benchmark_synth( place_and_route( name = "sequence_executor_place_and_route", - clock_period = "750", + clock_period = CLOCK_PERIOD_PS, core_padding_microns = 2, min_pin_distance = "0.4", placement_density = "0.30", @@ -688,15 +693,15 @@ xls_dslx_test( tags = ["manual"], ) +repacketizer_codegen_args = common_codegen_args | { + "module_name": "Repacketizer", + "clock_period_ps": "0", + "pipeline_stages": "2", +} + xls_dslx_verilog( name = "repacketizer_verilog", - codegen_args = { - "module_name": "Repacketizer", - "delay_model": "asap7", - "pipeline_stages": "2", - "reset": "rst", - "use_system_verilog": "false", - }, + codegen_args = repacketizer_codegen_args, dslx_top = "Repacketizer", library = ":repacketizer_dslx", tags = ["manual"], @@ -706,9 +711,8 @@ xls_dslx_verilog( xls_benchmark_ir( name = "repacketizer_opt_ir_benchmark", src = ":repacketizer_verilog.opt.ir", - benchmark_ir_args = { - "pipeline_stages": "2", - "delay_model": "asap7", + benchmark_ir_args = repacketizer_codegen_args | { + "pipeline_stages": "10", }, tags = ["manual"], ) @@ -739,7 +743,7 @@ benchmark_synth( place_and_route( name = "repacketizer_place_and_route", - clock_period = "750", + clock_period = CLOCK_PERIOD_PS, core_padding_microns = 2, min_pin_distance = "0.5", placement_density = "0.30", From 5b883286d46da177a9442a66c20e5a93ced2d8ab Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 8 Oct 2024 17:41:55 +0200 Subject: [PATCH 08/85] modules/zstd: Add AxiCsrAccessor Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 77 ++++++ xls/modules/zstd/axi_csr_accessor.x | 384 ++++++++++++++++++++++++++++ 2 files changed, 461 insertions(+) create mode 100644 xls/modules/zstd/axi_csr_accessor.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 28fb70013c..f769fb4078 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -898,3 +898,80 @@ place_and_route( tags = ["manual"], target_die_utilization_percentage = "10", ) + +xls_dslx_library( + name = "axi_csr_accessor_dslx", + srcs = [ + "axi_csr_accessor.x", + ], + deps = [ + ":csr_config_dslx", + "//xls/modules/zstd/memory:axi_dslx", + ], +) + +xls_dslx_test( + name = "axi_csr_accessor_dslx_test", + library = ":axi_csr_accessor_dslx", + tags = ["manual"], +) + +axi_csr_accessor_codegen_args = common_codegen_args | { + "module_name": "AxiCsrAccessor", + "pipeline_stages": "1", +} + +xls_dslx_verilog( + name = "axi_csr_accessor_verilog", + codegen_args = axi_csr_accessor_codegen_args, + dslx_top = "AxiCsrAccessorInst", + library = ":axi_csr_accessor_dslx", + tags = ["manual"], + verilog_file = "axi_csr_accessor.v", +) + +xls_benchmark_ir( + name = "axi_csr_accessor_opt_ir_benchmark", + src = ":axi_csr_accessor_verilog.opt.ir", + benchmark_ir_args = axi_csr_accessor_codegen_args | { + "pipeline_stages": "10", + "top": "__axi_csr_accessor__AxiCsrAccessorInst__AxiCsrAccessor_0__16_32_4_4_2_4_16_next", + }, + tags = ["manual"], +) + +verilog_library( + name = "axi_csr_accessor_verilog_lib", + srcs = [ + ":axi_csr_accessor.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "axi_csr_accessor_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "AxiCsrAccessor", + deps = [ + ":axi_csr_accessor_verilog_lib", + ], +) + +benchmark_synth( + name = "axi_csr_accessor_benchmark_synth", + synth_target = ":axi_csr_accessor_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "axi_csr_accessor_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":axi_csr_accessor_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) diff --git a/xls/modules/zstd/axi_csr_accessor.x b/xls/modules/zstd/axi_csr_accessor.x new file mode 100644 index 0000000000..01cef68cd9 --- /dev/null +++ b/xls/modules/zstd/axi_csr_accessor.x @@ -0,0 +1,384 @@ +// Copyright 2023-2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains implementation of a proc that handles CSRs. It provides +// an AXI interface for reading and writing the values as well as separate +// request/response channels. Apart from that it has an output channel which +// notifies aboud changes made to CSRs. + +import std; +import xls.modules.zstd.memory.axi; +import xls.modules.zstd.csr_config; + + +struct AxiCsrAccessorState { + w_id: uN[ID_W], + w_addr: uN[ADDR_W], + r_id: uN[ID_W], + r_addr: uN[ADDR_W], +} + +pub proc AxiCsrAccessor< + ID_W: u32, ADDR_W: u32, DATA_W: u32, REGS_N: u32, + DATA_W_DIV8: u32 = { DATA_W / u32:8 }, + LOG2_REGS_N: u32 = { std::clog2(REGS_N) }, + LOG2_DATA_W_DIV8: u32 = { std::clog2(DATA_W / u32:8) }, +> { + type AxiAw = axi::AxiAw; + type AxiW = axi::AxiW; + type AxiB = axi::AxiB; + type AxiAr = axi::AxiAr; + type AxiR = axi::AxiR; + + type RdReq = csr_config::CsrRdReq; + type RdResp = csr_config::CsrRdResp; + type WrReq = csr_config::CsrWrReq; + type WrResp = csr_config::CsrWrResp; + + type State = AxiCsrAccessorState; + type Data = uN[DATA_W]; + type RegN = uN[LOG2_REGS_N]; + + axi_aw_r: chan in; + axi_w_r: chan in; + axi_b_s: chan out; + axi_ar_r: chan in; + axi_r_s: chan out; + + csr_rd_req_s: chan out; + csr_rd_resp_r: chan in; + csr_wr_req_s: chan out; + csr_wr_resp_r: chan in; + + config ( + axi_aw_r: chan in, + axi_w_r: chan in, + axi_b_s: chan out, + axi_ar_r: chan in, + axi_r_s: chan out, + + csr_rd_req_s: chan out, + csr_rd_resp_r: chan in, + csr_wr_req_s: chan out, + csr_wr_resp_r: chan in, + ) { + ( + axi_aw_r, axi_w_r, axi_b_s, + axi_ar_r, axi_r_s, + csr_rd_req_s, csr_rd_resp_r, + csr_wr_req_s, csr_wr_resp_r, + ) + } + + init { + zero!() + } + + next (state: State) { + let tok_0 = join(); + // write to CSR via AXI + let (tok_1_1, axi_aw, axi_aw_valid) = recv_non_blocking(tok_0, axi_aw_r, AxiAw {id: state.w_id, addr: state.w_addr, ..zero!()}); + + // validate axi aw + assert!(!(axi_aw_valid && axi_aw.addr as u32 >= REGS_N), "invalid_aw_addr"); + assert!(!(axi_aw_valid && axi_aw.len != u8:0), "invalid_aw_len"); + + let (tok_1_2, axi_w, axi_w_valid) = recv_non_blocking(tok_1_1, axi_w_r, zero!()); + + // Send WriteRequest to CSRs + let data_w = if axi_w_valid { + let (w_data, _, _) = for (i, (w_data, strb, mask)): (u32, (uN[DATA_W], uN[DATA_W_DIV8], uN[DATA_W])) in range(u32:0, DATA_W_DIV8) { + let w_data = if axi_w.strb as u1 { + w_data | (axi_w.data & mask) + } else { + w_data + }; + ( + w_data, + strb >> u32:1, + mask << u32:8, + ) + }((uN[DATA_W]:0, axi_w.strb, uN[DATA_W]:0xFF)); + w_data + } else { + uN[DATA_W]:0 + }; + + let wr_req = WrReq { + csr: (axi_aw.addr >> LOG2_DATA_W_DIV8) as uN[LOG2_REGS_N], + value: data_w + }; + + let tok_1_3 = send_if(tok_1_2, csr_wr_req_s, axi_w_valid, wr_req); + + let (tok_2_1, csr_wr_resp, csr_wr_resp_valid) = recv_non_blocking(tok_0, csr_wr_resp_r, zero!()); + let axi_write_resp = AxiB { + resp: axi::AxiWriteResp::OKAY, + id: axi_aw.id, + }; + let tok_2_2 = send_if(tok_2_1, axi_b_s, csr_wr_resp_valid, axi_write_resp); + + + // Send ReadRequest to CSRs + let (tok_3_1, axi_ar, axi_ar_valid) = recv_non_blocking(tok_0, axi_ar_r, AxiAr {id: state.r_id, addr: state.r_addr, ..zero!()}); + // validate ar bundle + assert!(!(axi_ar_valid && axi_ar.addr as u32 >= REGS_N), "invalid_ar_addr"); + assert!(!(axi_ar_valid && axi_ar.len != u8:0), "invalid_ar_len"); + let rd_req = RdReq { + csr: (axi_ar.addr >> LOG2_DATA_W_DIV8) as uN[LOG2_REGS_N], + }; + let tok_3_2 = send_if(tok_3_1, csr_rd_req_s, axi_ar_valid, rd_req); + + let (tok_4_1, csr_rd_resp, csr_rd_resp_valid) = recv_non_blocking(tok_0, csr_rd_resp_r, zero!()); + + let axi_read_resp = AxiR { + id: axi_ar.id, + data: csr_rd_resp.value, + resp: axi::AxiReadResp::OKAY, + last: true, + }; + let tok_4_2 = send_if(tok_4_1, axi_r_s, csr_rd_resp_valid, axi_read_resp); + + State { + w_id: axi_aw.id, + w_addr: axi_aw.addr, + r_id: axi_ar.id, + r_addr: axi_ar.addr, + } + } +} + +const INST_ID_W = u32:4; +const INST_DATA_W = u32:32; +const INST_ADDR_W = u32:16; +const INST_REGS_N = u32:16; +const INST_DATA_W_DIV8 = INST_DATA_W / u32:8; +const INST_LOG2_REGS_N = std::clog2(INST_REGS_N); + +proc AxiCsrAccessorInst { + type InstAxiAw = axi::AxiAw; + type InstAxiW = axi::AxiW; + type InstAxiB = axi::AxiB; + type InstAxiAr = axi::AxiAr; + type InstAxiR = axi::AxiR; + + type InstCsrRdReq = csr_config::CsrRdReq; + type InstCsrRdResp = csr_config::CsrRdResp; + type InstCsrWrReq = csr_config::CsrWrReq; + type InstCsrWrResp = csr_config::CsrWrResp; + type InstCsrChange = csr_config::CsrChange; + + config( + axi_aw_r: chan in, + axi_w_r: chan in, + axi_b_s: chan out, + axi_ar_r: chan in, + axi_r_s: chan out, + + + csr_rd_req_s: chan out, + csr_rd_resp_r: chan in, + csr_wr_req_s: chan out, + csr_wr_resp_r: chan in, + ) { + spawn AxiCsrAccessor ( + axi_aw_r, axi_w_r, axi_b_s, + axi_ar_r, axi_r_s, + csr_rd_req_s, csr_rd_resp_r, + csr_wr_req_s, csr_wr_resp_r, + ); + } + + init { } + + next (state: ()) { } +} + +const TEST_ID_W = u32:4; +const TEST_DATA_W = u32:32; +const TEST_ADDR_W = u32:16; +const TEST_REGS_N = u32:16; +const TEST_DATA_W_DIV8 = TEST_DATA_W / u32:8; +const TEST_LOG2_REGS_N = std::clog2(TEST_REGS_N); +const TEST_LOG2_DATA_W_DIV8 = std::clog2(TEST_DATA_W_DIV8); + +type TestCsr = uN[TEST_LOG2_REGS_N]; +type TestValue = uN[TEST_DATA_W]; + +struct TestData { + csr: uN[TEST_LOG2_REGS_N], + value: uN[TEST_DATA_W], +} + +const TEST_DATA = TestData[20]:[ + TestData{ csr: TestCsr:0, value: TestValue:0xca32_9f4a }, + TestData{ csr: TestCsr:1, value: TestValue:0x0fb3_fa42 }, + TestData{ csr: TestCsr:2, value: TestValue:0xe7ee_da41 }, + TestData{ csr: TestCsr:3, value: TestValue:0xef51_f98c }, + TestData{ csr: TestCsr:0, value: TestValue:0x97a3_a2d2 }, + TestData{ csr: TestCsr:0, value: TestValue:0xea06_e94b }, + TestData{ csr: TestCsr:1, value: TestValue:0x5fac_17ce }, + TestData{ csr: TestCsr:3, value: TestValue:0xf9d8_9938 }, + TestData{ csr: TestCsr:2, value: TestValue:0xc262_2d2e }, + TestData{ csr: TestCsr:2, value: TestValue:0xb4dd_424e }, + TestData{ csr: TestCsr:1, value: TestValue:0x01f9_b9e4 }, + TestData{ csr: TestCsr:1, value: TestValue:0x3020_6eec }, + TestData{ csr: TestCsr:3, value: TestValue:0x3124_87b5 }, + TestData{ csr: TestCsr:0, value: TestValue:0x0a49_f5e3 }, + TestData{ csr: TestCsr:2, value: TestValue:0xde3b_5d0f }, + TestData{ csr: TestCsr:3, value: TestValue:0x5948_c1b3 }, + TestData{ csr: TestCsr:0, value: TestValue:0xa26d_851f }, + TestData{ csr: TestCsr:3, value: TestValue:0x3fa9_59c0 }, + TestData{ csr: TestCsr:1, value: TestValue:0x4efd_dd09 }, + TestData{ csr: TestCsr:1, value: TestValue:0x6d75_058a }, +]; + +#[test_proc] +proc AxiCsrAccessorTest { + type TestAxiAw = axi::AxiAw; + type TestAxiW = axi::AxiW; + type TestAxiB = axi::AxiB; + type TestAxiAr = axi::AxiAr; + type TestAxiR = axi::AxiR; + + + type TestCsrRdReq = csr_config::CsrRdReq; + type TestCsrRdResp = csr_config::CsrRdResp; + type TestCsrWrReq = csr_config::CsrWrReq; + type TestCsrWrResp = csr_config::CsrWrResp; + type TestCsrChange = csr_config::CsrChange; + + terminator: chan out; + + axi_aw_s: chan out; + axi_w_s: chan out; + axi_b_r: chan in; + axi_ar_s: chan out; + axi_r_r: chan in; + + csr_rd_req_r: chan in; + csr_rd_resp_s: chan out; + csr_wr_req_r: chan in; + csr_wr_resp_s: chan out; + + config (terminator: chan out) { + let (axi_aw_s, axi_aw_r) = chan("axi_aw"); + let (axi_w_s, axi_w_r) = chan("axi_w"); + let (axi_b_s, axi_b_r) = chan("axi_b"); + let (axi_ar_s, axi_ar_r) = chan("axi_ar"); + let (axi_r_s, axi_r_r) = chan("axi_r"); + + let (csr_rd_req_s, csr_rd_req_r) = chan("csr_rd_req"); + let (csr_rd_resp_s, csr_rd_resp_r) = chan("csr_rd_resp"); + + let (csr_wr_req_s, csr_wr_req_r) = chan("csr_wr_req"); + let (csr_wr_resp_s, csr_wr_resp_r) = chan("csr_wr_resp"); + + spawn AxiCsrAccessor ( + axi_aw_r, axi_w_r, axi_b_s, + axi_ar_r, axi_r_s, + csr_rd_req_s, csr_rd_resp_r, + csr_wr_req_s, csr_wr_resp_r, + ); + + ( + terminator, + axi_aw_s, axi_w_s, axi_b_r, + axi_ar_s, axi_r_r, + csr_rd_req_r, csr_rd_resp_s, + csr_wr_req_r, csr_wr_resp_s, + ) + } + + init { } + + next (state: ()) { + // test writing via AXI + let tok = for ((i, test_data), tok): ((u32, TestData), token) in enumerate(TEST_DATA) { + // write CSR via AXI + let axi_aw = TestAxiAw { + id: i as uN[TEST_ID_W], + addr: (test_data.csr << TEST_LOG2_DATA_W_DIV8) as uN[TEST_ADDR_W], + size: axi::AxiAxSize::MAX_4B_TRANSFER, + len: u8:0, + burst: axi::AxiAxBurst::FIXED, + }; + let tok = send(tok, axi_aw_s, axi_aw); + trace_fmt!("Sent #{} AXI AW: {:#x}", i + u32:1, axi_aw); + + let axi_w = TestAxiW { + data: test_data.value, + strb: !uN[TEST_DATA_W_DIV8]:0, + last: true, + }; + let tok = send(tok, axi_w_s, axi_w); + trace_fmt!("Sent #{} AXI W: {:#x}", i + u32:1, axi_w); + + let expected_wr_req = TestCsrWrReq { + csr: test_data.csr, + value: test_data.value + }; + let (tok, wr_req) = recv(tok, csr_wr_req_r); + trace_fmt!("Received #{} CSR WriteRequest: {:#x}", i + u32:1, wr_req); + assert_eq(expected_wr_req, wr_req); + + let tok = send(tok, csr_wr_resp_s, TestCsrWrResp{}); + trace_fmt!("Sent #{} CsrWrResp", i + u32:1); + let (tok, axi_b) = recv(tok, axi_b_r); + trace_fmt!("Received #{} AXI B: {:#x}", i + u32:1, axi_b); + let expected_axi_resp = TestAxiB{ + resp: axi::AxiWriteResp::OKAY, + id: i as uN[TEST_ID_W], + }; + assert_eq(expected_axi_resp, axi_b); + + // read CSRs via AXI + let axi_ar = TestAxiAr { + id: i as uN[TEST_ID_W], + addr: (test_data.csr << TEST_LOG2_DATA_W_DIV8) as uN[TEST_ADDR_W], + len: u8:0, + ..zero!() + }; + let tok = send(tok, axi_ar_s, axi_ar); + trace_fmt!("Sent #{} AXI AR: {:#x}", i + u32:1, axi_ar); + + let expected_rd_req = TestCsrRdReq { + csr: test_data.csr, + }; + let (tok, rd_req) = recv(tok, csr_rd_req_r); + trace_fmt!("Received #{} CSR ReadRequest: {:#x}", i + u32:1, rd_req); + assert_eq(expected_rd_req, rd_req); + let rd_resp = TestCsrRdResp { + csr: test_data.csr, + value: test_data.value + }; + let tok = send(tok, csr_rd_resp_s, rd_resp); + trace_fmt!("Sent #{} CsrRdResp: {:#x}", i + u32:1, rd_resp); + + let (tok, axi_r) = recv(tok, axi_r_r); + trace_fmt!("Received #{} AXI R: {:#x}", i + u32:1, axi_r); + let expected_axi_rd_resp = TestAxiR{ + id: i as uN[TEST_ID_W], + data: test_data.value, + resp: axi::AxiReadResp::OKAY, + last: true, + }; + assert_eq(expected_axi_rd_resp, axi_r); + + tok + }(join()); + + send(tok, terminator, true); + } +} From 579bfe48cd3520514d065f41b9a86dbb3fe95784 Mon Sep 17 00:00:00 2001 From: Maciej Torhan Date: Tue, 8 Oct 2024 17:49:59 +0200 Subject: [PATCH 09/85] modules/zstd: Add CsrConfig Co-authored-by: Pawel Czarnecki Co-authored-by: Robert Winkler Signed-off-by: Maciej Torhan Signed-off-by: Pawel Czarnecki Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 76 +++++++ xls/modules/zstd/csr_config.x | 397 ++++++++++++++++++++++++++++++++++ 2 files changed, 473 insertions(+) create mode 100644 xls/modules/zstd/csr_config.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index f769fb4078..b717e7be96 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -975,3 +975,79 @@ place_and_route( tags = ["manual"], target_die_utilization_percentage = "10", ) + +xls_dslx_library( + name = "csr_config_dslx", + srcs = [ + "csr_config.x", + ], + deps = [ + "//xls/modules/zstd/memory:axi_dslx", + ], +) + +xls_dslx_test( + name = "csr_config_dslx_test", + library = ":csr_config_dslx", + tags = ["manual"], +) + +csr_config_codegen_args = common_codegen_args | { + "module_name": "CsrConfig", + "pipeline_stages": "3", +} + +xls_dslx_verilog( + name = "csr_config_verilog", + codegen_args = csr_config_codegen_args, + dslx_top = "CsrConfigInst", + library = ":csr_config_dslx", + tags = ["manual"], + verilog_file = "csr_config.v", +) + +xls_benchmark_ir( + name = "csr_config_opt_ir_benchmark", + src = ":csr_config_verilog.opt.ir", + benchmark_ir_args = csr_config_codegen_args | { + "pipeline_stages": "10", + "top": "__csr_config__CsrConfigInst__CsrConfig_0__2_32_4_32_2_4_next", + }, + tags = ["manual"], +) + +verilog_library( + name = "csr_config_verilog_lib", + srcs = [ + ":csr_config.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "csr_config_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "CsrConfig", + deps = [ + ":csr_config_verilog_lib", + ], +) + +benchmark_synth( + name = "csr_config_benchmark_synth", + synth_target = ":csr_config_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "csr_config_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":csr_config_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) diff --git a/xls/modules/zstd/csr_config.x b/xls/modules/zstd/csr_config.x new file mode 100644 index 0000000000..a792757cfa --- /dev/null +++ b/xls/modules/zstd/csr_config.x @@ -0,0 +1,397 @@ +// Copyright 2023-2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains implementation of a proc that handles CSRs. It provides +// an AXI interface for reading and writing the values as well as separate +// request/response channels. Apart from that it has an output channel which +// notifies aboud changes made to CSRs. + +import std; +import xls.modules.zstd.memory.axi; + +pub struct CsrRdReq { + csr: uN[LOG2_REGS_N], +} + +pub struct CsrRdResp { + csr: uN[LOG2_REGS_N], + value: uN[DATA_W], +} + +pub struct CsrWrReq { + csr: uN[LOG2_REGS_N], + value: uN[DATA_W], +} + +pub struct CsrWrResp { } + +pub struct CsrChange { + csr: uN[LOG2_REGS_N], +} + +struct CsrConfigState { + register_file: uN[DATA_W][REGS_N], +} + +pub proc CsrConfig< + ID_W: u32, ADDR_W: u32, DATA_W: u32, REGS_N: u32, + //REGS_INIT: u64[64] = {u64[64]:[u64:0, ...]}, + DATA_W_DIV8: u32 = { DATA_W / u32:8 }, + LOG2_REGS_N: u32 = { std::clog2(REGS_N) }, +> { + + type RdReq = CsrRdReq; + type RdResp = CsrRdResp; + type WrReq = CsrWrReq; + type WrResp = CsrWrResp; + type Change = CsrChange; + + type State = CsrConfigState; + type Data = uN[DATA_W]; + type RegN = uN[LOG2_REGS_N]; + + ext_csr_rd_req_r: chan in; + ext_csr_rd_resp_s: chan out; + ext_csr_wr_req_r: chan in; + ext_csr_wr_resp_s: chan out; + + csr_rd_req_r: chan in; + csr_rd_resp_s: chan out; + csr_wr_req_r: chan in; + csr_wr_resp_s: chan out; + + csr_change_s: chan out; + + config ( + ext_csr_rd_req_r: chan in, + ext_csr_rd_resp_s: chan out, + ext_csr_wr_req_r: chan in, + ext_csr_wr_resp_s: chan out, + + csr_rd_req_r: chan in, + csr_rd_resp_s: chan out, + csr_wr_req_r: chan in, + csr_wr_resp_s: chan out, + csr_change_s: chan out, + ) { + ( + ext_csr_rd_req_r, ext_csr_rd_resp_s, + ext_csr_wr_req_r, ext_csr_wr_resp_s, + csr_rd_req_r, csr_rd_resp_s, + csr_wr_req_r, csr_wr_resp_s, + csr_change_s, + ) + } + + init { + zero!() + } + + next (state: State) { + let register_file = state.register_file; + + let tok_0 = join(); + + // write to CSR + let (tok_1_1_1, ext_csr_wr_req, ext_csr_wr_req_valid) = recv_non_blocking(tok_0, ext_csr_wr_req_r, zero!()); + let (tok_1_1_2, csr_wr_req, csr_wr_req_valid) = recv_non_blocking(tok_0, csr_wr_req_r, zero!()); + + // Mux the Write Requests from External and Internal sources + // Write requests from external source take precedence before internal writes + let wr_req = if (ext_csr_wr_req_valid) { + ext_csr_wr_req + } else if {csr_wr_req_valid} { + csr_wr_req + } else { + zero!() + }; + + let wr_req_valid = ext_csr_wr_req_valid | csr_wr_req_valid; + + let register_file = if wr_req_valid { + update(register_file, wr_req.csr as u32, wr_req.value) + } else { + register_file + }; + + // Send Write Response + let tok_1_1 = join(tok_1_1_1, tok_1_1_2); + let tok_1_2_1 = send_if(tok_1_1, ext_csr_wr_resp_s, ext_csr_wr_req_valid, WrResp {}); + let tok_1_2_2 = send_if(tok_1_1, csr_wr_resp_s, csr_wr_req_valid, WrResp {}); + + // Send change notification + let tok_1_2 = join(tok_1_2_1, tok_1_2_2); + let tok_1_3 = send_if(tok_1_2, csr_change_s, wr_req_valid, Change { csr: wr_req.csr }); + + + // Read from CSRs + let (tok_2_1, ext_csr_rd_req, ext_csr_req_valid) = recv_non_blocking(tok_0, ext_csr_rd_req_r, zero!()); + + send_if(tok_2_1, ext_csr_rd_resp_s, ext_csr_req_valid, RdResp { + csr: ext_csr_rd_req.csr, + value: register_file[ext_csr_rd_req.csr as u32], + }); + + let (tok_3_1, csr_rd_req, csr_req_valid) = recv_non_blocking(tok_0, csr_rd_req_r, zero!()); + send_if(tok_3_1, csr_rd_resp_s, csr_req_valid, RdResp { + csr: csr_rd_req.csr, + value: register_file[csr_rd_req.csr as u32], + }); + + State { + register_file: register_file, + } + } +} + +const INST_ID_W = u32:32; +const INST_DATA_W = u32:32; +const INST_ADDR_W = u32:2; +const INST_REGS_N = u32:4; +const INST_DATA_W_DIV8 = INST_DATA_W / u32:8; +const INST_LOG2_REGS_N = std::clog2(INST_REGS_N); + +proc CsrConfigInst { + type InstCsrRdReq = CsrRdReq; + type InstCsrRdResp = CsrRdResp; + type InstCsrWrReq = CsrWrReq; + type InstCsrWrResp = CsrWrResp; + type InstCsrChange = CsrChange; + + config( + ext_csr_rd_req_r: chan in, + ext_csr_rd_resp_s: chan out, + ext_csr_wr_req_r: chan in, + ext_csr_wr_resp_s: chan out, + + csr_rd_req_r: chan in, + csr_rd_resp_s: chan out, + csr_wr_req_r: chan in, + csr_wr_resp_s: chan out, + csr_change_s: chan out, + ) { + spawn CsrConfig ( + ext_csr_rd_req_r, ext_csr_rd_resp_s, + ext_csr_wr_req_r, ext_csr_wr_resp_s, + csr_rd_req_r, csr_rd_resp_s, + csr_wr_req_r, csr_wr_resp_s, + csr_change_s, + ); + } + + init { } + + next (state: ()) { } +} + +const TEST_ID_W = u32:32; +const TEST_DATA_W = u32:32; +const TEST_ADDR_W = u32:2; +const TEST_REGS_N = u32:4; +const TEST_DATA_W_DIV8 = TEST_DATA_W / u32:8; +const TEST_LOG2_REGS_N = std::clog2(TEST_REGS_N); + +type TestCsr = uN[TEST_LOG2_REGS_N]; +type TestValue = uN[TEST_DATA_W]; + +struct TestData { + csr: uN[TEST_LOG2_REGS_N], + value: uN[TEST_DATA_W], +} + +const TEST_DATA = TestData[20]:[ + TestData{ csr: TestCsr:0, value: TestValue:0xca32_9f4a }, + TestData{ csr: TestCsr:1, value: TestValue:0x0fb3_fa42 }, + TestData{ csr: TestCsr:2, value: TestValue:0xe7ee_da41 }, + TestData{ csr: TestCsr:3, value: TestValue:0xef51_f98c }, + TestData{ csr: TestCsr:0, value: TestValue:0x97a3_a2d2 }, + TestData{ csr: TestCsr:0, value: TestValue:0xea06_e94b }, + TestData{ csr: TestCsr:1, value: TestValue:0x5fac_17ce }, + TestData{ csr: TestCsr:3, value: TestValue:0xf9d8_9938 }, + TestData{ csr: TestCsr:2, value: TestValue:0xc262_2d2e }, + TestData{ csr: TestCsr:2, value: TestValue:0xb4dd_424e }, + TestData{ csr: TestCsr:1, value: TestValue:0x01f9_b9e4 }, + TestData{ csr: TestCsr:1, value: TestValue:0x3020_6eec }, + TestData{ csr: TestCsr:3, value: TestValue:0x3124_87b5 }, + TestData{ csr: TestCsr:0, value: TestValue:0x0a49_f5e3 }, + TestData{ csr: TestCsr:2, value: TestValue:0xde3b_5d0f }, + TestData{ csr: TestCsr:3, value: TestValue:0x5948_c1b3 }, + TestData{ csr: TestCsr:0, value: TestValue:0xa26d_851f }, + TestData{ csr: TestCsr:3, value: TestValue:0x3fa9_59c0 }, + TestData{ csr: TestCsr:1, value: TestValue:0x4efd_dd09 }, + TestData{ csr: TestCsr:1, value: TestValue:0x6d75_058a }, +]; + +#[test_proc] +proc CsrConfig_test { + type TestCsrRdReq = CsrRdReq; + type TestCsrRdResp = CsrRdResp; + type TestCsrWrReq = CsrWrReq; + type TestCsrWrResp = CsrWrResp; + type TestCsrChange = CsrChange; + + terminator: chan out; + + ext_csr_rd_req_s: chan out; + ext_csr_rd_resp_r: chan in; + ext_csr_wr_req_s: chan out; + ext_csr_wr_resp_r: chan in; + + csr_rd_req_s: chan out; + csr_rd_resp_r: chan in; + csr_wr_req_s: chan out; + csr_wr_resp_r: chan in; + + csr_change_r: chan in; + + config (terminator: chan out) { + let (ext_csr_rd_req_s, ext_csr_rd_req_r) = chan("ext_csr_rd_req"); + let (ext_csr_rd_resp_s, ext_csr_rd_resp_r) = chan("ext_csr_rd_resp"); + + let (ext_csr_wr_req_s, ext_csr_wr_req_r) = chan("ext_csr_wr_req"); + let (ext_csr_wr_resp_s, ext_csr_wr_resp_r) = chan("ext_csr_wr_resp"); + + let (csr_rd_req_s, csr_rd_req_r) = chan("csr_rd_req"); + let (csr_rd_resp_s, csr_rd_resp_r) = chan("csr_rd_resp"); + + let (csr_wr_req_s, csr_wr_req_r) = chan("csr_wr_req"); + let (csr_wr_resp_s, csr_wr_resp_r) = chan("csr_wr_resp"); + + let (csr_change_s, csr_change_r) = chan("csr_change"); + + spawn CsrConfig ( + ext_csr_rd_req_r, ext_csr_rd_resp_s, + ext_csr_wr_req_r, ext_csr_wr_resp_s, + csr_rd_req_r, csr_rd_resp_s, + csr_wr_req_r, csr_wr_resp_s, + csr_change_s, + ); + + ( + terminator, + ext_csr_rd_req_s, ext_csr_rd_resp_r, + ext_csr_wr_req_s, ext_csr_wr_resp_r, + csr_rd_req_s, csr_rd_resp_r, + csr_wr_req_s, csr_wr_resp_r, + csr_change_r, + ) + } + + init { } + + next (state: ()) { + let expected_values = zero!(); + + // Test Writes through external interface + let (tok, expected_values) = for ((i, test_data), (tok, expected_values)): ((u32, TestData), (token, uN[TEST_DATA_W][TEST_REGS_N])) in enumerate(TEST_DATA) { + // write CSR via external interface + let wr_req = TestCsrWrReq { + csr: test_data.csr, + value: test_data.value, + }; + let tok = send(tok, ext_csr_wr_req_s, wr_req); + trace_fmt!("Sent #{} WrReq through external interface: {:#x}", i + u32:1, wr_req); + + let (tok, wr_resp) = recv(tok, ext_csr_wr_resp_r); + trace_fmt!("Received #{} WrResp through external interface: {:#x}", i + u32:1, wr_resp); + + // read CSR change + let (tok, csr_change) = recv(tok, csr_change_r); + trace_fmt!("Received #{} CSR change {:#x}", i + u32:1, csr_change); + + assert_eq(test_data.csr, csr_change.csr); + + // update expected values + let expected_values = update(expected_values, test_data.csr as u32, test_data.value); + + let tok = for (test_csr, tok): (u32, token) in u32:0..u32:4 { + let rd_req = TestCsrRdReq { + csr: test_csr as TestCsr, + }; + let expected_rd_resp = TestCsrRdResp{ + csr: test_csr as TestCsr, + value: expected_values[test_csr as u32] + }; + + // Read CSR via external interface + let tok = send(tok, ext_csr_rd_req_s, rd_req); + trace_fmt!("Sent #{} RdReq through external interface: {:#x}", i + u32:1, rd_req); + let (tok, rd_resp) = recv(tok, ext_csr_rd_resp_r); + trace_fmt!("Received #{} RdResp through external interface: {:#x}", i + u32:1, rd_resp); + assert_eq(expected_rd_resp, rd_resp); + + // Read CSR via internal interface + let tok = send(tok, csr_rd_req_s, rd_req); + trace_fmt!("Sent #{} RdReq through internal interface: {:#x}", i + u32:1, rd_req); + let (tok, csr_rd_resp) = recv(tok, csr_rd_resp_r); + trace_fmt!("Received #{} RdResp through internal interface: {:#x}", i + u32:1, csr_rd_resp); + assert_eq(expected_rd_resp, csr_rd_resp); + tok + }(tok); + + (tok, expected_values) + }((join(), expected_values)); + + // Test writes via internal interface + let (tok, _) = for ((i, test_data), (tok, expected_values)): ((u32, TestData), (token, uN[TEST_DATA_W][TEST_REGS_N])) in enumerate(TEST_DATA) { + // write CSR via request channel + let csr_wr_req = TestCsrWrReq { + csr: test_data.csr, + value: test_data.value, + }; + let tok = send(tok, csr_wr_req_s, csr_wr_req); + trace_fmt!("Sent #{} WrReq through internal interface: {:#x}", i + u32:1, csr_wr_req); + + let (tok, csr_wr_resp) = recv(tok, csr_wr_resp_r); + trace_fmt!("Received #{} WrResp through internal interface {:#x}", i + u32:1, csr_wr_resp); + + // read CSR change + let (tok, csr_change) = recv(tok, csr_change_r); + trace_fmt!("Received #{} CSR change {:#x}", i + u32:1, csr_change); + assert_eq(test_data.csr, csr_change.csr); + + // update expected values + let expected_values = update(expected_values, test_data.csr as u32, test_data.value); + + let tok = for (test_csr, tok): (u32, token) in u32:0..u32:4 { + let rd_req = TestCsrRdReq { + csr: test_csr as TestCsr, + }; + let expected_rd_resp = TestCsrRdResp{ + csr: test_csr as TestCsr, + value: expected_values[test_csr as u32] + }; + + // Read CSR via external interface + let tok = send(tok, ext_csr_rd_req_s, rd_req); + trace_fmt!("Sent #{} RdReq through external interface: {:#x}", i + u32:1, rd_req); + let (tok, rd_resp) = recv(tok, ext_csr_rd_resp_r); + trace_fmt!("Received #{} RdResp through external interface: {:#x}", i + u32:1, rd_resp); + assert_eq(expected_rd_resp, rd_resp); + + // Read CSR via internal interface + let tok = send(tok, csr_rd_req_s, rd_req); + trace_fmt!("Sent #{} RdReq through internal interface: {:#x}", i + u32:1, rd_req); + let (tok, csr_rd_resp) = recv(tok, csr_rd_resp_r); + trace_fmt!("Received #{} RdResp through internal interface: {:#x}", i + u32:1, csr_rd_resp); + assert_eq(expected_rd_resp, csr_rd_resp); + tok + }(tok); + + (tok, expected_values) + }((join(), expected_values)); + + send(tok, terminator, true); + } +} From d6e1b356e224aebf43669f3fc60cd584e6f81335 Mon Sep 17 00:00:00 2001 From: Krzysztof Oblonczek Date: Tue, 8 Oct 2024 18:02:20 +0200 Subject: [PATCH 10/85] modules/zstd: Add FrameHeaderDecoder Signed-off-by: Krzysztof Oblonczek --- xls/modules/zstd/BUILD | 112 ++--- xls/modules/zstd/frame_header_dec.x | 670 ++++++++++++++++++++++++++ xls/modules/zstd/frame_header_test.cc | 407 ---------------- xls/modules/zstd/frame_header_test.x | 30 -- 4 files changed, 703 insertions(+), 516 deletions(-) create mode 100644 xls/modules/zstd/frame_header_dec.x delete mode 100644 xls/modules/zstd/frame_header_test.cc delete mode 100644 xls/modules/zstd/frame_header_test.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index b717e7be96..b6ba693a28 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -158,16 +158,6 @@ cc_library( ], ) -xls_dslx_library( - name = "frame_header_dslx", - srcs = [ - "frame_header.x", - ], - deps = [ - ":buffer_dslx", - ], -) - xls_dslx_library( name = "common_dslx", srcs = [ @@ -176,116 +166,80 @@ xls_dslx_library( deps = [], ) -xls_dslx_test( - name = "frame_header_dslx_test", - dslx_test_args = {"compare": "jit"}, - library = ":frame_header_dslx", - tags = ["manual"], -) - xls_dslx_library( - name = "frame_header_test_dslx", + name = "frame_header_dec_dslx", srcs = [ - "frame_header_test.x", + "frame_header_dec.x", ], deps = [ - ":buffer_dslx", - ":frame_header_dslx", + "//xls/modules/zstd/memory:axi_dslx", + "//xls/modules/zstd/memory:mem_reader_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/fuzzing:fuzztest", - "//xls/common/status:matchers", - "//xls/common/status:ret_check", - "//xls/dslx:create_import_data", - "//xls/dslx:import_data", - "//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:bits", - "//xls/ir:value", - "//xls/simulation:sim_test_base", - "@com_google_absl//absl/container:flat_hash_map", - "@com_google_absl//absl/status:statusor", - "@com_google_absl//absl/types:span", - "@googletest//:gtest", - "@zstd", - ], +xls_dslx_test( + name = "frame_header_dec_dslx_test", + library = ":frame_header_dec_dslx", + tags = ["manual"], ) +frame_header_dec_codegen_args = common_codegen_args | { + "module_name": "FrameHeaderDecoder", + "clock_period_ps": "0", + "pipeline_stages": "6", +} + 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", + name = "frame_header_dec_verilog", + codegen_args = frame_header_dec_codegen_args, + dslx_top = "FrameHeaderDecoderInst", + library = ":frame_header_dec_dslx", tags = ["manual"], - verilog_file = "frame_header.v", + verilog_file = "frame_header_dec.v", ) xls_benchmark_ir( - name = "frame_header_opt_ir_benchmark", - src = ":frame_header_verilog.opt.ir", - benchmark_ir_args = { - "pipeline_stages": "9", - "delay_model": "asap7", + name = "frame_header_dec_opt_ir_benchmark", + src = ":frame_header_dec_verilog.opt.ir", + benchmark_ir_args = frame_header_dec_codegen_args | { + "top": "__frame_header_dec__FrameHeaderDecoderInst__FrameHeaderDecoder_0__16_32_30_5_next", + "pipeline_stages": "10", }, tags = ["manual"], ) verilog_library( - name = "frame_header_verilog_lib", + name = "frame_header_dec_verilog_lib", srcs = [ - ":frame_header.v", + ":frame_header_dec.v", ], tags = ["manual"], ) synthesize_rtl( - name = "frame_header_synth_asap7", + name = "frame_header_dec_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", tags = ["manual"], top_module = "FrameHeaderDecoder", deps = [ - ":frame_header_verilog_lib", + ":frame_header_dec_verilog_lib", ], ) benchmark_synth( - name = "frame_header_benchmark_synth", - synth_target = ":frame_header_synth_asap7", + name = "frame_header_dec_benchmark_synth", + synth_target = ":frame_header_dec_synth_asap7", tags = ["manual"], ) place_and_route( - name = "frame_header_place_and_route", - clock_period = "750", + name = "frame_header_dec_place_and_route", + clock_period = CLOCK_PERIOD_PS, core_padding_microns = 2, min_pin_distance = "0.5", placement_density = "0.30", stop_after_step = "global_routing", - synthesized_rtl = ":frame_header_synth_asap7", + synthesized_rtl = ":frame_header_dec_synth_asap7", tags = ["manual"], target_die_utilization_percentage = "10", ) diff --git a/xls/modules/zstd/frame_header_dec.x b/xls/modules/zstd/frame_header_dec.x new file mode 100644 index 0000000000..8647435996 --- /dev/null +++ b/xls/modules/zstd/frame_header_dec.x @@ -0,0 +1,670 @@ +// 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 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.memory.mem_reader; + +pub type WindowSize = u64; +pub type FrameContentSize = u64; +pub type DictionaryId = u32; + +// 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 FrameHeaderDecoderStatus: u2 { + OKAY = 0, + CORRUPTED = 1, + UNSUPPORTED_WINDOW_SIZE = 2, +} + +pub struct FrameHeaderDecoderReq { + addr: uN[ADDR_W], +} + +pub struct FrameHeaderDecoderResp { + status: FrameHeaderDecoderStatus, + header: FrameHeader, + length: u5, +} + +// 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, +} + +// 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 + } + ); +} + +// 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[3:8]; + let mantissa = data[0:3]; + + let window_base = (u42:1 << (u6:10 + exponent as u6)); + let window_base_add = (window_base >> u2:3) as u42; + // optimization: perform multiplication by a 3-bit value with adds and shifts + // because XLS only allows multiplying operands of the same width + let window_add = match mantissa { + u3:0 => u42:0, + u3:1 => window_base_add, // u39 + u3:2 => window_base_add + window_base_add, // u39 + u39 = u40 + u3:3 => (window_base_add << u1:1) + window_base_add, // u40 + u39 = u41 + u3:4 => (window_base_add << u1:1) + (window_base_add << u1:1), // u40 + u40 = u41 + u3:5 => (window_base_add << u2:2) + window_base_add, // u41 + u39 = u42 + u3:6 => (window_base_add << u2:2) + (window_base_add << u2:1), // u41 + u40 = u42 + u3:7 => (window_base_add << u2:3) - window_base_add, // u42 - u39 = u42 + _ => fail!("extract_window_size_from_window_descriptor_unreachable", u42:0), + }; + + window_base as u64 + window_add as u64 +} + +#[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); +} + +// 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); +} + + +// 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 +} + + +pub fn parse_frame_header(header_raw: uN[112]) -> (FrameHeader, u4, u1) { + let fhd_raw = header_raw[0:8]; + let fhd = extract_frame_header_descriptor(fhd_raw); + // RFC8878 Section 3.1.1.1.1.4 + // "This [reserved] bit is reserved for some future feature. Its value + // must be zero. A decoder compliant with this specification version must + // ensure it is not set." + let header_ok = !fhd.reserved; + + let window_descriptor_start = u32:1; + // RFC8878 Section 3.1.1.1.2 + // "When Single_Segment_Flag is set, Window_Descriptor is not present." + let window_descriptor_len = match fhd.single_segment_flag { + u1:0 => u1:1, + u1:1 => u1:0, + _ => fail!("window_descriptor_len_unreachable", u1:0), + }; + let window_descriptor_raw = header_raw[u32:8*window_descriptor_start+:u8]; + let window_size = extract_window_size_from_window_descriptor(window_descriptor_raw); + + let dictionary_id_start = window_descriptor_start + window_descriptor_len as u32; + let dictionary_id_len = match fhd.dictionary_id_flag { + u2:0 => u32:0, + u2:1 => u32:1, + u2:2 => u32:2, + u2:3 => u32:4, + _ => fail!("dictionary_id_len_unreachable", u32:0), + }; + let dictionary_id_raw = header_raw[u32:8*dictionary_id_start+:u32]; + let dictionary_id = dictionary_id_raw & match fhd.dictionary_id_flag { + u2:0 => u32:0x0000_0000, + u2:1 => u32:0x0000_00ff, + u2:2 => u32:0x0000_ffff, + u2:3 => u32:0xffff_ffff, + _ => fail!("dictionary_id_unreachable", u32:0), + }; + + let frame_content_size_start = dictionary_id_start + dictionary_id_len; + // RFC8878 Section 3.1.1.1.1.1 + // "When Frame_Content_Size_Flag is 0, FCS_Field_Size depends on + // Single_Segment_Flag: If Single_Segment_Flag is set, FCS_Field_Siz + // is 1. Otherwise, FCS_Field_Size is 0;" + let frame_content_size_len = match (fhd.frame_content_size_flag, fhd.single_segment_flag) { + (u2:0, u1:0) => u32:0, + (u2:0, u1:1) => u32:1, + (u2:1, _) => u32:2, + (u2:2, _) => u32:4, + (u2:3, _) => u32:8, + _ => fail!("frame_content_size_len_unreachable", u32:0), + }; + + let frame_content_size_raw = header_raw[u32:8*frame_content_size_start+:u64]; + let frame_content_size_masked = frame_content_size_raw & match frame_content_size_len { + u32:0 => u64:0x0000_0000_0000_0000, + u32:1 => u64:0x0000_0000_0000_00ff, + u32:2 => u64:0x0000_0000_0000_ffff, + u32:4 => u64:0x0000_0000_ffff_ffff, + u32:8 => u64:0xffff_ffff_ffff_ffff, + _ => fail!("frame_content_size_masked_unreachable", u64:0), + }; + + // RFC8878 Section 3.1.1.1.4 + // "When FCS_Field_Size is 2, the offset of 256 is added." + let frame_content_size = frame_content_size_masked + match frame_content_size_len { + u32:2 => u64:256, + _ => u64:0, + }; + + // RFC8878 Section 3.1.1.1.2 + // "When Single_Segment_Flag is set, Window_Descriptor is not present. + // In this case, Window_Size is Frame_Content_Size [...]" + let window_size = if (window_descriptor_exists(fhd)) { + window_size + } else if (frame_content_size_exists(fhd)) { + frame_content_size + } else { + WindowSize:0 + }; + + let total_header_len = (frame_content_size_start + frame_content_size_len) as u4; + + (FrameHeader { + window_size: window_size, + frame_content_size: if frame_content_size_len != u32:0 { frame_content_size } else { FrameContentSize:0 }, + dictionary_id: if dictionary_id_len != u32:0 { dictionary_id } else { DictionaryId:0 }, + content_checksum_flag: fhd.content_checksum_flag, + }, total_header_len, header_ok) +} + + +#[test] +fn test_parse_frame_header() { + // normal case + let test_vec = uN[112]:0x1234567890ABCDEF_CAFE_09_C2; + let (frame_header_result, len, ok) = parse_frame_header(test_vec); + assert_eq(frame_header_result, FrameHeader { + window_size: u64:0x900, + frame_content_size: u64:0x1234567890ABCDEF, + dictionary_id: u32:0xCAFE, + content_checksum_flag: u1:0, + }); + assert_eq(len, u4:12); + assert_eq(ok, u1:1); + + // SingleSegmentFlag is set + let test_vec = uN[112]:0xaa20; + let (frame_header_result, len, ok) = parse_frame_header(test_vec); + assert_eq(frame_header_result, FrameHeader { + window_size: u64:0xaa, + frame_content_size: u64:0xaa, + dictionary_id: u32:0x0, + content_checksum_flag: u1:0, + }); + assert_eq(len, u4:2); + assert_eq(ok, u1:1); + + // SingleSegmentFlag is set and FrameContentSize is bigger than accepted window_size + let test_vec = uN[112]:0x1234567890ABCDEF_CAFE_E2; + let (frame_header_result, len, ok) = parse_frame_header(test_vec); + assert_eq(frame_header_result, FrameHeader { + window_size: u64:0x1234567890ABCDEF, + frame_content_size: u64:0x1234567890ABCDEF, + dictionary_id: u32:0xCAFE, + content_checksum_flag: u1:0, + }); + assert_eq(len, u4:11); + assert_eq(ok, u1:1); + + // Frame header descriptor is corrupted (we don't check frame header and length) + let test_vec = uN[112]:0x1234567890ABCDEF_1234_09_CA; + let (_, _, ok) = parse_frame_header(test_vec); + assert_eq(ok, u1:0); + + // Large window size + let test_vec = uN[112]:0xd310; + let (frame_header_result, len, ok) = parse_frame_header(test_vec); + assert_eq(frame_header_result, FrameHeader { + window_size: u64:0x1600000000, + ..zero!() + }); + assert_eq(len, u4:2); + assert_eq(ok, u1:1); + + // Large window size + let test_vec = uN[112]:0xf45b5b5b0db1; + let (frame_header_result, len, ok) = parse_frame_header(test_vec); + assert_eq(frame_header_result, FrameHeader { + window_size: u64:0xf45b5b5b, + frame_content_size: u64:0xf45b5b5b, + dictionary_id: u32:0xD, + content_checksum_flag: u1:0, + }); + assert_eq(len, u4:6); + assert_eq(ok, u1:1); + + // Large window size + let test_vec = uN[112]:0xc0659db6813a16b33f3da53a79e4; + let (frame_header_result, len, ok) = parse_frame_header(test_vec); + assert_eq(frame_header_result, FrameHeader { + window_size: u64:0x3a16b33f3da53a79, + frame_content_size: u64:0x3a16b33f3da53a79, + dictionary_id: u32:0, + content_checksum_flag: u1:1, + }); + assert_eq(len, u4:9); + assert_eq(ok, u1:1); +} + + +enum FrameHeaderDecoderFsm: u1 { + RECV = 0, + RESP = 1 +} + +// Magic number value, as in: +// https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1 +const MAGIC_NUMBER = u32:0xFD2FB528; +const MAGIC_NUMBER_LEN = u32:4; + +const MAX_HEADER_LEN = u32:14; +const MAX_MAGIC_PLUS_HEADER_LEN = MAGIC_NUMBER_LEN + MAX_HEADER_LEN; + +struct FrameHeaderDecoderState { + fsm: FrameHeaderDecoderFsm, + xfers: u32, + raw_header: uN[XFER_SIZE][XFER_COUNT], +} + +pub proc FrameHeaderDecoder< + WINDOW_LOG_MAX: u32, + DATA_W: u32, + ADDR_W: u32, + XFERS_FOR_HEADER: u32 = {((MAX_MAGIC_PLUS_HEADER_LEN * u32:8) / DATA_W) + u32:1}, +> { + type State = FrameHeaderDecoderState; + type Fsm = FrameHeaderDecoderFsm; + type Req = FrameHeaderDecoderReq; + type Resp = FrameHeaderDecoderResp; + type ReaderReq = mem_reader::MemReaderReq; + type ReaderResp = mem_reader::MemReaderResp; + + reader_req_s: chan out; + reader_resp_r: chan in; + + decode_req_r: chan in; + decode_resp_s: chan out; + + config( + reader_req_s: chan out, + reader_resp_r: chan in, + decode_req_r: chan in, + decode_resp_s: chan out + ) { + (reader_req_s, reader_resp_r, decode_req_r, decode_resp_s) + } + + init { zero!() } + + next(state: State) { + type ReaderReq = mem_reader::MemReaderReq; + type State = FrameHeaderDecoderState; + + let tok0 = join(); + let (tok_req, req, do_req) = recv_non_blocking(tok0, decode_req_r, zero!()); + send_if(tok_req, reader_req_s, do_req, ReaderReq { addr: req.addr, length: MAX_MAGIC_PLUS_HEADER_LEN as uN[ADDR_W] }); + + let do_recv = (state.fsm == Fsm::RECV); + let (tok, resp, recvd) = recv_if_non_blocking(tok0, reader_resp_r, do_recv, zero!()); + + let do_resp = (state.fsm == Fsm::RESP); + let raw_header_bits = state.raw_header as uN[DATA_W * XFERS_FOR_HEADER]; + let raw_magic_number = raw_header_bits[:s32:8 * MAGIC_NUMBER_LEN as s32]; + let raw_header = raw_header_bits[s32:8 * MAGIC_NUMBER_LEN as s32 : s32:8 * MAX_MAGIC_PLUS_HEADER_LEN as s32]; + let magic_number_ok = raw_magic_number == MAGIC_NUMBER; + let (decoded_header, header_len, header_ok) = parse_frame_header(raw_header); + + let status = if (!header_ok || !magic_number_ok) { + FrameHeaderDecoderStatus::CORRUPTED + } else if (!window_size_valid(decoded_header.window_size)) { + FrameHeaderDecoderStatus::UNSUPPORTED_WINDOW_SIZE + } else { + FrameHeaderDecoderStatus::OKAY + }; + + let header_result = FrameHeaderDecoderResp { + status: status, + header: decoded_header, + length: header_len as u5 + MAGIC_NUMBER_LEN as u5, + }; + + send_if(tok0, decode_resp_s, do_resp, header_result); + + let next_state = match (state.fsm) { + Fsm::RECV => { + if (recvd) { + // raw_header is updated from the highest to lowest index because + // highest index in an array contains least significant bytes when + // casting to a bit vector + let update_idx = XFERS_FOR_HEADER - state.xfers - u32:1; + let next_raw_header = update(state.raw_header, update_idx, resp.data); + if (resp.last) { + State { raw_header: next_raw_header, fsm: Fsm::RESP, ..state } + } else { + State { raw_header: next_raw_header, xfers: state.xfers + u32:1, ..state } + } + } else { + state + } + }, + Fsm::RESP => { + State { fsm: Fsm::RECV, xfers: u32:0, ..state } + }, + _ => fail!("FrameHeaderDecoder_fsm_unreachable", zero!()) + }; + + next_state + } +} + +// The largest allowed WindowLog for DSLX tests +pub const TEST_WINDOW_LOG_MAX = u32:22; +pub const TEST_DATA_W = u32:32; +pub const TEST_ADDR_W = u32:16; +pub const TEST_XFERS_FOR_HEADER = ((MAX_MAGIC_PLUS_HEADER_LEN * u32:8) / TEST_DATA_W) + u32:1; + +#[test_proc] +proc FrameHeaderDecoderTest { + type Req = FrameHeaderDecoderReq; + type Resp = FrameHeaderDecoderResp; + type ReaderReq = mem_reader::MemReaderReq; + type ReaderResp = mem_reader::MemReaderResp; + + terminator: chan out; + + reader_req_r: chan in; + reader_resp_s: chan out; + + decode_req_s: chan out; + decode_resp_r: chan in; + + config(terminator: chan out) { + let (reader_req_s, reader_req_r) = chan("reader_req"); + let (reader_resp_s, reader_resp_r) = chan("reader_resp"); + let (decode_req_s, decode_req_r) = chan("decode_req"); + let (decode_resp_s, decode_resp_r) = chan("decode_resp"); + spawn FrameHeaderDecoder( + reader_req_s, + reader_resp_r, + decode_req_r, + decode_resp_s + ); + (terminator, reader_req_r, reader_resp_s, decode_req_s, decode_resp_r) + } + + init {} + + next(state: ()) { + let tok = join(); + let tests: (u32[TEST_XFERS_FOR_HEADER], FrameHeaderDecoderResp)[7] = [ + ( + // normal case + [u32:0xFD2FB528, u32:0xCAFE_09_C2, u32:0x90ABCDEF, u32:0x12345678, u32:0x0], + FrameHeaderDecoderResp { + header: FrameHeader { + window_size: u64:0x900, + frame_content_size: u64:0x1234567890ABCDEF, + dictionary_id: u32:0xCAFE, + content_checksum_flag: u1:0, + }, + status: FrameHeaderDecoderStatus::OKAY, + length: u5:16 + }, + ), ( + // SingleSegmentFlag is set + [u32:0xFD2FB528, u32:0xAA20, u32:0x0, u32:0x0, u32:0x0], + FrameHeaderDecoderResp { + header: FrameHeader { + window_size: u64:0xaa, + frame_content_size: u64:0xaa, + dictionary_id: u32:0x0, + content_checksum_flag: u1:0, + }, + status: FrameHeaderDecoderStatus::OKAY, + length: u5:6 + }, + ), ( + // SingleSegmentFlag is set and FrameContentSize is bigger than accepted window_size + [u32:0xFD2FB528, u32:0xEF_CAFE_E2, u32:0x7890ABCD, u32:0x123456, u32:0x0], + FrameHeaderDecoderResp { + header: FrameHeader { + window_size: u64:0x1234567890ABCDEF, + frame_content_size: u64:0x1234567890ABCDEF, + dictionary_id: u32:0xCAFE, + content_checksum_flag: u1:0, + }, + status: FrameHeaderDecoderStatus::UNSUPPORTED_WINDOW_SIZE, + length: u5:15 + }, + ), ( + // Frame header descriptor is corrupted (we don't check 'header' and 'length' fields) + [u32:0xFD2FB528, u32:0x1234_09_CA, u32:0x90ABCDEF, u32:0x12345678, u32:0x0], + FrameHeaderDecoderResp { + header: zero!(), + status: FrameHeaderDecoderStatus::CORRUPTED, + length: u5:0 + }, + ), ( + // Window size required by frame is too big for given decoder configuration + [u32:0xFD2FB528, u32:0xD310, u32:0x0, u32:0x0, u32:0x0], + FrameHeaderDecoderResp { + header: FrameHeader { + window_size: u64:0x1600000000, + ..zero!() + }, + status: FrameHeaderDecoderStatus::UNSUPPORTED_WINDOW_SIZE, + length: u5:6 + }, + ), ( + // Window size required by frame is too big for given decoder configuration + [u32:0xFD2FB528, u32:0x5B5B0DB1, u32:0xF45B, u32:0x0, u32:0x0], + FrameHeaderDecoderResp { + header: FrameHeader { + window_size: u64:0xf45b5b5b, + frame_content_size: u64:0xf45b5b5b, + dictionary_id: u32:0xD, + content_checksum_flag: u1:0, + }, + status: FrameHeaderDecoderStatus::UNSUPPORTED_WINDOW_SIZE, + length: u5:10 + }, + ), ( + // Window size required by frame is too big for given decoder configuration + [u32:0xFD2FB528, u32:0xA53A79E4, u32:0x16B33F3D, u32:0x9DB6813A, u32:0xC065], + FrameHeaderDecoderResp { + header: FrameHeader { + window_size: u64:0x3a16b33f3da53a79, + frame_content_size: u64:0x3a16b33f3da53a79, + dictionary_id: u32:0, + content_checksum_flag: u1:1, + }, + status: FrameHeaderDecoderStatus::UNSUPPORTED_WINDOW_SIZE, + length: u5:13 + } + ) + ]; + + const ADDR = u16:0x1234; + let tok = for ((_, (test_vec, expected)), tok): ((u32, (u32[TEST_XFERS_FOR_HEADER], FrameHeaderDecoderResp)), token) in enumerate(tests) { + let tok = send(tok, decode_req_s, FrameHeaderDecoderReq { addr: ADDR }); + let (tok, recv_data) = recv(tok, reader_req_r); + + assert_eq(recv_data, ReaderReq { addr: ADDR, length: MAX_MAGIC_PLUS_HEADER_LEN as u16 }); + + let tok = for ((j, word), tok): ((u32, u32), token) in enumerate(test_vec) { + let last = j + u32:1 == array_size(test_vec); + send(tok, reader_resp_s, ReaderResp { + status: mem_reader::MemReaderStatus::OKAY, + data: word, + length: if !last { (TEST_DATA_W / u32:8) as u16 } else { (MAX_MAGIC_PLUS_HEADER_LEN % TEST_XFERS_FOR_HEADER) as u16 }, + last: last, + }) + }(tok); + + let (tok, recv_data) = recv(tok, decode_resp_r); + if (recv_data.status == FrameHeaderDecoderStatus::OKAY || recv_data.status == FrameHeaderDecoderStatus::UNSUPPORTED_WINDOW_SIZE) { + assert_eq(recv_data, expected); + } else { + // if the header is corrupted we don't offer any guarantees + // about its contents so we just check that the status matches + assert_eq(recv_data.status, expected.status); + }; + + tok + }(tok); + + send(tok, terminator, true); + } +} + + +// 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 = u32:30; + +proc FrameHeaderDecoderInst { + type Req = FrameHeaderDecoderReq; + type Resp = FrameHeaderDecoderResp; + type ReaderReq = mem_reader::MemReaderReq; + type ReaderResp = mem_reader::MemReaderResp; + + reader_req_s: chan out; + reader_resp_r: chan in; + + decode_req_r: chan in; + decode_resp_s: chan out; + + config( + reader_req_s: chan out, + reader_resp_r: chan in, + decode_req_r: chan in, + decode_resp_s: chan out, + ) { + spawn FrameHeaderDecoder( + reader_req_s, + reader_resp_r, + decode_req_r, + decode_resp_s + ); + (reader_req_s, reader_resp_r, decode_req_r, decode_resp_s) + } + + init {} + + next(state: ()) {} +} diff --git a/xls/modules/zstd/frame_header_test.cc b/xls/modules/zstd/frame_header_test.cc deleted file mode 100644 index 55530c80f5..0000000000 --- a/xls/modules/zstd/frame_header_test.cc +++ /dev/null @@ -1,407 +0,0 @@ -// Copyright 2024 The XLS Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this kFile 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 - -// NOLINTBEGIN(build/include_order) - Silence include order warnings. -#include "xls/simulation/sim_test_base.h" -#define ZSTD_STATIC_LINKING_ONLY 1 - -#include -#include -#include -#include -#include // NOLINT -#include -#include -#include -// NOLINTEND(build/include_order) - Silence include order warnings. - -#include "gtest/gtest.h" -#include "xls/common/fuzzing/fuzztest.h" -#include "absl/container/flat_hash_map.h" -#include "absl/status/statusor.h" -#include "absl/types/span.h" -#include "xls/common/file/filesystem.h" -#include "xls/common/file/get_runfile_path.h" -#include "xls/common/status/matchers.h" -#include "xls/common/status/ret_check.h" -#include "xls/dslx/create_import_data.h" -#include "xls/dslx/import_data.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/bits.h" -#include "xls/ir/value.h" -#include "xls/modules/zstd/data_generator.h" -#include "external/zstd/lib/zstd.h" -#include "external/zstd/lib/zstd_errors.h" - -namespace xls { -namespace { - -// Must be in sync with FrameHeaderStatus from -// xls/modules/zstd/frame_header.x -enum FrameHeaderStatus : uint8_t { - OK, - CORRUPTED, - NO_ENOUGH_DATA, - UNSUPPORTED_WINDOW_SIZE -}; - -class ZstdFrameHeader { - public: - absl::Span buffer() const { - return absl::MakeConstSpan(buffer_); - } - - ZSTD_frameHeader header() const { return header_; } - - size_t result() const { return result_; } - - ZstdFrameHeader(absl::Span buffer, ZSTD_frameHeader h, - size_t r) - : header_(h), result_(r) { - std::vector v(buffer.begin(), buffer.end()); - buffer_ = v; - } - // Parse a frame header from an arbitrary buffer with the ZSTD library. - static absl::StatusOr Parse( - absl::Span buffer) { - XLS_RET_CHECK(!buffer.empty()); - XLS_RET_CHECK(buffer.data() != nullptr); - ZSTD_frameHeader zstd_fh; - size_t result = ZSTD_getFrameHeader_advanced( - &zstd_fh, buffer.data(), buffer.size(), ZSTD_f_zstd1_magicless); - return ZstdFrameHeader(buffer, zstd_fh, result); - } - - private: - std::vector buffer_; - ZSTD_frameHeader header_; - size_t result_; -}; - -class FrameHeaderTest : public xls::SimTestBase { - public: - // Prepare simulation environment - void SetUp() override { - XLS_ASSERT_OK_AND_ASSIGN(std::filesystem::path path, - xls::GetXlsRunfilePath(this->kFile)); - 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->kFileName, - this->kModuleName, &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, kFunctionName, &import_data, - symbolic_bindings, options)); - } - - // Prepare inputs for DSLX simulation based on the given zstd header, - // form the expected output from the simulation, - // run the simulation of frame header parser and compare the results against - // expected values. - void RunAndExpectFrameHeader(const ZstdFrameHeader& zstd_frame_header) { - // Extend buffer contents to 128 bits if necessary. - const absl::Span buffer = zstd_frame_header.buffer(); - std::vector buffer_extended(kDslxBufferSizeBytes, 0); - absl::Span input_buffer; - if (buffer.size() < kDslxBufferSizeBytes) { - std::copy(buffer.begin(), buffer.end(), buffer_extended.begin()); - input_buffer = absl::MakeSpan(buffer_extended); - } else { - input_buffer = buffer; - } - - // Decide on the expected status - ZSTD_frameHeader zstd_fh = zstd_frame_header.header(); - size_t result = zstd_frame_header.result(); - 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; - } - // 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); - 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 std::string_view kFile = "xls/modules/zstd/frame_header_test.x"; - const std::string_view kModuleName = "frame_header_test"; - const std::string_view kFileName = "frame_header_test.x"; - const std::string_view kFunctionName = "parse_frame_header_128"; - std::string converted; - - private: - static const size_t kDslxBufferSize = 128; - static const size_t kDslxBufferSizeBytes = - (kDslxBufferSize + CHAR_BIT - 1) / CHAR_BIT; - - // Largest allowed WindowLog accepted by libzstd decompression function - // 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 kTestWindowLogMaxLibZstd in frame_header_test.x - const uint64_t kTestWindowLogMaxLibZstd = 30; - - // Maximal mantissa value for calculating maximal accepted window_size - // as per https://datatracker.ietf.org/doc/html/rfc8878#name-window-descriptor - const uint64_t kMaxMantissa = 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(uint64_t window_size) { - auto max_window_size = - (1 << kTestWindowLogMaxLibZstd) + - (((1 << kTestWindowLogMaxLibZstd) >> 3) * kMaxMantissa); - - return window_size <= max_window_size; - } - - // 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) { - 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)), - }); - } - 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, - size_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 || - expected_status == FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE) { - return Value::Tuple({/*contents:*/ Value(UBits(0, kDslxBufferSize)), - /*length:*/ Value(UBits(0, 32))}); - } - - // Frame Header parsing succeeded. Expect output buffer contents with - // removed first `consumed_bytes_count` bytes and extended to - // kDslxBufferSize if necessary - size_t bytes_to_extend = - kDslxBufferSizeBytes - (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, kDslxBufferSize)); - size_t output_buffer_size_bits = - (input_buffer.size() - consumed_bytes_count) * CHAR_BIT; - size_t expected_buffer_size = output_buffer_size_bits > kDslxBufferSize - ? kDslxBufferSize - : 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(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, - /*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 > kDslxBufferSizeBytes) { - size = kDslxBufferSizeBytes; - } - - return Value::Tuple( - {/*contents:*/ Value(Bits::FromBytes(input_buffer, kDslxBufferSize)), - /*length:*/ Value(UBits(size * CHAR_BIT, 32))}); - } -}; - -/* TESTS */ - -TEST_F(FrameHeaderTest, Success) { - XLS_ASSERT_OK_AND_ASSIGN( - auto header, - ZstdFrameHeader::Parse({0xC2, 0x09, 0xFE, 0xCA, 0xEF, 0xCD, 0xAB, 0x90, - 0x78, 0x56, 0x34, 0x12})); - this->RunAndExpectFrameHeader(header); -} - -TEST_F(FrameHeaderTest, FailCorruptedReservedBit) { - XLS_ASSERT_OK_AND_ASSIGN( - auto header, ZstdFrameHeader::Parse({0xEA, 0xFE, 0xCA, 0xEF, 0xCD, 0xAB, - 0x90, 0x78, 0x56, 0x34, 0x12})); - this->RunAndExpectFrameHeader(header); -} - -TEST_F(FrameHeaderTest, FailUnsupportedWindowSizeTooBig) { - XLS_ASSERT_OK_AND_ASSIGN(auto header, ZstdFrameHeader::Parse({0x10, 0xD3})); - this->RunAndExpectFrameHeader(header); -} - -TEST_F(FrameHeaderTest, FailNoEnoughData) { - XLS_ASSERT_OK_AND_ASSIGN(auto header, ZstdFrameHeader::Parse({0xD3, 0xED})); - this->RunAndExpectFrameHeader(header); -} - -// NO_ENOUGH_DATA has priority over CORRUPTED from reserved bit -TEST_F(FrameHeaderTest, FailNoEnoughDataReservedBit) { - XLS_ASSERT_OK_AND_ASSIGN(auto header, ZstdFrameHeader::Parse({0xED, 0xD3})); - this->RunAndExpectFrameHeader(header); -} - -TEST_F(FrameHeaderTest, FailUnsupportedFrameContentSizeThroughSingleSegment) { - XLS_ASSERT_OK_AND_ASSIGN( - auto header, ZstdFrameHeader::Parse({0261, 015, 91, 91, 91, 0364})); - this->RunAndExpectFrameHeader(header); -} - -TEST_F(FrameHeaderTest, - FailUnsupportedVeryLargeFrameContentSizeThroughSingleSegment) { - XLS_ASSERT_OK_AND_ASSIGN( - auto header, - ZstdFrameHeader::Parse({0344, 'y', ':', 0245, '=', '?', 0263, 0026, ':', - 0201, 0266, 0235, 'e', 0300})); - this->RunAndExpectFrameHeader(header); -} - -TEST_F(FrameHeaderTest, FailUnsupportedWindowSize) { - XLS_ASSERT_OK_AND_ASSIGN( - auto header, - ZstdFrameHeader::Parse({'S', 0301, 'i', 0320, 0, 0256, 'd', 'D', 0226, - 'F', 'Z', 'Z', 0332, 0370, 'A'})); - this->RunAndExpectFrameHeader(header); -} - -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(); - XLS_ASSERT_OK_AND_ASSIGN(auto buffer, zstd::GenerateFrameHeader(seed, false)); - XLS_ASSERT_OK_AND_ASSIGN(auto frame_header, ZstdFrameHeader::Parse(buffer)); - this->RunAndExpectFrameHeader(frame_header); -} - -INSTANTIATE_TEST_SUITE_P( - FrameHeaderSeededTest, FrameHeaderSeededTest, - ::testing::Range(0, FrameHeaderSeededTest::random_headers_count)); - -class FrameHeaderFuzzTest - : public fuzztest::PerFuzzTestFixtureAdapter { - public: - void ParseMultipleRandomFrameHeaders(const std::vector& buffer) { - auto frame_header = ZstdFrameHeader::Parse(buffer); - XLS_ASSERT_OK(frame_header); - this->RunAndExpectFrameHeader(frame_header.value()); - } -}; - -// 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 deleted file mode 100644 index 9216dfab8d..0000000000 --- a/xls/modules/zstd/frame_header_test.x +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2024 The XLS Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import std; -import xls.modules.zstd.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 e3477dee1ee8e9264348d67686caaae7cef1b7ef Mon Sep 17 00:00:00 2001 From: Maciej Torhan Date: Tue, 8 Oct 2024 18:04:07 +0200 Subject: [PATCH 11/85] modules/zstd: Add BlockHeaderDecoder Signed-off-by: Maciej Torhan --- xls/modules/zstd/BUILD | 78 ++++++++ xls/modules/zstd/block_header_dec.x | 293 ++++++++++++++++++++++++++++ 2 files changed, 371 insertions(+) create mode 100644 xls/modules/zstd/block_header_dec.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index b6ba693a28..ede9709eb9 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -261,6 +261,84 @@ xls_dslx_test( tags = ["manual"], ) +xls_dslx_library( + name = "block_header_dec_dslx", + srcs = [ + "block_header_dec.x", + ], + deps = [ + ":block_header_dslx", + ":common_dslx", + "//xls/modules/zstd/memory:mem_reader_dslx", + ], +) + +xls_dslx_test( + name = "block_header_dec_dslx_test", + library = ":block_header_dec_dslx", + tags = ["manual"], +) + +block_header_dec_codegen_args = common_codegen_args | { + "module_name": "BlockHeaderDec", + "pipeline_stages": "1", +} + +xls_dslx_verilog( + name = "block_header_dec_verilog", + codegen_args = block_header_dec_codegen_args, + dslx_top = "BlockHeaderDecoderInst", + library = ":block_header_dec_dslx", + tags = ["manual"], + verilog_file = "block_header_dec.v", +) + +xls_benchmark_ir( + name = "block_header_dec_opt_ir_benchmark", + src = ":block_header_dec_verilog.opt.ir", + benchmark_ir_args = block_header_dec_codegen_args | { + "pipeline_stages": "10", + "top": "__block_header_dec__BlockHeaderDecoderInst__BlockHeaderDecoder_0__16_64_next", + }, + tags = ["manual"], +) + +verilog_library( + name = "block_header_dec_verilog_lib", + srcs = [ + ":block_header_dec.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "block_header_dec_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "BlockHeaderDec", + deps = [ + ":block_header_dec_verilog_lib", + ], +) + +benchmark_synth( + name = "block_header_dec_benchmark_synth", + synth_target = ":block_header_dec_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "block_header_dec_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":block_header_dec_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + xls_dslx_library( name = "raw_block_dec_dslx", srcs = [ diff --git a/xls/modules/zstd/block_header_dec.x b/xls/modules/zstd/block_header_dec.x new file mode 100644 index 0000000000..45c69e921c --- /dev/null +++ b/xls/modules/zstd/block_header_dec.x @@ -0,0 +1,293 @@ +// 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 xls.modules.zstd.block_header as block_header; +import xls.modules.zstd.common as common; +import xls.modules.zstd.memory.mem_reader as mem_reader; + +type BlockSize = common::BlockSize; +type BlockType = common::BlockType; +type BlockHeader = block_header::BlockHeader; + +pub struct BlockHeaderDecoderReq { + addr: uN[ADDR_W], +} + +pub enum BlockHeaderDecoderStatus: u2 { + OKAY = 0, + CORRUPTED = 1, + MEMORY_ACCESS_ERROR = 2, +} + +pub struct BlockHeaderDecoderResp { + status: BlockHeaderDecoderStatus, + header: BlockHeader, + rle_symbol: u8, +} + +pub proc BlockHeaderDecoder { + type Req = BlockHeaderDecoderReq; + type Resp = BlockHeaderDecoderResp; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + type MemReaderStatus = mem_reader::MemReaderStatus; + + type Status = BlockHeaderDecoderStatus; + type Length = uN[ADDR_W]; + type Addr = uN[ADDR_W]; + + req_r: chan in; + resp_s: chan out; + mem_req_s: chan out; + mem_resp_r: chan in; + + config ( + req_r: chan in, + resp_s: chan out, + mem_req_s: chan out, + mem_resp_r: chan in, + ) { + (req_r, resp_s, mem_req_s, mem_resp_r) + } + + init { } + + next (state: ()) { + let tok0 = join(); + + // receive request + let (tok1_0, req, req_valid) = recv_non_blocking(tok0, req_r, zero!()); + + // send memory read request + let mem_req = MemReaderReq {addr: req.addr, length: Length:4 }; + let tok2_0 = send_if(tok1_0, mem_req_s, req_valid, mem_req); + + // receive memory read response + let (tok1_1, mem_resp, mem_resp_valid) = recv_non_blocking(tok0, mem_resp_r, zero!()); + + let header = block_header::extract_block_header(mem_resp.data as u24); + let rle_symbol = mem_resp.data[u32:24 +: u8]; + let status = match ( mem_resp.status == MemReaderStatus::OKAY, header.btype != BlockType::RESERVED) { + (true, true) => Status::OKAY, + (true, false) => Status::CORRUPTED, + ( _, _) => Status::MEMORY_ACCESS_ERROR, + }; + + let resp = Resp { status, header, rle_symbol }; + let tok2_1 = send_if(tok1_1, resp_s, mem_resp_valid, resp); + } +} + +const INST_DATA_W = u32:64; +const INST_ADDR_W = u32:16; + +proc BlockHeaderDecoderInst { + type Req = BlockHeaderDecoderReq; + type Resp = BlockHeaderDecoderResp; + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + + config ( + req_r: chan in, + resp_s: chan out, + mem_req_s: chan out, + mem_resp_r: chan in, + ) { + spawn BlockHeaderDecoder( req_r, resp_s, mem_req_s, mem_resp_r); + } + + init { } + next (state: ()) { } +} + +const TEST_DATA_W = u32:32; +const TEST_ADDR_W = u32:32; + +fn header_to_raw(header: BlockHeader, rle_symbol: u8) -> u32 { + rle_symbol ++ header.size ++ (header.btype as u2) ++ header.last +} + + +#[test_proc] +proc BlockHeaderDecoderTest { + type Req = BlockHeaderDecoderReq; + type Resp = BlockHeaderDecoderResp; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + type MemReaderStatus = mem_reader::MemReaderStatus; + + type Data = uN[TEST_DATA_W]; + type Addr = uN[TEST_ADDR_W]; + type Length = uN[TEST_ADDR_W]; + + terminator: chan out; + + req_s: chan out; + resp_r: chan in; + + mem_req_r: chan in; + mem_resp_s: chan out; + + config (terminator: chan out) { + + let (req_s, req_r) = chan("req"); + let (resp_s, resp_r) = chan("resp"); + + let (mem_req_s, mem_req_r) = chan("mem_req"); + let (mem_resp_s, mem_resp_r) = chan("mem_resp"); + + spawn BlockHeaderDecoder ( + req_r, resp_s, mem_req_s, mem_resp_r + ); + + (terminator, req_s, resp_r, mem_req_r, mem_resp_s) + } + + init { } + + next (state: ()) { + const LENGTH = Length:4; + + let tok = join(); + + // Test Raw + let addr = Addr:0x1234; + let header = BlockHeader { size: BlockSize:0x100, btype: BlockType::RAW, last: true}; + let rle_symbol = u8:0; + + let req = Req { addr }; + let tok = send(tok, req_s, req); + + let (tok, mem_req) = recv(tok, mem_req_r); + assert_eq(mem_req, MemReaderReq { addr, length: LENGTH }); + + let mem_resp = MemReaderResp { + status: MemReaderStatus::OKAY, + data: checked_cast(header_to_raw(header, rle_symbol)), + length: LENGTH, + last: true, + }; + let tok = send(tok, mem_resp_s, mem_resp); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: BlockHeaderDecoderStatus::OKAY, + header: header, + rle_symbol: rle_symbol + }); + + // Test RLE + let addr = Addr:0x2000; + let header = BlockHeader { size: BlockSize:0x40, btype: BlockType::RLE, last: false}; + let rle_symbol = u8:123; + + let req = Req { addr }; + let tok = send(tok, req_s, req); + + let (tok, mem_req) = recv(tok, mem_req_r); + assert_eq(mem_req, MemReaderReq { addr, length: LENGTH }); + + let mem_resp = MemReaderResp { + status: MemReaderStatus::OKAY, + data: checked_cast(header_to_raw(header, rle_symbol)), + length: LENGTH, + last: true, + }; + let tok = send(tok, mem_resp_s, mem_resp); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: BlockHeaderDecoderStatus::OKAY, + header: header, + rle_symbol: rle_symbol + }); + + // Test COMPRESSED + let addr = Addr:0x2000; + let header = BlockHeader { size: BlockSize:0x40, btype: BlockType::COMPRESSED, last: true}; + let rle_symbol = u8:0; + + let req = Req { addr }; + let tok = send(tok, req_s, req); + + let (tok, mem_req) = recv(tok, mem_req_r); + assert_eq(mem_req, MemReaderReq { addr, length: LENGTH }); + + let mem_resp = MemReaderResp { + status: MemReaderStatus::OKAY, + data: checked_cast(header_to_raw(header, rle_symbol)), + length: LENGTH, + last: true, + }; + let tok = send(tok, mem_resp_s, mem_resp); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: BlockHeaderDecoderStatus::OKAY, + header: header, + rle_symbol: rle_symbol + }); + + // Test RESERVED + let addr = Addr:0x2000; + let header = BlockHeader { size: BlockSize:0x40, btype: BlockType::RESERVED, last: true}; + let rle_symbol = u8:0; + + let req = Req { addr }; + let tok = send(tok, req_s, req); + + let (tok, mem_req) = recv(tok, mem_req_r); + assert_eq(mem_req, MemReaderReq { addr, length: LENGTH }); + + let mem_resp = MemReaderResp { + status: MemReaderStatus::OKAY, + data: checked_cast(header_to_raw(header, rle_symbol)), + length: LENGTH, + last: true, + }; + let tok = send(tok, mem_resp_s, mem_resp); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: BlockHeaderDecoderStatus::CORRUPTED, + header: header, + rle_symbol: rle_symbol + }); + + // Test memory error + let addr = Addr:0x2000; + let header = BlockHeader { size: BlockSize:0x40, btype: BlockType::RESERVED, last: true}; + let rle_symbol = u8:0; + + let req = Req { addr }; + let tok = send(tok, req_s, req); + + let (tok, mem_req) = recv(tok, mem_req_r); + assert_eq(mem_req, MemReaderReq { addr, length: LENGTH }); + + let mem_resp = MemReaderResp { + status: MemReaderStatus::ERROR, + data: checked_cast(header_to_raw(header, rle_symbol)), + length: LENGTH, + last: true, + }; + let tok = send(tok, mem_resp_s, mem_resp); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: BlockHeaderDecoderStatus::MEMORY_ACCESS_ERROR, + header: header, + rle_symbol: rle_symbol + }); + + send(tok, terminator, true); + } +} From 59f7d9d4e501428848825d9c36e56baec13c765a Mon Sep 17 00:00:00 2001 From: Maciej Torhan Date: Tue, 8 Oct 2024 18:06:13 +0200 Subject: [PATCH 12/85] modules/zstd: Add RawBlockDecoder Co-authored-by: Pawel Czarnecki Signed-off-by: Maciej Torhan Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 25 ++- xls/modules/zstd/raw_block_dec.x | 312 +++++++++++++++++++++++++------ 2 files changed, 267 insertions(+), 70 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index ede9709eb9..79021e59b1 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -345,8 +345,8 @@ xls_dslx_library( "raw_block_dec.x", ], deps = [ - ":buffer_dslx", ":common_dslx", + "//xls/modules/zstd/memory:mem_reader_dslx", ], ) @@ -357,16 +357,15 @@ xls_dslx_test( tags = ["manual"], ) +raw_block_dec_codegen_args = common_codegen_args | { + "module_name": "RawBlockDecoder", + "pipeline_stages": "1", +} + 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", + codegen_args = raw_block_dec_codegen_args, + dslx_top = "RawBlockDecoderInst", library = ":raw_block_dec_dslx", tags = ["manual"], verilog_file = "raw_block_dec.v", @@ -375,9 +374,9 @@ xls_dslx_verilog( 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", + benchmark_ir_args = raw_block_dec_codegen_args | { + "pipeline_stages": "10", + "top": "__raw_block_dec__RawBlockDecoderInst__RawBlockDecoder_0__32_32_next", }, tags = ["manual"], ) @@ -408,7 +407,7 @@ benchmark_synth( place_and_route( name = "raw_block_dec_place_and_route", - clock_period = "750", + clock_period = CLOCK_PERIOD_PS, core_padding_microns = 2, min_pin_distance = "0.5", placement_density = "0.30", diff --git a/xls/modules/zstd/raw_block_dec.x b/xls/modules/zstd/raw_block_dec.x index a3656011b0..669b66d5b1 100644 --- a/xls/modules/zstd/raw_block_dec.x +++ b/xls/modules/zstd/raw_block_dec.x @@ -17,6 +17,7 @@ // https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1.2.2 import xls.modules.zstd.common as common; +import xls.modules.zstd.memory.mem_reader as mem_reader; type BlockDataPacket = common::BlockDataPacket; type BlockPacketLength = common::BlockPacketLength; @@ -26,92 +27,289 @@ 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 +pub struct RawBlockDecoderReq { + id: u32, + addr: uN[ADDR_W], + length: uN[ADDR_W], + last_block: bool, } -const ZERO_RAW_BLOCK_DECODER_STATE = zero!(); +pub enum RawBlockDecoderStatus: u1 { + OKAY = 0, + ERROR = 1, +} + +pub struct RawBlockDecoderResp { + status: RawBlockDecoderStatus, +} + +struct RawBlockDecoderState { + id: u32, // ID of the block + last_block: bool, // if the block is the last one +} // 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; +pub proc RawBlockDecoder { + type Req = RawBlockDecoderReq; + type Resp = RawBlockDecoderResp; + type Output = ExtendedBlockDataPacket; + type Status = RawBlockDecoderStatus; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + type MemReaderStatus = mem_reader::MemReaderStatus; + + type State = RawBlockDecoderState; - init { (ZERO_RAW_BLOCK_DECODER_STATE) } + // decoder input + req_r: chan in; + resp_s: chan out; + + // decoder output + output_s: chan out; + + // memory interface + mem_req_s: chan out; + mem_resp_r: chan in; + + init { zero!() } config( - input_r: chan in, - output_s: chan out - ) {(input_r, output_s)} + req_r: chan in, + resp_s: chan out, + output_s: chan out, - 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 + mem_req_s: chan out, + mem_resp_r: chan in, + ) { + ( + req_r, resp_s, output_s, + mem_req_s, mem_resp_r, + ) + } + + next(state: State) { + let tok0 = join(); + + // receive request + let (tok1_0, req, req_valid) = recv_non_blocking(tok0, req_r, zero!>()); + + // update ID and last in state + let state = if req_valid { + State { id: req.id, last_block: req.last_block} + } else { state }; + + // send memory read request + let req = MemReaderReq { addr: req.addr, length: req.length }; + let tok2_0 = send_if(tok1_0, mem_req_s, req_valid, req); + + // receive memory read response + let (tok1_1, mem_resp, mem_resp_valid) = recv_non_blocking(tok0, mem_resp_r, zero!()); + let mem_resp_error = (mem_resp.status != MemReaderStatus::OKAY); + + // prepare output data, decoded RAW block is always a literal + let output_data = Output { 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, + last: mem_resp.last, + last_block: state.last_block, + id: state.id, + data: checked_cast(mem_resp.data), + length: checked_cast(mem_resp.length ++ u3:0), }, }; - let tok = send(tok, output_s, output_data); + // send output data + let mem_resp_correct = mem_resp_valid && !mem_resp_error; + let tok2_1 = send_if(tok1_1, output_s, mem_resp_correct, output_data); + + // send response after block end + let resp = if mem_resp_correct { + Resp { status: Status::OKAY } + } else { + Resp { status: Status::ERROR } + }; + + let do_send_resp = mem_resp_valid && mem_resp.last; + let tok2_2 = send_if(tok1_1, resp_s, do_send_resp, resp); - RawBlockDecoderState { - prev_valid: true, - prev_id: output_data.packet.id, - prev_last: output_data.packet.last - } + state } } +const INST_DATA_W = u32:32; +const INST_ADDR_W = u32:32; + +pub proc RawBlockDecoderInst { + type Req = RawBlockDecoderReq; + type Resp = RawBlockDecoderResp; + type Output = ExtendedBlockDataPacket; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + + config ( + req_r: chan in, + resp_s: chan out, + output_s: chan out, + mem_req_s: chan out, + mem_resp_r: chan in, + ) { + spawn RawBlockDecoder( + req_r, resp_s, output_s, mem_req_s, mem_resp_r + ); + } + + init { } + + next (state: ()) { } +} + +const TEST_DATA_W = u32:64; +const TEST_ADDR_W = u32:32; + #[test_proc] proc RawBlockDecoderTest { + type Req = RawBlockDecoderReq; + type Resp = RawBlockDecoderResp; + type Output = ExtendedBlockDataPacket; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + + type Data = uN[TEST_DATA_W]; + type Addr = uN[TEST_ADDR_W]; + type Length = uN[TEST_ADDR_W]; + terminator: chan out; - dec_input_s: chan out; - dec_output_r: chan in; + + req_s: chan out; + resp_r: chan in; + output_r: chan in; + + mem_req_r: chan in; + mem_resp_s: chan out; 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) + let (req_s, req_r) = chan("req"); + let (resp_s, resp_r) = chan("resp"); + let (output_s, output_r) = chan("output"); + + let (mem_req_s, mem_req_r) = chan("mem_req"); + let (mem_resp_s, mem_resp_r) = chan("mem_resp"); + + spawn RawBlockDecoder( + req_r, resp_s, output_s, mem_req_s, mem_resp_r + ); + + (terminator, req_s, resp_r, output_r, mem_req_r, mem_resp_s) } 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); + + // Test 0 + let req = Req { id: u32:0, last_block: false, addr: Addr:0, length: Length:8 }; + let tok = send(tok, req_s, req); + + let (tok, mem_req) = recv(tok, mem_req_r); + assert_eq(mem_req, MemReaderReq { addr: Addr:0, length: Length:8 }); + + let mem_resp = MemReaderResp { + status: mem_reader::MemReaderStatus::OKAY, + data: Data:0x1122_3344, + length: Length:8, + last: true, + }; + let tok = send(tok, mem_resp_s, mem_resp); + let (tok, output) = recv(tok, output_r); + assert_eq(output, Output { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { + last: true, + last_block: false, + id: u32:0, + data: Data:0x1122_3344, + length: Length:64, + }, + }); + + // Test 1 + let req = Req { id: u32:1, last_block: true, addr: Addr:0x1001, length: Length:15 }; + let tok = send(tok, req_s, req); + + let (tok, mem_req) = recv(tok, mem_req_r); + assert_eq(mem_req, MemReaderReq { addr: Addr:0x1001, length: Length:15 }); + + let mem_resp = MemReaderResp { + status: mem_reader::MemReaderStatus::OKAY, + data: Data:0x1122_3344_5566_7788, + length: Length:8, + last: false + }; + let tok = send(tok, mem_resp_s, mem_resp); + + let mem_resp = MemReaderResp { + status: mem_reader::MemReaderStatus::OKAY, + data: Data:0xAA_BBCC_DDEE_FF99, + length: Length:7, + last: true, + }; + let tok = send(tok, mem_resp_s, mem_resp); + + let (tok, output) = recv(tok, output_r); + assert_eq(output, Output { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { + last: false, + last_block: true, + id: u32:1, + data: Data:0x1122_3344_5566_7788, + length: Length:64, + }, + }); + + let (tok, output) = recv(tok, output_r); + assert_eq(output, Output { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { + last: true, + last_block: true, + id: u32:1, + data: Data:0xAA_BBCC_DDEE_FF99, + length: Length:56, + }, + }); + + // Test 2 + let req = Req {id: u32:2, last_block: false, addr: Addr:0x2000, length: Length:0 }; + let tok = send(tok, req_s, req); + + let (tok, mem_req) = recv(tok, mem_req_r); + assert_eq(mem_req, MemReaderReq { addr: Addr:0x2000, length: Length:0 }); + + let mem_resp = MemReaderResp { + status: mem_reader::MemReaderStatus::OKAY, + data: Data:0x0, + length: Length:0, + last: true, + }; + let tok = send(tok, mem_resp_s, mem_resp); + let (tok, output) = recv(tok, output_r); + assert_eq(output, Output { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { + last: true, + last_block: false, + id: u32:2, + data: Data:0x0, + length: Length:0, + }, + }); send(tok, terminator, true); } From 978a8e0d3830be4326ffc9626415ee7149cc6996 Mon Sep 17 00:00:00 2001 From: Maciej Torhan Date: Tue, 8 Oct 2024 17:59:46 +0200 Subject: [PATCH 13/85] modules/zstd: Add RleBlockDecoder Co-authored-by: Pawel Czarnecki Co-authored-by: Robert Winkler Signed-off-by: Maciej Torhan Signed-off-by: Pawel Czarnecki Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 33 +- xls/modules/zstd/rle_block_dec.x | 844 +++++++------------------------ 2 files changed, 190 insertions(+), 687 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 79021e59b1..255418a161 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -423,10 +423,7 @@ xls_dslx_library( "rle_block_dec.x", ], deps = [ - ":buffer_dslx", ":common_dslx", - "//xls/modules/rle:rle_common_dslx", - "//xls/modules/rle:rle_dec_dslx", ], ) @@ -437,22 +434,16 @@ xls_dslx_test( tags = ["manual"], ) +rle_block_dec_codegen_args = common_codegen_args | { + "module_name": "RleBlockDecoder", + "pipeline_stages": "1", +} + 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", - "fifo_module": "", - "materialize_internal_fifos": "true", - }, - dslx_top = "RleBlockDecoder", + codegen_args = rle_block_dec_codegen_args, + dslx_top = "RleBlockDecoderInst", library = ":rle_block_dec_dslx", - opt_ir_args = { - "top": "__rle_block_dec__RleBlockDecoder__BatchPacker_0_next", - }, tags = ["manual"], verilog_file = "rle_block_dec.v", ) @@ -460,11 +451,9 @@ xls_dslx_verilog( 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", - "fifo_module": "", - "materialize_internal_fifos": "true", + benchmark_ir_args = rle_block_dec_codegen_args | { + "pipeline_stages": "10", + "top": "__rle_block_dec__RleBlockDecoderInst__RleBlockDecoder_0__64_next", }, tags = ["manual"], ) @@ -495,7 +484,7 @@ benchmark_synth( place_and_route( name = "rle_block_dec_place_and_route", - clock_period = "750", + clock_period = CLOCK_PERIOD_PS, core_padding_microns = 2, min_pin_distance = "0.5", placement_density = "0.30", diff --git a/xls/modules/zstd/rle_block_dec.x b/xls/modules/zstd/rle_block_dec.x index 232d9a6381..c5529c978b 100644 --- a/xls/modules/zstd/rle_block_dec.x +++ b/xls/modules/zstd/rle_block_dec.x @@ -12,44 +12,10 @@ // 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 std; 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; @@ -61,696 +27,244 @@ 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 +pub enum RleBlockDecoderStatus: u1 { + OKAY = 0, } -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); - } +pub struct RleBlockDecoderReq { + id: u32, + symbol: u8, + length: BlockSize, + last_block: bool, } -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); - } +pub struct RleBlockDecoderResp { + status: RleBlockDecoderStatus } -#[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 RleBlockDecoderState { + req: RleBlockDecoderReq, + req_valid: bool, } -struct BatchPackerState { - batch: BlockData, - symbols_in_batch: BlockPacketLength, - symbols_in_block: BlockPacketLength, - prev_last: bool, - prev_sync: BlockSyncData, -} +pub proc RleBlockDecoder { + type Req = RleBlockDecoderReq; + type Resp = RleBlockDecoderResp; + type Output = ExtendedBlockDataPacket; -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}; + type State = RleBlockDecoderState; -proc BatchPacker { - rle_data_r: chan in; - sync_r: chan in; - block_data_s: chan out; + req_r: chan in; + resp_s: chan out; + output_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) - } + config( req_r: chan in, + resp_s: chan out, + output_s: chan out, + ) { (req_r, resp_s, output_s) } - // Init the state to signal new batch to process - init { (BatchPackerState { prev_last: true, ..ZERO_BATCH_STATE }) } + init { zero!() } - 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); - }; + next(state: State) { + const MAX_OUTPUT_SYMBOLS = (DATA_W / u32:8); + const MAX_LEN = MAX_OUTPUT_SYMBOLS as uN[common::BLOCK_SIZE_WIDTH]; - 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 tok0 = join(); - 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 (tok1, req) = recv_if(tok0, req_r, !state.req_valid, state.req); - 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 last = req.length <= MAX_LEN; + let length = if last { req.length } else { MAX_LEN }; + let data = unroll_for! (i, data): (u32, uN[DATA_W]) in range(u32:0, MAX_OUTPUT_SYMBOLS) { + bit_slice_update(data, i * u32:8, req.symbol) + }(uN[DATA_W]:0); - 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 + let output = Output { 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, + last, + last_block: req.last_block, + id: req.id, + data: checked_cast(data), + length: checked_cast(length << 3), } }; - 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) - }; + send_if(tok1, resp_s, last, zero!()); + send(tok1, output_s, output); - let (new_sync_data, new_symbols_in_block) = if (decoded_data.last || (sync_data.last_block && empty_block)) { - (ZERO_BLOCK_SYNC_DATA, BlockPacketLength:0) + if last { + zero!() } 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 + let length = req.length - MAX_LEN; + State { + req: Req { length, ..req }, + req_valid: true, + } + } } } -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) - } +const TEST_DATA_W = u32:64; - init { } +#[test_proc] +proc RleBlockDecoderTest { + type Req = RleBlockDecoderReq; + type Resp = RleBlockDecoderResp; + type Output = ExtendedBlockDataPacket; - 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); - } -} + type Data = uN[TEST_DATA_W]; + type Length = uN[common::BLOCK_SIZE_WIDTH]; -#[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"); + req_s: chan out; + resp_r: chan in; + output_r: chan in; + + config (terminator: chan out) { + let (req_s, req_r) = chan("req"); + let (resp_s, resp_r) = chan("resp"); + let (output_s, output_r) = chan("output"); - spawn BatchPacker(in_r, sync_r, out_s); + spawn RleBlockDecoder( + req_r, resp_s, output_s + ); - (terminator, in_s, sync_s, out_r) + (terminator, req_s, resp_r, output_r) } - init { } + init { } - next(state: ()) { + 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; + let tok = send(tok, req_s, Req { id: u32:5, symbol: u8:0xAB, length: Length:0x28, last_block: true }); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { status: RleBlockDecoderStatus::OKAY }); - 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"); + let (tok, output) = recv(tok, output_r); + assert_eq(output, Output { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { + last: false, + last_block: true, + id: u32:5, + data: BlockData:0xABAB_ABAB_ABAB_ABAB, + length: BlockPacketLength:64 + } + }); - spawn RleDataPacker(input_r, in_s, sync_s); - spawn rle_dec::RunLengthDecoder( - in_r, out_s); - spawn BatchPacker(out_r, sync_r, output_s); + let (tok, output) = recv(tok, output_r); + assert_eq(output, Output { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { + last: false, + last_block: true, + id: u32:5, + data: BlockData:0xABAB_ABAB_ABAB_ABAB, + length: BlockPacketLength:64 + } + }); - (input_r, output_s) - } - init { } + let (tok, output) = recv(tok, output_r); + assert_eq(output, Output { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { + last: false, + last_block: true, + id: u32:5, + data: BlockData:0xABAB_ABAB_ABAB_ABAB, + length: BlockPacketLength:64 + } + }); - next(state: ()) { } -} -#[test_proc] -proc RleBlockDecoder_test { - terminator: chan out; - in_s: chan out; - out_r: chan in; + let (tok, output) = recv(tok, output_r); + assert_eq(output, Output { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { + last: false, + last_block: true, + id: u32:5, + data: BlockData:0xABAB_ABAB_ABAB_ABAB, + length: BlockPacketLength:64 + } + }); - config(terminator: chan out) { - let (in_s, in_r) = chan("in"); - let (out_s, out_r) = chan("out"); - spawn RleBlockDecoder(in_r, out_s); + let (tok, output) = recv(tok, output_r); + assert_eq(output, Output { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { + last: true, + last_block: true, + id: u32:5, + data: BlockData:0xABAB_ABAB_ABAB_ABAB, + length: BlockPacketLength:64 + } + }); - (terminator, in_s, out_r) - } + let tok = send(tok, req_s, Req { id: u32:1, symbol: u8:0xAB, length: Length:0, last_block: true }); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { status: RleBlockDecoderStatus::OKAY }); - init { } + let (tok, output) = recv(tok, output_r); + assert_eq(output, Output { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { + last: true, + last_block: true, + id: u32:1, + data: BlockData:0xABAB_ABAB_ABAB_ABAB, + length: BlockPacketLength:0 + } + }); + + let tok = send(tok, req_s, Req { id: u32:10, symbol: u8:0xAB, length: Length:0, last_block: false }); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { status: RleBlockDecoderStatus::OKAY }); + + let (tok, output) = recv(tok, output_r); + assert_eq(output, Output { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { + last: true, + last_block: false, + id: u32:10, + data: BlockData:0xABAB_ABAB_ABAB_ABAB, + length: BlockPacketLength:0 + } + }); - 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"); +const INST_DATA_W = u32:64; - spawn RleBlockDecoder(in_r, out_s); +proc RleBlockDecoderInst { + type Req = RleBlockDecoderReq; + type Resp = RleBlockDecoderResp; + type Output = ExtendedBlockDataPacket; - (terminator, in_s, out_r) + type Data = uN[INST_DATA_W]; + type Length = uN[common::BLOCK_SIZE_WIDTH]; + + config( + req_r: chan in, + resp_s: chan out, + output_s: chan out, + ) { + spawn RleBlockDecoder(req_r, resp_s, output_s); } - 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); - } + init { } + + next (state: ()) {} } From 315d7e84ccd353284c9c62de8612393798f2efdd Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 8 Oct 2024 17:53:10 +0200 Subject: [PATCH 14/85] modules/zstd: Add ZstdDecoder Co-authored-by: Pawel Czarnecki Signed-off-by: Robert Winkler Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 312 ++---- xls/modules/zstd/zstd_dec.x | 1737 ++++++++++++++++++++++------- xls/modules/zstd/zstd_dec_test.cc | 297 ----- 3 files changed, 1406 insertions(+), 940 deletions(-) delete mode 100644 xls/modules/zstd/zstd_dec_test.cc diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 255418a161..c086942d22 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -21,7 +21,6 @@ load( "//xls/build_rules:xls_build_defs.bzl", "xls_benchmark_ir", "xls_benchmark_verilog", - "xls_dslx_ir", "xls_dslx_library", "xls_dslx_test", "xls_dslx_verilog", @@ -696,229 +695,6 @@ place_and_route( target_die_utilization_percentage = "10", ) -xls_dslx_library( - name = "repacketizer_dslx", - srcs = [ - "repacketizer.x", - ], - deps = [ - ":common_dslx", - ], -) - -xls_dslx_test( - name = "repacketizer_dslx_test", - dslx_test_args = {"compare": "jit"}, - library = ":repacketizer_dslx", - tags = ["manual"], -) - -repacketizer_codegen_args = common_codegen_args | { - "module_name": "Repacketizer", - "clock_period_ps": "0", - "pipeline_stages": "2", -} - -xls_dslx_verilog( - name = "repacketizer_verilog", - codegen_args = repacketizer_codegen_args, - dslx_top = "Repacketizer", - library = ":repacketizer_dslx", - tags = ["manual"], - verilog_file = "repacketizer.v", -) - -xls_benchmark_ir( - name = "repacketizer_opt_ir_benchmark", - src = ":repacketizer_verilog.opt.ir", - benchmark_ir_args = repacketizer_codegen_args | { - "pipeline_stages": "10", - }, - 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", - tags = ["manual"], - top_module = "Repacketizer", - deps = [ - ":repacketizer_verilog_lib", - ], -) - -benchmark_synth( - name = "repacketizer_benchmark_synth", - synth_target = ":repacketizer_synth_asap7", - tags = ["manual"], -) - -place_and_route( - name = "repacketizer_place_and_route", - clock_period = CLOCK_PERIOD_PS, - core_padding_microns = 2, - min_pin_distance = "0.5", - placement_density = "0.30", - stop_after_step = "global_routing", - synthesized_rtl = ":repacketizer_synth_asap7", - tags = ["manual"], - target_die_utilization_percentage = "10", -) - -xls_dslx_library( - name = "zstd_dec_dslx", - srcs = [ - "zstd_dec.x", - ], - deps = [ - ":block_header_dslx", - ":buffer_dslx", - ":common_dslx", - ":frame_header_dslx", - ":frame_header_test_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", - "fifo_module": "", - "materialize_internal_fifos": "true", - }, - dslx_top = "ZstdDecoder", - library = ":zstd_dec_dslx", - tags = ["manual"], - verilog_file = "zstd_dec.v", -) - -xls_dslx_ir( - name = "zstd_dec_test_ir", - dslx_top = "ZstdDecoderTest", - ir_file = "zstd_dec_test.ir", - library = ":zstd_dec_dslx", - tags = ["manual"], -) - -cc_test( - name = "zstd_dec_cc_test", - size = "large", - srcs = [ - "zstd_dec_test.cc", - ], - data = [ - ":zstd_dec_test.ir", - ], - 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/common/status:ret_check", - "//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_google_absl//absl/container:flat_hash_map", - "@com_google_absl//absl/log", - "@com_google_absl//absl/status:statusor", - "@com_google_absl//absl/types:span", - "@googletest//:gtest", - "@zstd", - ], -) - -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", - "fifo_module": "", - "materialize_internal_fifos": "true", - }, - 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", - tags = ["manual"], - top_module = "ZstdDecoder", - deps = [ - ":zstd_dec_verilog_lib", - ], -) - -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", - tags = ["manual"], - target_die_utilization_percentage = "10", -) - xls_dslx_library( name = "axi_csr_accessor_dslx", srcs = [ @@ -1071,3 +847,91 @@ place_and_route( tags = ["manual"], target_die_utilization_percentage = "10", ) + +xls_dslx_library( + name = "zstd_dec_dslx", + srcs = [ + "zstd_dec.x", + ], + deps = [ + ":axi_csr_accessor_dslx", + ":block_header_dec_dslx", + ":block_header_dslx", + ":common_dslx", + ":csr_config_dslx", + ":dec_mux_dslx", + ":frame_header_dec_dslx", + ":raw_block_dec_dslx", + ":repacketizer_dslx", + ":rle_block_dec_dslx", + ":sequence_executor_dslx", + "//xls/examples:ram_dslx", + "//xls/modules/zstd/memory:mem_reader_dslx", + ], +) + +xls_dslx_test( + name = "zstd_dec_dslx_test", + library = ":zstd_dec_dslx", + tags = ["manual"], +) + +zstd_dec_internal_codegen_args = common_codegen_args | { + "module_name": "ZstdDecoderInternal", + "pipeline_stages": "2", +} + +xls_dslx_verilog( + name = "zstd_dec_internal_verilog", + codegen_args = zstd_dec_internal_codegen_args, + dslx_top = "ZstdDecoderInternalInst", + library = ":zstd_dec_dslx", + tags = ["manual"], + verilog_file = "zstd_dec_internal.v", +) + +xls_benchmark_ir( + name = "zstd_dec_internal_opt_ir_benchmark", + src = ":zstd_dec_internal_verilog.opt.ir", + benchmark_ir_args = { + "top": "__zstd_dec__ZstdDecoderInternalInst__ZstdDecoderInternal_0__16_64_8_4_16_next", + "pipeline_stages": "10", + }, + tags = ["manual"], +) + +verilog_library( + name = "zstd_dec_internal_verilog_lib", + srcs = [ + ":zstd_dec_internal.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "zstd_dec_internal_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "ZstdDecoderInternal", + deps = [ + ":zstd_dec_internal_verilog_lib", + ], +) + +benchmark_synth( + name = "zstd_dec_internal_benchmark_synth", + synth_target = ":zstd_dec_internal_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "zstd_dec_internal_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.35", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":zstd_dec_internal_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) diff --git a/xls/modules/zstd/zstd_dec.x b/xls/modules/zstd/zstd_dec.x index d3dea6ae59..6e28bd8167 100644 --- a/xls/modules/zstd/zstd_dec.x +++ b/xls/modules/zstd/zstd_dec.x @@ -17,479 +17,1378 @@ // https://datatracker.ietf.org/doc/html/rfc8878 import std; +import xls.examples.ram; +import xls.modules.zstd.axi_csr_accessor; +import xls.modules.zstd.common; +import xls.modules.zstd.memory.axi; +import xls.modules.zstd.csr_config; +import xls.modules.zstd.memory.mem_reader; +import xls.modules.zstd.frame_header_dec; import xls.modules.zstd.block_header; -import xls.modules.zstd.block_dec; +import xls.modules.zstd.block_header_dec; +import xls.modules.zstd.raw_block_dec; +import xls.modules.zstd.rle_block_dec; +import xls.modules.zstd.dec_mux; 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, +type BlockType = common::BlockType; +type BlockHeader = block_header::BlockHeader; + +enum ZstdDecoderInternalFsm: u4 { + IDLE = 0, + READ_CONFIG = 1, + DECODE_FRAME_HEADER = 2, + DECODE_BLOCK_HEADER = 3, + DECODE_RAW_BLOCK = 4, + DECODE_RLE_BLOCK = 5, + DECODE_COMPRESSED_BLOCK = 6, + DECODE_CHECKSUM = 7, + FINISH = 8, + ERROR = 13, + INVALID = 15, } -struct ZstdDecoderState { - status: ZstdDecoderStatus, - buffer: Buffer, - frame_header: frame_header::FrameHeader, - block_size_bytes: BlockSize, - last: bool, - bytes_sent: BlockSize, +enum ZstdDecoderStatus: u3 { + OKAY = 0, + FINISHED = 1, + FRAME_HEADER_CORRUPTED = 2, + FRAME_HEADER_UNSUPPORTED_WINDOW_SIZE = 3, + BLOCK_HEADER_CORRUPTED = 4, } -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, - }; - trace_fmt!("zstd_dec: decode_magic_number: new_state: {:#x}", new_state); - - (false, zero!(), new_state) +enum Csr: u3 { + STATUS = 0, // Keeps the code describing the current state of the ZSTD Decoder + START = 1, // Writing 1 when decoder is in IDLE state starts the decoding process + RESET = 2, // Writing 1 will reset the decoder to the IDLE state + INPUT_BUFFER = 3, // Keeps the base address for the input buffer that is used for storing the frame to decode + OUTPUT_BUFFER = 4, // Keeps the base address for the output buffer, ZSTD Decoder will write the decoded frame into memory starting from this address. + WHO_AM_I = 5, // Contains the identification number of the ZSTD Decoder } -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 - }, - }; - trace_fmt!("zstd_dec: decode_frame_header: new_state: {:#x}", new_state); - - (false, zero!(), new_state) +fn csr(c: Csr) -> uN[LOG2_REGS_N] { + c as uN[LOG2_REGS_N] } -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, - }; - trace_fmt!("zstd_dec: decode_block_header: new_state: {:#x}", new_state); - - (false, zero!(), new_state) +struct ZstdDecoderInternalState { + fsm: ZstdDecoderInternalFsm, + + // Reading CSRs + conf_cnt: uN[LOG2_REGS_N], + conf_send: bool, + input_buffer: uN[AXI_ADDR_W], + input_buffer_valid: bool, + output_buffer: uN[AXI_ADDR_W], + output_buffer_valid: bool, + + // Writing to CSRs + csr_wr_req: csr_config::CsrWrReq, + csr_wr_req_valid: bool, + + // BH address + bh_addr: uN[AXI_ADDR_W], + + // Block + block_addr: uN[AXI_ADDR_W], + block_length: uN[AXI_ADDR_W], + block_last: bool, + block_id: u32, + block_rle_symbol: u8, + + // Req + req_sent: bool, } -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::min(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, +proc ZstdDecoderInternal< + AXI_DATA_W: u32, AXI_ADDR_W: u32, REGS_N: u32, + LOG2_REGS_N:u32 = {std::clog2(REGS_N)}, + HB_RAM_N:u32 = {u32:8}, +> { + + type State = ZstdDecoderInternalState; + type Fsm = ZstdDecoderInternalFsm; + type Reg = uN[LOG2_REGS_N]; + type Data = uN[AXI_DATA_W]; + type Addr = uN[AXI_ADDR_W]; + + type CsrRdReq = csr_config::CsrRdReq; + type CsrRdResp = csr_config::CsrRdResp; + type CsrWrReq = csr_config::CsrWrReq; + type CsrWrResp = csr_config::CsrWrResp; + type CsrChange = csr_config::CsrChange; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + + type FrameHeaderDecoderStatus = frame_header_dec::FrameHeaderDecoderStatus; + type FrameHeaderDecoderReq = frame_header_dec::FrameHeaderDecoderReq; + type FrameHeaderDecoderResp = frame_header_dec::FrameHeaderDecoderResp; + + type BlockHeaderDecoderStatus = block_header_dec::BlockHeaderDecoderStatus; + type BlockHeaderDecoderReq = block_header_dec::BlockHeaderDecoderReq; + type BlockHeaderDecoderResp = block_header_dec::BlockHeaderDecoderResp; + + type RawBlockDecoderStatus = raw_block_dec::RawBlockDecoderStatus; + type RawBlockDecoderReq = raw_block_dec::RawBlockDecoderReq; + type RawBlockDecoderResp = raw_block_dec::RawBlockDecoderResp; + + type RleBlockDecoderStatus = rle_block_dec::RleBlockDecoderStatus; + type RleBlockDecoderReq = rle_block_dec::RleBlockDecoderReq; + type RleBlockDecoderResp = rle_block_dec::RleBlockDecoderResp; + + // CsrConfig + csr_rd_req_s: chan out; + csr_rd_resp_r: chan in; + csr_wr_req_s: chan out; + csr_wr_resp_r: chan in; + csr_change_r: chan in; + + // MemReader + FameHeaderDecoder + fh_req_s: chan out; + fh_resp_r: chan in; + + // MemReader + BlockHeaderDecoder + bh_req_s: chan out; + bh_resp_r: chan in; + + // MemReader + RawBlockDecoder + raw_req_s: chan out; + raw_resp_r: chan in; + + // MemReader + RleBlockDecoder + rle_req_s: chan out; + rle_resp_r: chan in; + + notify_s: chan<()> out; + + init { + zero!() + } + + config( + csr_rd_req_s: chan out, + csr_rd_resp_r: chan in, + csr_wr_req_s: chan out, + csr_wr_resp_r: chan in, + csr_change_r: chan in, + + // MemReader + FameHeaderDecoder + fh_req_s: chan out, + fh_resp_r: chan in, + + // MemReader + BlockHeaderDecoder + bh_req_s: chan out, + bh_resp_r: chan in, + + // MemReader + RawBlockDecoder + raw_req_s: chan out, + raw_resp_r: chan in, + + // MemReader + RleBlockDecoder + rle_req_s: chan out, + rle_resp_r: chan in, + + notify_s: chan<()> out, + ) { + ( + csr_rd_req_s, csr_rd_resp_r, csr_wr_req_s, csr_wr_resp_r, csr_change_r, + fh_req_s, fh_resp_r, + bh_req_s, bh_resp_r, + raw_req_s, raw_resp_r, + rle_req_s, rle_resp_r, + notify_s, + ) + } + + next (state: State) { + let tok0 = join(); + + const CSR_REQS = CsrRdReq[2]:[ + CsrRdReq {csr: csr(Csr::INPUT_BUFFER)}, + CsrRdReq {csr: csr(Csr::OUTPUT_BUFFER)} + ]; + + const CSR_REQS_MAX = checked_cast(array_size(CSR_REQS) - u32:1); + + let (tok1_0, csr_change, csr_change_valid) = recv_non_blocking(tok0, csr_change_r, zero!()); + let is_start = (csr_change_valid && (csr_change.csr == csr(Csr::START))); + + let do_send_csr_req = (state.fsm == Fsm::READ_CONFIG) && (!state.conf_send); + let csr_req = CSR_REQS[state.conf_cnt]; + let tok1_1 = send_if(tok0, csr_rd_req_s, do_send_csr_req, csr_req); + if do_send_csr_req { + trace_fmt!("[READ_CONFIG] Sending read request {:#x}", csr_req); + } else {}; + + let do_recv_csr_resp = (state.fsm == Fsm::READ_CONFIG); + let (tok1_2, csr_data, csr_data_valid) = recv_if_non_blocking(tok0, csr_rd_resp_r, do_recv_csr_resp, zero!()); + if csr_data_valid { + trace_fmt!("[READ_CONFIG] Received CSR data: {:#x}", csr_data); + } else {}; + + let do_send_fh_req = (state.fsm == Fsm::DECODE_FRAME_HEADER) && !state.req_sent; + let fh_req = FrameHeaderDecoderReq { addr: state.input_buffer }; + let tok1_3 = send_if(tok0, fh_req_s, do_send_fh_req, fh_req); + if do_send_fh_req { + trace_fmt!("[DECODE_FRAME_HEADER] Sending FH request {:#x}", fh_req); + } else {}; + + let do_recv_fh_resp = (state.fsm == Fsm::DECODE_FRAME_HEADER); + let (tok1_4, fh_resp, fh_resp_valid) = recv_if_non_blocking(tok0, fh_resp_r, do_recv_fh_resp, zero!()); + if fh_resp_valid { + trace_fmt!("[DECODE_FRAME_HEADER]: Received FH {:#x}", fh_resp); + } else {}; + + let do_send_notify = (state.fsm == Fsm::ERROR || state.fsm == Fsm::FINISH); + let tok = send_if(tok0, notify_s, do_send_notify, ()); + if do_send_notify { + trace_fmt!("[[NOTIFY]]"); + } else {}; + + let tok1_5 = send_if(tok0, csr_wr_req_s, state.csr_wr_req_valid, state.csr_wr_req); + let (tok, _, _) = recv_non_blocking(tok0, csr_wr_resp_r, zero!()); + if state.csr_wr_req_valid { + trace_fmt!("[[CSR_WR_REQ]] Request: {:#x}", state.csr_wr_req); + } else {}; + + let do_send_bh_req = (state.fsm == Fsm::DECODE_BLOCK_HEADER) && !state.req_sent; + let bh_req = BlockHeaderDecoderReq { addr: state.bh_addr }; + let tok1_6 = send_if(tok0, bh_req_s, do_send_bh_req, bh_req); + if do_send_bh_req { + trace_fmt!("[DECODE_BLOCK_HEADER]: Sending BH request: {:#x}", bh_req); + } else {}; + + let do_recv_bh_resp = (state.fsm == Fsm::DECODE_BLOCK_HEADER); + let (tok1_4, bh_resp, bh_resp_valid) = recv_if_non_blocking(tok0, bh_resp_r, do_recv_bh_resp, zero!()); + if bh_resp_valid { + trace_fmt!("[DECODE_BLOCK_HEADER]: Received BH {:#x}", bh_resp); + } else {}; + + let do_send_raw_req = (state.fsm == Fsm::DECODE_RAW_BLOCK) && !state.req_sent; + let raw_req = RawBlockDecoderReq { + id: state.block_id, + last_block: state.block_last, + addr: state.block_addr, + length: state.block_length, + }; + let tok1_6 = send_if(tok0, raw_req_s, do_send_raw_req, raw_req); + if do_send_raw_req { + trace_fmt!("[DECODE_RAW_BLOCK]: Sending RAW request: {:#x}", raw_req); + } else {}; + + let do_recv_raw_resp = (state.fsm == Fsm::DECODE_RAW_BLOCK); + let (tok1_7, raw_resp, raw_resp_valid) = recv_if_non_blocking(tok0, raw_resp_r, do_recv_raw_resp, zero!()); + if raw_resp_valid { + trace_fmt!("[DECODE_RAW_BLOCK]: Received RAW {:#x}", raw_resp); + } else {}; + + let do_send_rle_req = (state.fsm == Fsm::DECODE_RLE_BLOCK) && !state.req_sent; + let rle_req = RleBlockDecoderReq { + id: state.block_id, + symbol: state.block_rle_symbol, + length: checked_cast(state.block_length), + last_block: state.block_last, + }; + let tok1_7 = send_if(tok0, rle_req_s, do_send_rle_req, rle_req); + if do_send_rle_req { + trace_fmt!("[DECODE_RLE_BLOCK]: Sending RLE request: {:#x}", rle_req); + } else {}; + + let do_recv_rle_resp = (state.fsm == Fsm::DECODE_RLE_BLOCK); + let (tok1_8, rle_resp, rle_resp_valid) = recv_if_non_blocking(tok0, rle_resp_r, do_recv_rle_resp, zero!()); + if raw_resp_valid { + trace_fmt!("[DECODE_RLE_BLOCK]: Received RAW {:#x}", raw_resp); + } else {}; + + let new_state = match (state.fsm) { + Fsm::IDLE => { + trace_fmt!("[IDLE]"); + if is_start { + State { fsm: Fsm::READ_CONFIG, conf_cnt: CSR_REQS_MAX, ..zero!() } + } else { zero!() } + }, + + Fsm::READ_CONFIG => { + trace_fmt!("[READ_CONFIG]"); + let is_input_buffer_csr = (csr_data.csr == csr(Csr::INPUT_BUFFER)); + let input_buffer = if csr_data_valid && is_input_buffer_csr { checked_cast(csr_data.value) } else { state.input_buffer }; + let input_buffer_valid = if csr_data_valid && is_input_buffer_csr { true } else { state.input_buffer_valid }; + + let is_output_buffer_csr = (csr_data.csr == csr(Csr::OUTPUT_BUFFER)); + let output_buffer = if (csr_data_valid && is_output_buffer_csr) { checked_cast(csr_data.value) } else { state.output_buffer }; + let output_buffer_valid = if (csr_data_valid && is_output_buffer_csr) { true } else { state.output_buffer_valid }; + + let all_collected = input_buffer_valid & output_buffer_valid; + let fsm = if all_collected { Fsm::DECODE_FRAME_HEADER } else { Fsm::READ_CONFIG }; + + let conf_send = (state.conf_cnt == Reg:0); + let conf_cnt = if conf_send { Reg:0 } else {state.conf_cnt - Reg:1}; + + State { + fsm, conf_cnt, conf_send, input_buffer, input_buffer_valid, output_buffer, output_buffer_valid, + ..zero!() + } + }, + + Fsm::DECODE_FRAME_HEADER => { + trace_fmt!("[DECODE_FRAME_HEADER]"); + let error = (fh_resp.status != FrameHeaderDecoderStatus::OKAY); + + let csr_wr_req_valid = (fh_resp_valid && error); + let csr_wr_req = CsrWrReq { + csr: csr(Csr::STATUS), + value: checked_cast(fh_resp.status), + }; + + let fsm = match (fh_resp_valid, error) { + ( true, false) => Fsm::DECODE_BLOCK_HEADER, + ( true, true) => Fsm::ERROR, + ( _, _) => Fsm::DECODE_FRAME_HEADER, + }; + + let bh_addr = state.input_buffer + fh_resp.length as Addr; + let req_sent = if !fh_resp_valid && !error { true } else { false }; + State {fsm, csr_wr_req, csr_wr_req_valid, bh_addr, req_sent, ..state } + }, + + Fsm::DECODE_BLOCK_HEADER => { + trace_fmt!("[DECODE_BLOCK_HEADER]"); + let error = (bh_resp.status != BlockHeaderDecoderStatus::OKAY); + + let csr_wr_req_valid = (bh_resp_valid && error); + let csr_wr_req = CsrWrReq { + csr: csr(Csr::STATUS), + value: checked_cast(bh_resp.status), }; - 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 - } + + let fsm = match (bh_resp_valid, error, bh_resp.header.btype) { + ( true, false, BlockType::RAW ) => Fsm::DECODE_RAW_BLOCK, + ( true, false, BlockType::RLE ) => Fsm::DECODE_RLE_BLOCK, + ( true, false, BlockType::COMPRESSED) => Fsm::ERROR, + ( true, true, _) => Fsm::ERROR, + ( _, _, _) => Fsm::DECODE_BLOCK_HEADER, + }; + + let (block_addr, block_length, block_last, block_rle_symbol, bh_addr) = if bh_resp_valid { + let block_addr = state.bh_addr + Addr:3; + let block_length = checked_cast(bh_resp.header.size); + let block_rle_symbol = bh_resp.rle_symbol; + let bh_addr = if bh_resp.header.btype == BlockType::RLE { + block_addr + Addr:1 } else { - ZstdDecoderStatus::DECODE_BLOCK_HEADER - } + block_addr + block_length + }; + + trace_fmt!("bh_addr: {:#x}", bh_addr); + + (block_addr, block_length, bh_resp.header.last, block_rle_symbol, bh_addr) } else { - ZstdDecoderStatus::FEED_BLOCK_DECODER + (state.block_addr, state.block_length, state.block_last, state.block_rle_symbol, state.bh_addr) }; - 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, + + let req_sent = if !bh_resp_valid && !error { true } else { false }; + State { + fsm, bh_addr, req_sent, + block_addr, block_length, block_last, block_rle_symbol, + csr_wr_req, csr_wr_req_valid, ..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) + + Fsm::DECODE_RAW_BLOCK => { + trace_fmt!("[DECODE_RAW_BLOCK]"); + + let error = (raw_resp.status != RawBlockDecoderStatus::OKAY); + + let csr_wr_req_valid = (raw_resp_valid && error); + let csr_wr_req = CsrWrReq { + csr: csr(Csr::STATUS), + value: checked_cast(raw_resp.status), + }; + + let fsm = match (raw_resp_valid, error, state.block_last) { + (true, false, false) => Fsm::DECODE_BLOCK_HEADER, + (true, false, true) => Fsm::DECODE_CHECKSUM, + (true, true, _) => Fsm::ERROR, + ( _, _, _) => Fsm::DECODE_RAW_BLOCK, + }; + + let req_sent = if !raw_resp_valid && !error { true } else { false }; + let block_id = if raw_resp_valid { state.block_id + u32:1} else {state.block_id }; + + let state = State {fsm, block_id, csr_wr_req, csr_wr_req_valid, req_sent, ..state}; + if fsm == Fsm::DECODE_BLOCK_HEADER { + trace_fmt!("Going to decode block header: {:#x}", state); + } else {}; + + state + }, + + Fsm::DECODE_RLE_BLOCK => { + trace_fmt!("[DECODE_RLE_BLOCK]"); + let error = (rle_resp.status != RleBlockDecoderStatus::OKAY); + + let csr_wr_req_valid = (rle_resp_valid && error); + let csr_wr_req = CsrWrReq { + csr: csr(Csr::STATUS), + value: checked_cast(rle_resp.status), + }; + + let fsm = match (rle_resp_valid, error, state.block_last) { + (true, false, false) => Fsm::DECODE_BLOCK_HEADER, + (true, false, true) => Fsm::DECODE_CHECKSUM, + (true, true, _) => Fsm::ERROR, + ( _, _, _) => Fsm::DECODE_RLE_BLOCK, + }; + + let req_sent = if !rle_resp_valid && !error { true } else { false }; + let block_id = if rle_resp_valid { state.block_id + u32:1} else {state.block_id }; + + let state = State {fsm, block_id, csr_wr_req, csr_wr_req_valid, req_sent, ..state}; + if fsm == Fsm::DECODE_BLOCK_HEADER { + trace_fmt!("Going to decode block header: {:#x}", state); + } else {}; + + state + }, + + Fsm::DECODE_CHECKSUM => { + trace_fmt!("[DECODE_CHECKSUM]"); + State {fsm: Fsm::FINISH, ..zero!() } + + }, + + Fsm::ERROR => { + trace_fmt!("[ERROR]"); + State { fsm: Fsm::IDLE, ..zero!() } + }, + + Fsm::FINISH => { + trace_fmt!("[FINISH]"); + let csr_wr_req_valid = true; + let csr_wr_req = CsrWrReq { + csr: csr(Csr::STATUS), + value: checked_cast(fh_resp.status), + }; + + State { fsm: Fsm::IDLE, csr_wr_req, csr_wr_req_valid, ..zero!() } + }, + + _ => zero!(), + }; + + new_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); +const TEST_AXI_DATA_W = u32:64; +const TEST_AXI_ADDR_W = u32:32; +const TEST_AXI_ID_W = u32:8; +const TEST_AXI_DEST_W = u32:8; +const TEST_REGS_N = u32:5; +const TEST_WINDOW_LOG_MAX = u32:30; + +const TEST_HB_ADDR_W = sequence_executor::ZSTD_RAM_ADDR_WIDTH; +const TEST_HB_DATA_W = sequence_executor::RAM_DATA_WIDTH; +const TEST_HB_NUM_PARTITIONS = sequence_executor::RAM_NUM_PARTITIONS; +const TEST_HB_SIZE_KB = sequence_executor::ZSTD_HISTORY_BUFFER_SIZE_KB; + +const TEST_LOG2_REGS_N = std::clog2(TEST_REGS_N); +const TEST_AXI_DATA_W_DIV8 = TEST_AXI_DATA_W / u32:8; +const TEST_HB_RAM_N = u32:8; + +const TEST_HB_SIZE = sequence_executor::ram_size(TEST_HB_SIZE_KB); +const TEST_HB_RAM_WORD_PARTITION_SIZE = sequence_executor::RAM_WORD_PARTITION_SIZE; +const TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = sequence_executor::TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR; +const TEST_HB_RAM_INITIALIZED = sequence_executor::TEST_RAM_INITIALIZED; +const TEST_HB_RAM_ASSERT_VALID_READ:bool = {false}; + + +#[test_proc] +proc ZstdDecoderInternalTest { + + type BlockType = common::BlockType; + type BlockSize = common::BlockSize; + type BlockHeader = block_header::BlockHeader; + type BlockHeaderDecoderStatus = block_header_dec::BlockHeaderDecoderStatus; + + type FrameHeaderDecoderReq = frame_header_dec::FrameHeaderDecoderReq; + type FrameHeaderDecoderResp = frame_header_dec::FrameHeaderDecoderResp; + type FrameHeaderDecoderStatus = frame_header_dec::FrameHeaderDecoderStatus; + type FrameContentSize = frame_header_dec::FrameContentSize; + type FrameHeader = frame_header_dec::FrameHeader; + type WindowSize = frame_header_dec::WindowSize; + type DictionaryId = frame_header_dec::DictionaryId; + + type CsrRdReq = csr_config::CsrRdReq; + type CsrRdResp = csr_config::CsrRdResp; + type CsrWrReq = csr_config::CsrWrReq; + type CsrWrResp = csr_config::CsrWrResp; + type CsrChange = csr_config::CsrChange; + + type FrameHeaderDecoderReq = frame_header_dec::FrameHeaderDecoderReq; + type FrameHeaderDecoderResp = frame_header_dec::FrameHeaderDecoderResp; + + type BlockHeaderDecoderReq = block_header_dec::BlockHeaderDecoderReq; + type BlockHeaderDecoderResp = block_header_dec::BlockHeaderDecoderResp; + + type RawBlockDecoderReq = raw_block_dec::RawBlockDecoderReq; + type RawBlockDecoderResp = raw_block_dec::RawBlockDecoderResp; + type RawBlockDecoderStatus = raw_block_dec::RawBlockDecoderStatus; + + type RleBlockDecoderReq = rle_block_dec::RleBlockDecoderReq; + type RleBlockDecoderResp = rle_block_dec::RleBlockDecoderResp; + type RleBlockDecoderStatus = rle_block_dec::RleBlockDecoderStatus; + + terminator: chan out; + + csr_rd_req_r: chan in; + csr_rd_resp_s: chan out; + csr_wr_req_r: chan in; + csr_wr_resp_s: chan out; + csr_change_s: chan out; + + fh_req_r: chan in; + fh_resp_s: chan out; + + bh_req_r: chan in; + bh_resp_s: chan out; + + raw_req_r: chan in; + raw_resp_s: chan out; + + rle_req_r: chan in; + rle_resp_s: chan out; + + notify_r: chan<()> in; + + init {} + + config(terminator: chan out) { + let (csr_rd_req_s, csr_rd_req_r) = chan("csr_rd_req"); + let (csr_rd_resp_s, csr_rd_resp_r) = chan("csr_rd_resp"); + let (csr_wr_req_s, csr_wr_req_r) = chan("csr_wr_req"); + let (csr_wr_resp_s, csr_wr_resp_r) = chan("csr_wr_resp"); + let (csr_change_s, csr_change_r) = chan("csr_change"); + + let (fh_req_s, fh_req_r) = chan("fh_req"); + let (fh_resp_s, fh_resp_r) = chan("fh_resp"); + + let (bh_req_s, bh_req_r) = chan("bh_req"); + let (bh_resp_s, bh_resp_r) = chan("bh_resp"); + + let (raw_req_s, raw_req_r) = chan("raw_req"); + let (raw_resp_s, raw_resp_r) = chan("raw_resp"); + + let (rle_req_s, rle_req_r) = chan("rle_req"); + let (rle_resp_s, rle_resp_r) = chan("rle_resp"); + + let (notify_s, notify_r) = chan<()>("notify"); + + spawn ZstdDecoderInternal( + csr_rd_req_s, csr_rd_resp_r, csr_wr_req_s, csr_wr_resp_r, csr_change_r, + fh_req_s, fh_resp_r, + bh_req_s, bh_resp_r, + raw_req_s, raw_resp_r, + rle_req_s, rle_resp_r, + notify_s, + ); + + ( + terminator, + csr_rd_req_r, csr_rd_resp_s, csr_wr_req_r, csr_wr_resp_s, csr_change_s, + fh_req_r, fh_resp_s, + bh_req_r, bh_resp_s, + raw_req_r, raw_resp_s, + rle_req_r, rle_resp_s, + notify_r, + ) + } + + next (state: ()) { + type Addr = uN[TEST_AXI_ADDR_W]; + type Length = uN[TEST_AXI_ADDR_W]; + + let tok = join(); + + // Error in frame header + + let tok = send(tok, csr_change_s, CsrChange { csr: csr(Csr::START)}); + let (tok, csr_data) = recv(tok, csr_rd_req_r); + assert_eq(csr_data, CsrRdReq {csr: csr(Csr::OUTPUT_BUFFER)}); + let (tok, csr_data) = recv(tok, csr_rd_req_r); + assert_eq(csr_data, CsrRdReq {csr: csr(Csr::INPUT_BUFFER)}); - 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); + send(tok, csr_rd_resp_s, CsrRdResp { + csr: csr(Csr::INPUT_BUFFER), + value: uN[TEST_AXI_DATA_W]:0x1000 + }); + send(tok, csr_rd_resp_s, CsrRdResp { + csr: csr(Csr::OUTPUT_BUFFER), + value: uN[TEST_AXI_DATA_W]:0x2000 + }); + let (tok, fh_req) = recv(tok, fh_req_r); + assert_eq(fh_req, FrameHeaderDecoderReq { addr: Addr:0x1000 }); - (false, zero!(), new_state) + let tok = send(tok, fh_resp_s, FrameHeaderDecoderResp { + status: FrameHeaderDecoderStatus::CORRUPTED, + header: FrameHeader { + window_size: WindowSize:100, + frame_content_size: FrameContentSize:200, + dictionary_id: DictionaryId:123, + content_checksum_flag: u1:1, + }, + length: u5:3, + }); + + + let (tok, ()) = recv(tok, notify_r); + + // Correct case + let tok = send(tok, csr_change_s, CsrChange { csr: csr(Csr::START)}); + let (tok, csr_data) = recv(tok, csr_rd_req_r); + assert_eq(csr_data, CsrRdReq {csr: csr(Csr::OUTPUT_BUFFER)}); + let (tok, csr_data) = recv(tok, csr_rd_req_r); + assert_eq(csr_data, CsrRdReq {csr: csr(Csr::INPUT_BUFFER)}); + + send(tok, csr_rd_resp_s, CsrRdResp { + csr: csr(Csr::INPUT_BUFFER), + value: uN[TEST_AXI_DATA_W]:0x1000 + }); + send(tok, csr_rd_resp_s, CsrRdResp { + csr: csr(Csr::OUTPUT_BUFFER), + value: uN[TEST_AXI_DATA_W]:0x2000 + }); + let (tok, fh_req) = recv(tok, fh_req_r); + assert_eq(fh_req, FrameHeaderDecoderReq { addr: Addr:0x1000 }); + + let tok = send(tok, fh_resp_s, FrameHeaderDecoderResp { + status: FrameHeaderDecoderStatus::OKAY, + header: FrameHeader { + window_size: WindowSize:100, + frame_content_size: FrameContentSize:200, + dictionary_id: DictionaryId:123, + content_checksum_flag: u1:1, + }, + length: u5:3, + }); + + let (tok, bh_req) = recv(tok, bh_req_r); + assert_eq(bh_req, BlockHeaderDecoderReq { + addr: Addr:0x1003, + }); + + let tok = send(tok, bh_resp_s, BlockHeaderDecoderResp { + status: BlockHeaderDecoderStatus::OKAY, + header: BlockHeader { + last: false, + btype: BlockType::RAW, + size: BlockSize:0x1000, + }, + rle_symbol: u8:0, + }); + + let (tok, raw_req) = recv(tok, raw_req_r); + assert_eq(raw_req, RawBlockDecoderReq { + last_block: false, + id: u32:0, + addr: Addr:0x1006, + length: Length:0x1000 + }); + + let tok = send(tok, raw_resp_s, RawBlockDecoderResp { + status: RawBlockDecoderStatus::OKAY, + }); + + let (tok, bh_req) = recv(tok, bh_req_r); + assert_eq(bh_req, BlockHeaderDecoderReq { + addr: Addr:0x2006 + }); + let tok = send(tok, bh_resp_s, BlockHeaderDecoderResp { + status: BlockHeaderDecoderStatus::OKAY, + header: BlockHeader { + last: false, + btype: BlockType::RLE, + size: BlockSize:0x1000, + }, + rle_symbol: u8:123, + }); + + let (tok, rle_req) = recv(tok, rle_req_r); + assert_eq(rle_req, RleBlockDecoderReq { + id: u32:1, + symbol: u8:123, + last_block: false, + length: checked_cast(Length:0x1000), + }); + let tok = send(tok, rle_resp_s, RleBlockDecoderResp { + status: RleBlockDecoderStatus::OKAY, + }); + + let (tok, bh_req) = recv(tok, bh_req_r); + assert_eq(bh_req, BlockHeaderDecoderReq { + addr: Addr:0x200A, + }); + + let tok = send(tok, bh_resp_s, BlockHeaderDecoderResp { + status: BlockHeaderDecoderStatus::OKAY, + header: BlockHeader { + last: true, + btype: BlockType::RAW, + size: BlockSize:0x1000, + }, + rle_symbol: u8:0, + }); + + let (tok, raw_req) = recv(tok, raw_req_r); + assert_eq(raw_req, RawBlockDecoderReq { + last_block: true, + id: u32:2, + addr: Addr:0x200D, + length: Length:0x1000 + }); + + let tok = send(tok, raw_resp_s, RawBlockDecoderResp { + status: RawBlockDecoderStatus::OKAY, + }); + + let (tok, ()) = recv(tok, notify_r); + + send(tok, terminator, true); + } } -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, + +proc ZstdDecoder< + // AXI parameters + AXI_DATA_W: u32, AXI_ADDR_W: u32, AXI_ID_W: u32, AXI_DEST_W: u32, + // decoder parameters + REGS_N: u32, WINDOW_LOG_MAX: u32, + HB_ADDR_W: u32, HB_DATA_W: u32, HB_NUM_PARTITIONS: u32, HB_SIZE_KB: u32, + // calculated parameters + AXI_DATA_W_DIV8: u32 = {AXI_DATA_W / u32:8}, + LOG2_REGS_N: u32 = {std::clog2(REGS_N)}, + HB_RAM_N: u32 = {u32:8}, +> { + type CsrAxiAr = axi::AxiAr; + type CsrAxiR = axi::AxiR; + type CsrAxiAw = axi::AxiAw; + type CsrAxiW = axi::AxiW; + type CsrAxiB = axi::AxiB; + + type CsrRdReq = csr_config::CsrRdReq; + type CsrRdResp = csr_config::CsrRdResp; + type CsrWrReq = csr_config::CsrWrReq; + type CsrWrResp = csr_config::CsrWrResp; + type CsrChange = csr_config::CsrChange; + + type MemAxiAr = axi::AxiAr; + type MemAxiR = axi::AxiR; + type MemAxiAw = axi::AxiAw; + type MemAxiW = axi::AxiW; + type MemAxiB = axi::AxiB; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + + type FrameHeaderDecoderReq = frame_header_dec::FrameHeaderDecoderReq; + type FrameHeaderDecoderResp = frame_header_dec::FrameHeaderDecoderResp; + + type BlockHeaderDecoderReq = block_header_dec::BlockHeaderDecoderReq; + type BlockHeaderDecoderResp = block_header_dec::BlockHeaderDecoderResp; + + type RawBlockDecoderReq = raw_block_dec::RawBlockDecoderReq; + type RawBlockDecoderResp = raw_block_dec::RawBlockDecoderResp; + type ExtendedBlockDataPacket = common::ExtendedBlockDataPacket; + + type RleBlockDecoderReq = rle_block_dec::RleBlockDecoderReq; + type RleBlockDecoderResp = rle_block_dec::RleBlockDecoderResp; + + type SequenceExecutorPacket = common::SequenceExecutorPacket; + type ZstdDecodedPacket = common::ZstdDecodedPacket; + + type RamRdReq = ram::ReadReq; + type RamRdResp = ram::ReadResp; + type RamWrReq = ram::WriteReq; + type RamWrResp = ram::WriteResp; + + // Complex Block Decoder + cmp_output_s: chan out; + + init {} + + config( + // AXI Ctrl (subordinate) + csr_axi_aw_r: chan in, + csr_axi_w_r: chan in, + csr_axi_b_s: chan out, + csr_axi_ar_r: chan in, + csr_axi_r_s: chan out, + + // AXI Frame Header Decoder (manager) + fh_axi_ar_s: chan out, + fh_axi_r_r: chan in, + + //// AXI Block Header Decoder (manager) + bh_axi_ar_s: chan out, + bh_axi_r_r: chan in, + + //// AXI RAW Block Decoder (manager) + raw_axi_ar_s: chan out, + raw_axi_r_r: chan in, + + // History Buffer + 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, + + // Decoder output 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, + notify_s: chan<()> out, ) { - 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"); + const CHANNEL_DEPTH = u32:1; + + // CSRs + + let (ext_csr_rd_req_s, ext_csr_rd_req_r) = chan("csr_rd_req"); + let (ext_csr_rd_resp_s, ext_csr_rd_resp_r) = chan("csr_rd_resp"); + let (ext_csr_wr_req_s, ext_csr_wr_req_r) = chan("csr_wr_req"); + let (ext_csr_wr_resp_s, ext_csr_wr_resp_r) = chan("csr_wr_resp"); + + let (csr_rd_req_s, csr_rd_req_r) = chan("csr_rd_req"); + let (csr_rd_resp_s, csr_rd_resp_r) = chan("csr_rd_resp"); + let (csr_wr_req_s, csr_wr_req_r) = chan("csr_wr_req"); + let (csr_wr_resp_s, csr_wr_resp_r) = chan("csr_wr_resp"); + + let (csr_change_s, csr_change_r) = chan("csr_change"); + + spawn axi_csr_accessor::AxiCsrAccessor( + csr_axi_aw_r, csr_axi_w_r, csr_axi_b_s, // csr write from AXI + csr_axi_ar_r, csr_axi_r_s, // csr read from AXI + ext_csr_rd_req_s, ext_csr_rd_resp_r, // csr read to CsrConfig + ext_csr_wr_req_s, ext_csr_wr_resp_r, // csr write to CsrConfig + ); + + spawn csr_config::CsrConfig( + ext_csr_rd_req_r, ext_csr_rd_resp_s, // csr read from AxiCsrAccessor + ext_csr_wr_req_r, ext_csr_wr_resp_s, // csr write from AxiCsrAccessor + csr_rd_req_r, csr_rd_resp_s, // csr read from design + csr_wr_req_r, csr_wr_resp_s, // csr write from design + csr_change_s, // notification about csr change + ); + + // Frame Header + + let (fh_mem_rd_req_s, fh_mem_rd_req_r) = chan("fh_mem_rd_req"); + let (fh_mem_rd_resp_s, fh_mem_rd_resp_r) = chan("fh_mem_rd_resp"); + + spawn mem_reader::MemReader( + fh_mem_rd_req_r, fh_mem_rd_resp_s, + fh_axi_ar_s, fh_axi_r_r, + ); + + let (fh_req_s, fh_req_r) = chan("fh_req"); + let (fh_resp_s, fh_resp_r) = chan("fh_resp"); + + spawn frame_header_dec::FrameHeaderDecoder( + fh_mem_rd_req_s, fh_mem_rd_resp_r, + fh_req_r, fh_resp_s, + ); + + // Block Header + + let (bh_mem_rd_req_s, bh_mem_rd_req_r) = chan("bh_mem_rd_req"); + let (bh_mem_rd_resp_s, bh_mem_rd_resp_r) = chan("bh_mem_rd_resp"); + + spawn mem_reader::MemReader( + bh_mem_rd_req_r, bh_mem_rd_resp_s, + bh_axi_ar_s, bh_axi_r_r, + ); + + let (bh_req_s, bh_req_r) = chan("bh_req"); + let (bh_resp_s, bh_resp_r) = chan("bh_resp"); + + spawn block_header_dec::BlockHeaderDecoder( + bh_req_r, bh_resp_s, + bh_mem_rd_req_s, bh_mem_rd_resp_r, + ); + + // Raw Block Decoder + + let (raw_mem_rd_req_s, raw_mem_rd_req_r) = chan("raw_mem_rd_req"); + let (raw_mem_rd_resp_s, raw_mem_rd_resp_r) = chan("raw_mem_rd_resp"); + + spawn mem_reader::MemReader( + raw_mem_rd_req_r, raw_mem_rd_resp_s, + raw_axi_ar_s, raw_axi_r_r, + ); + + let (raw_req_s, raw_req_r) = chan("raw_req"); + let (raw_resp_s, raw_resp_r) = chan("raw_resp"); + let (raw_output_s, raw_output_r) = chan("raw_output"); + + spawn raw_block_dec::RawBlockDecoder( + raw_req_r, raw_resp_s, raw_output_s, + raw_mem_rd_req_s, raw_mem_rd_resp_r, + ); + + // RLE Block Decoder + + let (rle_req_s, rle_req_r) = chan("rle_req"); + let (rle_resp_s, rle_resp_r) = chan("rle_resp"); + let (rle_output_s, rle_output_r) = chan("rle_output"); + + spawn rle_block_dec::RleBlockDecoder( + rle_req_r, rle_resp_s, rle_output_s + ); + + // Collecting Packets + + let (cmp_output_s, cmp_output_r) = chan("cmp_output"); + let (seq_exec_input_s, seq_exec_input_r) = chan("demux_output"); + + spawn dec_mux::DecoderMux( + raw_output_r, rle_output_r, cmp_output_r, + seq_exec_input_s, + ); - spawn block_dec::BlockDecoder(block_dec_in_r, seq_exec_in_s); + // Sequence Execution - 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, + let (seq_exec_looped_s, seq_exec_looped_r) = chan("seq_exec_looped"); + let (seq_exec_output_s, seq_exec_output_r) = chan("seq_exec_output"); + + spawn sequence_executor::SequenceExecutor( + seq_exec_input_r, seq_exec_output_s, + seq_exec_looped_r, seq_exec_looped_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_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, + 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) + // Repacketizer + + spawn repacketizer::Repacketizer(seq_exec_output_r, output_s); + + // Zstd Decoder Control + + spawn ZstdDecoderInternal ( + csr_rd_req_s, csr_rd_resp_r, csr_wr_req_s, csr_wr_resp_r, csr_change_r, + fh_req_s, fh_resp_r, + bh_req_s, bh_resp_r, + raw_req_s, raw_resp_r, + rle_req_s, rle_resp_r, + notify_s, + ); + + (cmp_output_s,) } - 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) - }; + next (state: ()) { + send_if(join(), cmp_output_s, false, zero!()); + } +} - 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); +//#[test_proc] +//proc ZstdDecoderTest { +// type CsrAxiAr = axi::AxiAr; +// type CsrAxiR = axi::AxiR; +// type CsrAxiAw = axi::AxiAw; +// type CsrAxiW = axi::AxiW; +// type CsrAxiB = axi::AxiB; +// +// type CsrRdReq = csr_config::CsrRdReq; +// type CsrRdResp = csr_config::CsrRdResp; +// type CsrWrReq = csr_config::CsrWrReq; +// type CsrWrResp = csr_config::CsrWrResp; +// type CsrChange = csr_config::CsrChange; +// +// type MemAxiAr = axi::AxiAr; +// type MemAxiR = axi::AxiR; +// type MemAxiAw = axi::AxiAw; +// type MemAxiW = axi::AxiW; +// type MemAxiB = axi::AxiB; +// +// type RamRdReq = ram::ReadReq; +// type RamRdResp = ram::ReadResp; +// type RamWrReq = ram::WriteReq; +// type RamWrResp = ram::WriteResp; +// +// type ZstdDecodedPacket = common::ZstdDecodedPacket; +// terminator: chan out; +// +// init {} +// +// config(terminator: chan out) { +// +// let (csr_axi_aw_s, csr_axi_aw_r) = chan("csr_axi_aw"); +// let (csr_axi_w_s, csr_axi_w_r) = chan("csr_axi_w"); +// let (csr_axi_b_s, csr_axi_b_r) = chan("csr_axi_b"); +// let (csr_axi_ar_s, csr_axi_ar_r) = chan("csr_axi_ar"); +// let (csr_axi_r_s, csr_axi_r_r) = chan("csr_axi_r"); +// +// let (fh_axi_ar_s, fh_axi_ar_r) = chan("fh_axi_ar_s"); +// let (fh_axi_r_s, fh_axi_r_r) = chan("fh_axi_r_r"); +// +// let (bh_axi_ar_s, bh_axi_ar_r) = chan("bh_axi_ar"); +// let (bh_axi_r_s, bh_axi_r_r) = chan("bh_axi_r"); +// +// let (raw_axi_ar_s, raw_axi_ar_r) = chan("raw_axi_ar"); +// let (raw_axi_r_s, raw_axi_r_r) = chan("raw_axi_r"); +// +// let (rle_axi_ar_s, rle_axi_ar_r) = chan("rle_axi_ar"); +// let (rle_axi_r_s, rle_axi_r_r) = chan("rle_axi_r"); +// +// let (ram_rd_req_0_s, ram_rd_req_0_r) = chan("ram_rd_req_0"); +// let (ram_rd_req_1_s, ram_rd_req_1_r) = chan("ram_rd_req_1"); +// let (ram_rd_req_2_s, ram_rd_req_2_r) = chan("ram_rd_req_2"); +// let (ram_rd_req_3_s, ram_rd_req_3_r) = chan("ram_rd_req_3"); +// let (ram_rd_req_4_s, ram_rd_req_4_r) = chan("ram_rd_req_4"); +// let (ram_rd_req_5_s, ram_rd_req_5_r) = chan("ram_rd_req_5"); +// let (ram_rd_req_6_s, ram_rd_req_6_r) = chan("ram_rd_req_6"); +// let (ram_rd_req_7_s, ram_rd_req_7_r) = chan("ram_rd_req_7"); +// +// let (ram_rd_resp_0_s, ram_rd_resp_0_r) = chan("ram_rd_resp_0"); +// let (ram_rd_resp_1_s, ram_rd_resp_1_r) = chan("ram_rd_resp_1"); +// let (ram_rd_resp_2_s, ram_rd_resp_2_r) = chan("ram_rd_resp_2"); +// let (ram_rd_resp_3_s, ram_rd_resp_3_r) = chan("ram_rd_resp_3"); +// let (ram_rd_resp_4_s, ram_rd_resp_4_r) = chan("ram_rd_resp_4"); +// let (ram_rd_resp_5_s, ram_rd_resp_5_r) = chan("ram_rd_resp_5"); +// let (ram_rd_resp_6_s, ram_rd_resp_6_r) = chan("ram_rd_resp_6"); +// let (ram_rd_resp_7_s, ram_rd_resp_7_r) = chan("ram_rd_resp_7"); +// +// let (ram_wr_req_0_s, ram_wr_req_0_r) = chan("ram_wr_req_0"); +// let (ram_wr_req_1_s, ram_wr_req_1_r) = chan("ram_wr_req_1"); +// let (ram_wr_req_2_s, ram_wr_req_2_r) = chan("ram_wr_req_2"); +// let (ram_wr_req_3_s, ram_wr_req_3_r) = chan("ram_wr_req_3"); +// let (ram_wr_req_4_s, ram_wr_req_4_r) = chan("ram_wr_req_4"); +// let (ram_wr_req_5_s, ram_wr_req_5_r) = chan("ram_wr_req_5"); +// let (ram_wr_req_6_s, ram_wr_req_6_r) = chan("ram_wr_req_6"); +// let (ram_wr_req_7_s, ram_wr_req_7_r) = chan("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"); +// +// let (output_s, output_r) = chan("output"); +// let (notify_s, notify_r) = chan<()>("notify"); +// +// spawn ZstdDecoder< +// TEST_AXI_DATA_W, TEST_AXI_ADDR_W, TEST_AXI_ID_W, TEST_AXI_DEST_W, +// TEST_REGS_N, TEST_WINDOW_LOG_MAX, +// TEST_HB_ADDR_W, TEST_HB_DATA_W, TEST_HB_NUM_PARTITIONS, TEST_HB_SIZE_KB, +// >( +// csr_axi_aw_r, csr_axi_w_r, csr_axi_b_s, csr_axi_ar_r, csr_axi_r_s, +// fh_axi_ar_s, fh_axi_r_r, +// bh_axi_ar_s, bh_axi_r_r, +// raw_axi_ar_s, raw_axi_r_r, +// rle_axi_ar_s, rle_axi_r_r, +// 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, +// output_s, notify_s, +// ); +// +// spawn ram::RamModel< +// TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, +// TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, +// TEST_HB_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< +// TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, +// TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, +// TEST_HB_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< +// TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, +// TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_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_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_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< +// TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, +// TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_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< +// TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, +// TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_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< +// TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, +// TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_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< +// TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, +// TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_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); +// +// ( +// terminator, +// csr_axi_aw_s, csr_axi_w_s, csr_axi_b_r, csr_axi_ar_s, csr_axi_s_r, +// fh_axi_ar_r, fh_axi_s_s, +// bh_axi_ar_r, bh_axi_s_s, +// raw_axi_ar_r, raw_axi_s_s, +// rle_axi_ar_r, rle_axi_s_s, +// ram_sd_seq_0_r, ram_sd_seq_1_r, ram_sd_seq_2_r, ram_sd_seq_3_r, +// ram_sd_seq_4_r, ram_sd_seq_5_r, ram_sd_seq_6_r, ram_sd_seq_7_r, +// ram_sd_sesp_0_s, ram_sd_sesp_1_s, ram_sd_sesp_2_s, ram_sd_sesp_3_s, +// ram_sd_sesp_4_s, ram_sd_sesp_5_s, ram_sd_sesp_6_s, ram_sd_sesp_7_s, +// ram_wr_seq_0_r, ram_wr_seq_1_r, ram_wr_seq_2_r, ram_wr_seq_3_r, +// ram_wr_seq_4_r, ram_wr_seq_5_r, ram_wr_seq_6_r, ram_wr_seq_7_r, +// ram_wr_sesp_0_s, ram_wr_sesp_1_s, ram_wr_sesp_2_s, ram_wr_sesp_3_s, +// ram_wr_sesp_4_s, ram_wr_sesp_5_s, ram_wr_sesp_6_s, ram_wr_sesp_7_s, +// output_r, notify_r, +// ) +// } +// +// next (state: ()) { +// +// trace_fmt!("Test start"); +// +// send(join(), terminator, true); +// } +//} + + +const INST_AXI_DATA_W = u32:64; +const INST_AXI_ADDR_W = u32:16; +const INST_AXI_ID_W = u32:4; +const INST_AXI_DEST_W = u32:4; +const INST_REGS_N = u32:16; +const INST_WINDOW_LOG_MAX = u32:30; +const INST_HB_ADDR_W = sequence_executor::ZSTD_RAM_ADDR_WIDTH; +const INST_HB_DATA_W = sequence_executor::RAM_DATA_WIDTH; +const INST_HB_NUM_PARTITIONS = sequence_executor::RAM_NUM_PARTITIONS; +const INST_HB_SIZE_KB = sequence_executor::ZSTD_HISTORY_BUFFER_SIZE_KB; + +const INST_LOG2_REGS_N = std::clog2(INST_REGS_N); +const INST_AXI_DATA_W_DIV8 = INST_AXI_DATA_W / u32:8; +const INST_HB_RAM_N = u32:8; + +proc ZstdDecoderInternalInst { + type State = ZstdDecoderInternalState; + type Fsm = ZstdDecoderInternalFsm; + + type CsrRdReq = csr_config::CsrRdReq; + type CsrRdResp = csr_config::CsrRdResp; + type CsrWrReq = csr_config::CsrWrReq; + type CsrWrResp = csr_config::CsrWrResp; + type CsrChange = csr_config::CsrChange; + + type FrameHeaderDecoderReq = frame_header_dec::FrameHeaderDecoderReq; + type FrameHeaderDecoderResp = frame_header_dec::FrameHeaderDecoderResp; + + type BlockHeaderDecoderReq = block_header_dec::BlockHeaderDecoderReq; + type BlockHeaderDecoderResp = block_header_dec::BlockHeaderDecoderResp; + + type RawBlockDecoderReq = raw_block_dec::RawBlockDecoderReq; + type RawBlockDecoderResp = raw_block_dec::RawBlockDecoderResp; + + type RleBlockDecoderReq = rle_block_dec::RleBlockDecoderReq; + type RleBlockDecoderResp = rle_block_dec::RleBlockDecoderResp; + + init { } + + config( + csr_rd_req_s: chan out, + csr_rd_resp_r: chan in, + csr_wr_req_s: chan out, + csr_wr_resp_r: chan in, + csr_change_r: chan in, + + // MemReader + FameHeaderDecoder + fh_req_s: chan out, + fh_resp_r: chan in, + + // MemReader + BlockHeaderDecoder + bh_req_s: chan out, + bh_resp_r: chan in, + + // MemReader + RawBlockDecoder + raw_req_s: chan out, + raw_resp_r: chan in, + + // MemReader + RleBlockDecoder + rle_req_s: chan out, + rle_resp_r: chan in, + + // IRQ + notify_s: chan<()> out, + ) { + spawn ZstdDecoderInternal< + INST_AXI_DATA_W, INST_AXI_ADDR_W, INST_REGS_N, + > ( + csr_rd_req_s, csr_rd_resp_r, csr_wr_req_s, csr_wr_resp_r, csr_change_r, + fh_req_s, fh_resp_r, + bh_req_s, bh_resp_r, + raw_req_s, raw_resp_r, + rle_req_s, rle_resp_r, + notify_s, + ); - state } + + next(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}; +proc ZstdDecoderInst { + type CsrAxiAr = axi::AxiAr; + type CsrAxiR = axi::AxiR; + type CsrAxiAw = axi::AxiAw; + type CsrAxiW = axi::AxiW; + type CsrAxiB = axi::AxiB; + + type MemAxiAr = axi::AxiAr; + type MemAxiR = axi::AxiR; + type MemAxiAw = axi::AxiAw; + type MemAxiW = axi::AxiW; + type MemAxiB = axi::AxiB; + + type RamRdReq = ram::ReadReq; + type RamRdResp = ram::ReadResp; + type RamWrReq = ram::WriteReq; + type RamWrResp = ram::WriteResp; + + type ZstdDecodedPacket = common::ZstdDecodedPacket; + + init { } -pub proc ZstdDecoderTest { - input_r: chan in; - output_s: chan out; + config( + // AXI Ctrl (subordinate) + csr_axi_aw_r: chan in, + csr_axi_w_r: chan in, + csr_axi_b_s: chan out, + csr_axi_ar_r: chan in, + csr_axi_r_s: chan out, - init {()} + // AXI Frame Header Decoder (manager) + fh_axi_ar_s: chan out, + fh_axi_r_r: chan in, - config ( - input_r: chan in, + // AXI Block Header Decoder (manager) + bh_axi_ar_s: chan out, + bh_axi_r_r: chan in, + + // AXI RAW Block Decoder (manager) + raw_axi_ar_s: chan out, + raw_axi_r_r: chan in, + + // History Buffer + 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, + + // Decoder output output_s: chan out, + notify_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, + spawn ZstdDecoder< + INST_AXI_DATA_W, INST_AXI_ADDR_W, INST_AXI_ID_W, INST_AXI_DEST_W, + INST_REGS_N, INST_WINDOW_LOG_MAX, + INST_HB_ADDR_W, INST_HB_DATA_W, INST_HB_NUM_PARTITIONS, INST_HB_SIZE_KB, + >( + csr_axi_aw_r, csr_axi_w_r, csr_axi_b_s, csr_axi_ar_r, csr_axi_r_s, + fh_axi_ar_s, fh_axi_r_r, + bh_axi_ar_s, bh_axi_r_r, + raw_axi_ar_s, raw_axi_r_r, + 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_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, + output_s, notify_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_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 deleted file mode 100644 index 0a6679a11d..0000000000 --- a/xls/modules/zstd/zstd_dec_test.cc +++ /dev/null @@ -1,297 +0,0 @@ -// 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. -// - -#include -#include -#include -#include -#include -#include -#include // NOLINT -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "gtest/gtest.h" -#include "absl/container/flat_hash_map.h" -#include "absl/log/log.h" -#include "absl/status/statusor.h" -#include "absl/types/span.h" -#include "xls/common/file/filesystem.h" -#include "xls/common/file/get_runfile_path.h" -#include "xls/common/status/matchers.h" -#include "xls/common/status/ret_check.h" -#include "xls/interpreter/channel_queue.h" -#include "xls/interpreter/serial_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 "external/zstd/lib/zstd.h" - -namespace xls { -namespace { - -class ZstdDecodedPacket { - public: - static absl::StatusOr MakeZstdDecodedPacket( - const Value& packet) { - // Expect tuple - XLS_RET_CHECK(packet.IsTuple()); - // Expect exactly 3 fields - XLS_RET_CHECK(packet.size() == 3); - for (int i = 0; i < 3; i++) { - // Expect fields to be Bits - XLS_RET_CHECK(packet.element(i).IsBits()); - // All fields must fit in 64 bits - XLS_RET_CHECK(packet.element(i).bits().FitsInUint64()); - } - - std::vector data = packet.element(0).bits().ToBytes(); - absl::StatusOr len = packet.element(1).bits().ToUint64(); - XLS_RET_CHECK(len.ok()); - 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; } - - std::string ToString() 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 - << static_cast(data[j]) << std::dec << ", "; - } - return s.str(); - } - - private: - ZstdDecodedPacket(std::vector data, uint64_t length, bool last) - : data(std::move(data)), length(length), last(last) {} - - std::vector data; - uint64_t length; - bool last; -}; - -class ZstdDecoderTest : public ::testing::Test { - public: - void SetUp() override { - XLS_ASSERT_OK_AND_ASSIGN(std::filesystem::path ir_path, - xls::GetXlsRunfilePath(this->kIrFile)); - 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->kRecvChannelName)); - XLS_ASSERT_OK_AND_ASSIGN( - this->send_queue, queue_manager.GetQueueByName(this->kSendChannelName)); - } - - 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) { - LOG(INFO) << "[TRACE] " << tm.message << "\n"; - } - } - } - - const std::string_view kProcName = "__zstd_dec__ZstdDecoderTest_0_next"; - const std::string_view kRecvChannelName = "zstd_dec__output_s"; - const std::string_view kSendChannelName = "zstd_dec__input_r"; - - const std::string_view kIrFile = "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) { - LOG(INFO) << "0x" << std::hex << std::setw(3) << std::left << i - << std::dec << ": "; - for (int j = 0; j < sizeof(uint64_t) && (i + j) < vec.size(); j++) { - LOG(INFO) << std::setfill('0') << std::setw(2) << std::hex - << static_cast(vec[i + j]) << std::dec << " "; - } - LOG(INFO) << "\n"; - } - } - - 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(); - 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}; - - 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(), static_cast(output_buffer.dst), - (static_cast(output_buffer.dst) + output_buffer.pos)); - - EXPECT_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::cerr << "lib_decomp_size: " << lib_decomp_size << "\n"; - - 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) { - // Pad packet w/ zeros to match the frame size expected by the design. - std::array packet_data = {}; - auto frame_packet_begin = frame.begin() + i; - auto frame_packet_end = frame_packet_begin + 8 < frame.end() - ? frame_packet_begin + 8 - : frame.end(); - std::copy(frame_packet_begin, frame_packet_end, packet_data.begin()); - auto span = absl::MakeSpan(packet_data.data(), 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()); - XLS_EXPECT_OK(packet); - 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_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 1915cf40a5fb8b74b2f4680523aa44bc708f73c8 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Thu, 10 Oct 2024 12:39:17 +0200 Subject: [PATCH 15/85] modules/zstd: Update documentation Internal-tag: [#67096] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/README.md | 401 ++++++++++-------- xls/modules/zstd/img/ZSTD_decoder.png | Bin 28375 -> 315264 bytes xls/modules/zstd/img/ZSTD_decoder_wrapper.png | Bin 0 -> 126231 bytes 3 files changed, 216 insertions(+), 185 deletions(-) create mode 100644 xls/modules/zstd/img/ZSTD_decoder_wrapper.png diff --git a/xls/modules/zstd/README.md b/xls/modules/zstd/README.md index 85c9919e4f..e606a02918 100644 --- a/xls/modules/zstd/README.md +++ b/xls/modules/zstd/README.md @@ -2,105 +2,196 @@ 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. +An overview of the decoder architecture is presented in 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. +* Memory Readers +* Memory Writer[^2], +* Control and Status Registers, +* Frame Header Decoder, +* Block Header Decoder, +* 3 types of processing units: RAW-, RLE-, and Compressed Block Decoders[^1], +* Command Aggregator, +* Repacketizer. + +The Decoder interacts with the environment through a set of ports: +* Memory Interface (AXI) +* CSR Interface (AXI) +* Notify line +* Stream Output + +The software controls the core through registers accessible through the `CSR Interface`. +The CSRs are used to configure the decoder and to start the decoding process. + +ZSTD frames to decode are placed in a memory that should be connected to +decoder's `Memory Interface`. + +Once the decoding process is started, the decoder: + +1. Reads the configuration from the CSRs, +2. Decodes the Frame Header, +3. Decodes the Block Header, +4. Decodes the Block Data with the correct processing unit picked based on the Block Type from the Block Header, +4. Aggregates the processing unit results in the correct order into a stream and routes it to the history buffer, +5. Assembles the data block outputs based on the history buffer contents and updates the history, +6. Prepares the final output of the decoder, +7. (Optional) Calculates checksum and compares it against the checksum read from the frame.[^3] ![](img/ZSTD_decoder.png) +## Registers description + +The ZSTD Decoder operation is based on the values stored in a set of CSRs accessible to the user through the AXI bus. +The registers are defined below: + +| Name | Address | Description | +| ---- | ------- | ----------- | +| Status | 0x0 | Keeps the code describing the current state of the ZSTD Decoder | +| Start | 0x8 | Writing `1` when the decoder is in the `IDLE` state starts the decoding process | +| Reset | 0x10 | Writing `1` will reset the decoder to the `IDLE` state | +| Input Buffer | 0x18 | Keeps the base address for the input buffer that is used for storing the frame to decode | +| Output Buffer | 0x20 | Keeps the base address for the output buffer, ZSTD Decoder will write the decoded frame into memory starting from this address. | + +### Status codes + +The following is a list of all available status codes that can be written in the `Status` register. + +| Name | Value | Description | +| ---- | ------- | ----------- | +| IDLE | 0 | Previous decoding finished successfully. The decoder waits for the configuration and writes to the `Start` register. | +| RUNNING | 1 | Decoding process is started | +| READ_CONFIG_OK |2 | Successfully read configuration from the CSRs | +| FRAME_HEADER_OK | 3 | Successfully decoded frame header | +| FRAME_HEADER_CORRUPTED | 4 | Frame header data is not valid | +| FRAME_HEADER_UNSUPPORTED_WINDOW_SIZE | 5 | The `WindowSize` parameter read from the frame header is not supported in the decoder | +| BLOCK_HEADER_OK | 6 | Successfully read the header of the Zstd data block | +| BLOCK_HEADER_CORRUPTED | 7 | Block type is `Reserved` | +| BLOCK_HEADER_MEMORY_ACCESS_ERROR | 8 | Failure in communication with the memory | +| RAW_BLOCK_OK | 9 | Successfully decoded raw data block | +| RAW_BLOCK_ERROR | 10 | Failure in communication with the memory | +| RLE_BLOCK_OK | 11 | Successfully decoded RLE data block | + +### Reset handling + +The expected behavior of the `Reset` CSR cannot be achieved solely in the DSLX code. +As of [cb2829ab](https://github.com/google/xls/commit/cb2829ab809c58f21d957a47e400456a8c8f8db1), the XLS toolchain does not support resetting the proc network on the DSLX level. +As a workaround for this issue, the `ZstdDec` proc defines a `reset` output channel that sends a pulse when there is a write to the `Reset` CSR. +The Verilog code that integrates the decoder in a target system must connect this output back to the standard `rst` input of the decoder. +If any external reset signal exists and is intended to be used with the decoder, it should be OR-ed with the `reset` channel output before connecting to the decoder's `rst` input. +Please refer to the diagram of the Verilog wrapper in the [Testing Methodology](#testing-methodology) chapter for example reset connection. + +## Controlling the decoder from the software + +The configuration done by the software must be carried out when the decoder is in the `IDLE` state. +It is the only time when the decoder will be able to take the configuration values from the CSRs and use those in the decoding process. + +The software should first read the `Status` register to confirm that the decoder is in the `IDLE` state. +In case it is not in the `IDLE` state, it is possible to reset the decoder by writing `1` to the `Reset` register. +Please note that this will stop ongoing decoding and all progress will be lost. + +Then, the software has to reserve the memory for the input buffer and write the frame to decode there. +The address of the buffer should be written into `Input Buffer` register so that the decoder will know where to look for the frame to decode. + +The next step is to reserve the memory space for the decoded frame where the Decoder will write the decompressed data. +The address to that buffer should be written to the `Output Buffer` register. + +Finally, it is possible to start the decoding process by writing `1` to the `Start` register. +This orders the Decoder to read the configuration CSRs and start reading and decoding data stored in the input buffer. +The Decoder transitions to the `RUNNING` state and then to other states that describe the status of the last operation finished in the decoder (see #status-codes for other possible status codes) which will be visible in the `Status` register. + +When the decoding process is finished the Decoder transitions back to the `IDLE` state and signals this on the `Notify` IRQ line. +The decoded data is stored under the address configured previously in the `Output Buffer` register. + +In case an error occurs during the decoding process it is also signaled on the `Notify` IRQ line and the error code is written to the `Status` CSR. + ## 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: +This state machine is responsible for controlling the operation of the whole decoder. +It uses the configuration data from the CSRs, connects all underlying modules and sends processing requests to those based on the state of the machine. +The states defined for the processing of the ZSTD frame are as follows: ```mermaid stateDiagram - direction LR + [*] --> IDLE + + IDLE --> READ_CONFIG: Start - [*] --> DECODE_MAGIC_NUMBER + READ_CONFIG --> DECODE_FRAME_HEADER - 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_BLOCK_HEADER + DECODE_FRAME_HEADER --> ERROR - 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_RAW_BLOCK + DECODE_BLOCK_HEADER --> DECODE_RLE_BLOCK + DECODE_BLOCK_HEADER --> DECODE_COMPRESED_BLOCK + DECODE_BLOCK_HEADER --> ERROR - 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_block_last <> + DECODE_RAW_BLOCK --> ERROR + DECODE_RAW_BLOCK --> if_block_last - state if_decode_checksum <> - state if_block_done <> + DECODE_RLE_BLOCK --> ERROR + DECODE_RLE_BLOCK --> if_block_last - 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_COMPRESSED_BLOCK --> ERROR + DECODE_COMPRESSED_BLOCK --> if_block_last - DECODE_CHECKSUM --> DECODE_MAGIC_NUMBER: Frame decoded + if_block_last --> DECODE_BLOCK_HEADER: Not last block in the frame + if_block_last --> DECODE_CHECKSUM: Last block in the frame - ERROR --> [*] + DECODE_CHECKSUM --> FINISH + + FINISH --> IDLE + ERROR --> IDLE ``` -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. +After going through the initial stage of reading the configuration from the CSRs, the decoder sends the processing requests to the underlying parts of the decoder. +The processing requests contain the addresses in the memory where particular parts of the encoded ZSTD frames reside. +The decoder, based on responses from consecutive internal modules, calculates offsets from the base address that was written to `Input Buffer` CSR and forms the requests for the next internal modules, e.g.: for `BlockHeaderDecoder` or any of the processing units (`RawBlockDecoder`, `RleBlockDecoder`, `CompressedBlockDecoder`). + +Each of the internal modules waits for the processing request. +Once received, the module fetches the data from the memory starting from the address received in the processing request. +`MemReader` procs are used by those modules to communicate with the external memory through the AXI interface. +Internal modules decode the acquired parts of the frame and return responses with the results back to the top level proc. + +The processing units also output the decoded blocks of data through a stream-based interface to `SequenceExecutor` and further to `Repacketizer` procs. +Those procs perform the last steps of the decoding before the final output is sent out back to the memory under the address stored in the `Output Buffer` CSR or through the stream-based output channel. +Once the decoding process is completed, the decoder sends the `Notify` signal and transitions back to the `IDLE` state. + +### Internal modules + +#### FrameHeaderDecoder +This proc receives requests with the address of the beginning of the ZSTD frame. +It then reads the frame data from the memory and starts parsing the frame header. +If the magic number is not detected or the frame header is invalid, the proc will send a response with an error code. +Otherwise, it will put the frame header into internal DSLX representation, calculate the length of the header and send those as a response with `OKAY` status. + +#### BlockHeaderDecoder +ZSTD block header size is always 3 bytes. +BlockHeaderDecoder always reads 4 bytes of data. +It extracts the information on block type, size and whether the block is the last one in the ZSTD frame and puts that data in the response. +The additional byte is also placed in the response as an optimization for the RleBlockDecoder. + +#### RawBlockDecoder +This proc passes the data read from the memory 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`. +#### RleBlockDecoder +This proc receives a tuple (s, N), where s is an 8-bit symbol and N is an accompanying `symbol_count`. +It does not have to read the 8-bit symbol from the memory because `BlockHeaderDecoder` did that before and passed the symbol in the processing request to the `RleBlockDecoder`. 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 +#### CompressedBlockDecoder[^1] This part of the design is responsible for decoding the compressed data blocks. -It ingests the bytes stream, internally translates and interprets incoming data. +It ingests the bytes stream, and 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. +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. +#### Commands aggregator (DecMux) +This stage takes the output from either RAW, RLE or CompressedBlockDecoder and sends it to the History buffer and command execution stage. +This stage orders streams based on the ID value assigned by the top level proc. 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. @@ -110,7 +201,7 @@ It continues to read that stream until the `last` signal is set, then it switche 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 +#### History buffer and command execution (SequenceExecutor) This stage receives data which is tagged either `literals` or `copy`. This stage will show the following behavior, depending on the tag: * `literals` @@ -121,13 +212,26 @@ This stage will show the following behavior, depending on the tag: * 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 +#### 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 the 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 that 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. + +### Compressed block decoder architecture[^1] 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. +This sequence is then processed by the history buffer to generate the expected data output. +An overview of the architecture is provided in the diagram below. +The architecture is split into 2 paths: the literals path and the 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. +Literals path uses Huffman trees to decode some types of compressed blocks: Compressed and Treeless blocks. ![](img/ZSTD_compressed_block_decoder.png) @@ -144,11 +248,11 @@ When `literals length` is greater than 0, it will send a request to the literals 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 +#### Literals path architecture ![](img/ZSTD_compressed_block_literals_decoder.png) -#### Literals decoder dispatcher +##### 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). @@ -156,59 +260,59 @@ It also controls the 4 stream operation mode [4-stream mode in RFC](https://www. 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 +##### RAW Literals This stage simply passes the incoming bytes as literals to the literals buffer. -#### RLE Literals +##### RLE Literals This stage works similarly to the [RLE stage](#rle-decoder) for RLE data blocks. -#### Huffman bitstream buffer +##### 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 +##### 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 +##### 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 +#### FSE Huffman decoder architecture ![](img/ZSTD_compressed_block_Huffman_decoder.png) -#### Huffman tree decoder dispatcher +##### 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 +##### 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 +##### 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 +##### 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 +##### 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 +#### Sequence path architecture ![](img/ZSTD_compressed_block_sequence_decoder.png) -#### Sequence Header parser and dispatcher +##### 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. @@ -216,37 +320,24 @@ 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 +##### 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 +##### 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 +##### 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: @@ -255,14 +346,22 @@ Testing of the `ZSTD decoder` is carried out on two levels: * 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. +Testing on the DSLX level allows the creation of small test cases that test for positive 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++. +Tests of the integrated ZSTD decoder are carried out on DSLX and Verilog levels. 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). +ZstdDecoder's main communication interfaces are the AXI buses. +Due to the way XLS handles the codegen of DSLX channels that model the AXI channels, the particular ports of the AXI channels are not represented correctly. +This enforces the introduction of a Verilog wrapper that maps the ports generated by XLS into proper AXI ports (see AXI peripherals [README](memory/README.md) for more information). +Additionally, the wrapper is used to mux multiple AXI interfaces from `Memory Readers` into a single outside-facing AXI interface (`Memory Interface`) that can be connected to the external memory. +The mux is implemented by a third-party [AXI Interconnect](https://github.com/alexforencich/verilog-axi). + +![](img/ZSTD_decoder_wrapper.png) + ### Failure points #### User-facing decoder errors @@ -274,19 +373,8 @@ The design will fail the tests under the following conditions: * 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 @@ -295,24 +383,20 @@ However, the majority of the errors require modification of the deeper parts of 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. +Upon entering the `ERROR` state, the decoder will write a specific error code to the `Status` CSR and send a `Notify` signal to the output. +The interacting software can then read the code from the register and properly handle the error. + 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 +The `ERROR` state can be encountered under the following conditions when running Top Level Proc Verilog tests but also in DSLX tests for the specific components: +* Corrupted data on the frame header 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 @@ -321,62 +405,9 @@ The `assert!()` or `fail!()` will occur in: * SequenceExecutor * Receive `SequenceExecutorPacket` with `msg_type==SEQUENCE` and `content` field with value: `0` -There are also several `impossible cases` covered by `assert!()` and `fail!()`: +There are also several `impossible cases` covered by `fail!()`. +Those are mostly enforced by the type checker for the `match` expressions to cover unreachable cases. +This is done for example in: * 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 of decoding ZSTD frame with RLE block types -* **[WIP]** Compressed block type is not supported -* Checksum is not being verified diff --git a/xls/modules/zstd/img/ZSTD_decoder.png b/xls/modules/zstd/img/ZSTD_decoder.png index f157751512745dc923beec4b242377fe3962eb89..494926d89f7108623b2a4d958d1980b9a34f75cc 100644 GIT binary patch literal 315264 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

)^@@|1jzus^eSWD7<{w!; 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

  • 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

    5#S z|G?f&49-w3G&}b4l^!i7vDPEiejn;zS?Or2=1?gdPb9~*e? z*HCf@G%J~>;qv{zt)2Ga?no?;Or!|K+-S~#bI~%YdAj$WP$4lPQbm-Zu`w+sbEFDh z;kXt;K_331;im4V*3w{;f13z;51aUrHM)F!@vu_H5tB@<9X)T7EU4>($L% zEVdkYcmIp+mCupx6Lo%p7X>QW{loY$4&ZG1uE_2#FHOor+~gwLVG~CuUoj#qfd)NdVwr(dP(aCdDNKeT^io6o_@{MHfLi@dJ%RCNCmWNia?GzavE`m za5JJOY-^9Zz%(4(x4*Vr1n2_UN?8I@gY{WQAXtg+B#X3_tpCaDP34ab22179V-}Mmmgh(A1n+zW2kg51 z_S*vLM>_o8mlJ}~%>mCzqh)L{SSzs}A~&`nc_(8rrH2M4a>T2fHD?1mGMd~Agh%Hh zGw0vqYVtlkf+HqL!CIA8O=6IkT@yp`nsFFBGH;w3`0+H!Jf6AOTRmt;r%fGHA@AS! zykd=glU@TZm_r)=fU36dh`m8Ro5e#>(*f-D)VF0N=qWyRl!>LBIdd_DZTcR-5-Cl{!a=u=AIna3BJLD)R;#v?;IU74K_|fXflit&9bN<2|G4Hpo`IGI5RQOaq z@*7~5!lywNW!LFByc@Hy!Hj0er$D};1IRY*f*a)xR5{`$MQ1-x{#OwEy()uIlK+{; zrstuyW?=8NXGFvu36d0n!|ly5m#%DIs&e5W(hE?qZGR~tCztaGhy8VNm(2 zxOw~jX4iDs(RRY;c*p6*-3o4;@2`SpX`&B*1;04=^S!rDno6{QYmxGsZ#_dA$03qW zYnWrkIX0Y+k9XOec4aO0GtV0cIZf>u9*fzPhF-k!X>v_NIH1a@{We1UGZ%n*+oy(b zi7eF^rG4Uy%)tlN{clauJ^}RN+1!faVg%5G(bOS}IpPb%g;>5*0rC#ecEbVc&%!^7 zsb7mjz!5Z}upt+4y>iTOp_q*0jxvWZ6?jnD%&(QauWpgs1&Z`zC|X#f3mWlAAJ_F1Vk#<}_6&Xxe}6HR9rY zo*$O7HB30bT+2Q;X{=Lz|Fy*BWhINQLSYf7>RVuF-HdVO4_r4h~hu;L;icg)3CI^`he_ur_RJovJTc0!(u)lZ&y^ zUy7?ER>+!!oKt^ZlY4*5TT>jHEm*i{+W6RT-}B-_#-hf97kHcF8BnaNkeI%uFL3w8 z2l?Uh%@Ur)-Hwstt%E@aTG48cE?2R8V(m@uC&EOIzs49<|4fuG*)4JLly^@#l;1wy zIyJtvofw{qb&l0=v3MzKc^P-fJBx5ntPigy?A%*S+D)Y1ej9Rc*?Op+6-_d+ymF(9l~(SuBk|ixG24|o zJdWS^+Y&42n`P1(f6#PfZiH&_2*iH40w{j50HLTnDvYt#(+3nj<&uIw_=;swz5pJu z*;+Owi1;D*>hz&J|EZ7nu}szTD|!O@6<+ubW^HvJ*!f7#_>G>&h5sR^lFYKxBpy&( zcpHS(msn?etMG0Ymr=DwU;2c7wn8FVt5ktO>}WDIg2$31=l;)LE0N=}qM1rs9!$kN z^~Dq0vHsyMF-tQSTzm7)oT+#X$x%utEAd1x)F|D9HmvXZZre67s|)|Keo+92ba#=RWn*BUO9PXi

    ?64~!?O2TK%w?4tgl5& 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 literal 28375 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 fe`JP`|1bW3`ds>vf( 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 SCL)x7c>GZNLHT`)@c#k)`e{o5 diff --git a/xls/modules/zstd/img/ZSTD_decoder_wrapper.png b/xls/modules/zstd/img/ZSTD_decoder_wrapper.png new file mode 100644 index 0000000000000000000000000000000000000000..fec37b51a4c73771c92a9ed7e9df707ede385aa7 GIT binary patch literal 126231 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

      o`n4 zH3K;(o6B25nJSU0aAG=Ft~p4(?csPJv%D*YbMrFI%YWDtdO|{Qs&_e;a4C@(-dcXf!km+0G8DpX}ai7@rdH9o^g zIWpeSmx73YSv3^yfB>7+lsQEx*kL-!E6hO&JTLuBTdxbGNz9MggF1x9W*LgVHydo2 zE7*!FRxp%5HXVbR3)FTOn$L;b+?{ZI{bo&tJ){Q@JgfL)|OD^w;2q)D-va5zM; z11`j5fAcE%q4z9=T(kn3bP-0~8W`#{rAhB#+8c5H;$_!6*YW->nM1=MLc91EtNvc> z{}siMD)%{DmW^!ch`TcFZf%W=#vloiO(sQ%mj7SWQz|MI#NYah9NhS}n(?TM zukG}AZf`GbVc<*QdV4~If8}2~0*NR>B@4QMGRF<2bj^9J63c<&B=@gVFDRoaZo-fC zAQ%!rQ^45nRXn+N;J8{qmUv!|0Vej_$N+W9s4PsG_Rj|A=OBEI z8DWs?1w{)VIoUy>km%=$ot`MQ1$hUyRo${}M~+_JeIOraIk+GEwM$4%w4KJEFEnr8 z00bO9QAKSNCYN*zX9#DVlcERne1g6?Uj6JJo#XOjS?*yP+<^9@yX5Zxko`F)S?_*dfJ3kfIRNp)Q3 zG^+pq;k$p?^|)8xH`l$1vVRt+46MP0Kl{FY_7%AM3AFMZbTk2QFmYkTG^X210-zm_ z3w5+T6$SDbi>-q%Kl6REZPr!Q_9iIp{fH6 z&M%bkqZ-&?S zU$=i1);u7Z1P{Os>*h!J+z?~z1sVrhBr3?U^1#*WF{-5fd( zfQHS02b!J&t|&Y@=Ob{Ny|QUmSa;=|irQ|0k0~*rh)hjS#hR8j9yzd$BLkSDmH`*} zqBm86HIxRZHMrx)64>@tSozPubk Date: Fri, 30 Aug 2024 13:52:13 +0200 Subject: [PATCH 16/85] modules/zstd: Add AxiRamReader implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maciej Torhan Signed-off-by: Krzysztof Obłonczek --- xls/modules/zstd/memory/BUILD | 88 ++++ xls/modules/zstd/memory/axi.x | 17 +- xls/modules/zstd/memory/axi_ram.x | 767 ++++++++++++++++++++++++++++++ 3 files changed, 869 insertions(+), 3 deletions(-) create mode 100644 xls/modules/zstd/memory/axi_ram.x diff --git a/xls/modules/zstd/memory/BUILD b/xls/modules/zstd/memory/BUILD index 47cd431e77..c8ee0a7fc6 100644 --- a/xls/modules/zstd/memory/BUILD +++ b/xls/modules/zstd/memory/BUILD @@ -301,6 +301,94 @@ place_and_route( target_die_utilization_percentage = "10", ) +xls_dslx_library( + name = "axi_ram_dslx", + srcs = ["axi_ram.x"], + deps = [ + ":axi_dslx", + "//xls/examples:ram_dslx", + "//xls/modules/zstd:math_dslx", + ], +) + +xls_dslx_test( + name = "axi_ram_dslx_test", + library = ":axi_ram_dslx", +) + +xls_dslx_verilog( + name = "axi_ram_verilog", + codegen_args = { + "module_name": "AxiRam", + "delay_model": "asap7", + "ram_configurations": "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 5, + ram_name = "ram", + rd_req = "axi_ram__rd_req_s", + rd_resp = "axi_ram__rd_resp_r", + wr_req = "axi_ram__wr_req_s", + wr_resp = "axi_ram__wr_resp_r", + ), + "pipeline_stages": "8", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "AxiRamReaderInstWithEmptyWrites", + library = ":axi_ram_dslx", + opt_ir_args = { + "inline_procs": "true", + "top": "__axi_ram__AxiRamReaderInstWithEmptyWrites__AxiRamReader_0__AxiRamReaderResponder_0__32_32_4_8_8_32768_7_32_4_100_next", + }, + tags = ["manual"], + verilog_file = "axi_ram.v", +) + +verilog_library( + name = "axi_ram_verilog_lib", + srcs = [ + ":axi_ram.v", + ], + tags = ["manual"], +) + +xls_benchmark_ir( + name = "axi_ram_opt_ir_benchmark", + src = ":axi_ram_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "4", + "delay_model": "asap7", + }, + tags = ["manual"], +) + +synthesize_rtl( + name = "axi_ram_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "AxiRam", + deps = [ + ":axi_ram_verilog_lib", + ], +) + +benchmark_synth( + name = "axi_ram_benchmark_synth", + synth_target = ":axi_ram_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "axi_ram_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 = ":axi_ram_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + xls_dslx_library( name = "mem_reader_dslx", srcs = ["mem_reader.x"], diff --git a/xls/modules/zstd/memory/axi.x b/xls/modules/zstd/memory/axi.x index 09bfc194e2..d4b347f013 100644 --- a/xls/modules/zstd/memory/axi.x +++ b/xls/modules/zstd/memory/axi.x @@ -25,7 +25,18 @@ pub enum AxiAxSize : u3 { MAX_128B_TRANSFER = 7, } -pub enum AxiWriteResp : u3 { +pub const AXI_AXSIZE_ENCODING_TO_SIZE = u11[8]:[ + u11:8, + u11:16, + u11:32, + u11:64, + u11:128, + u11:256, + u11:512, + u11:1024, +]; + +pub enum AxiWriteResp: u3 { OKAY = 0, EXOKAY = 1, SLVERR = 2, @@ -95,12 +106,12 @@ pub struct AxiAw { pub struct AxiW { data: uN[DATA_W], strb: uN[STRB_W], - last: u1 + last: u1, } pub struct AxiB { resp: AxiWriteResp, - id: uN[ID_W] + id: uN[ID_W], } pub struct AxiAr { diff --git a/xls/modules/zstd/memory/axi_ram.x b/xls/modules/zstd/memory/axi_ram.x new file mode 100644 index 0000000000..28326a848d --- /dev/null +++ b/xls/modules/zstd/memory/axi_ram.x @@ -0,0 +1,767 @@ +// Copyright 2023-2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import std; + +import xls.modules.zstd.math; +import xls.modules.zstd.memory.axi; +import xls.examples.ram; + +type AxiAr = axi::AxiAr; +type AxiR = axi::AxiR; + +type AxiReadResp = axi::AxiReadResp; +type AxiAxBurst = axi::AxiAxBurst; + +const AXI_AXSIZE_ENCODING_TO_SIZE = axi::AXI_AXSIZE_ENCODING_TO_SIZE; + +enum AxiRamReaderStatus: u1 { + IDLE = 0, + READ_BURST = 1, +} + +// FIXME: add default value for RAM_DATA_W_LOG2 = {std::clog2(AXI_DATA_W + u32:1)} (https://github.com/google/xls/issues/992) +struct AxiRamReaderSync { + do_recv_ram_resp: bool, + read_data_size: uN[RAM_DATA_W_LOG2], + read_data_offset: uN[RAM_DATA_W_LOG2], + send_data: bool, + resp: AxiReadResp, + id: uN[AXI_ID_W], + last: bool, +} + +struct AxiRamReaderRequesterState { + status: AxiRamReaderStatus, + ar_bundle: AxiAr, + read_data_size: u32, + addr: uN[AXI_ADDR_W], + ram_rd_req_idx: u8, +} + +// FIXME: add default value for AXI_DATA_W_LOG2 = {std::clog2(AXI_DATA_W + u32:1)} (https://github.com/google/xls/issues/992) +struct AxiRamReaderResponderState { + data: uN[AXI_DATA_W], + data_size: uN[AXI_DATA_W_LOG2], +} + +// Translates RAM requests to AXI read requests +proc AxiRamReaderRequester< + // AXI parameters + AXI_ADDR_W: u32, AXI_DATA_W: u32, AXI_DEST_W: u32, AXI_ID_W: u32, + + // FIXME: The parameter below should be calculated correctly, + // but causes deduction errors. The issue is possibly related to: + // https://github.com/google/xls/issues/1523 + + // RAM parameters + RAM_SIZE: u32, + RAM_DATA_W: u32, // = {AXI_DATA_W}, + RAM_ADDR_W: u32, // = {AXI_ADDR_W}, + RAM_NUM_PARTITIONS: u32, // = {AXI_DATA_W / u32:8 }, + + BASE_ADDR: u32, // = {u32:0}, + AXI_DATA_W_DIV8: u32, // = { AXI_DATA_W / u32:8 } +> { + type AxiAr = axi::AxiAr; + // FIXME: Replace with params + type ReadReq = ram::ReadReq; + + type State = AxiRamReaderRequesterState; + type Status = AxiRamReaderStatus; + type Sync = AxiRamReaderSync; + + axi_ar_r: chan in; + rd_req_s: chan out; + + sync_s: chan out; + + init { zero!() } + + config( + // AXI interface + axi_ar_r: chan in, + rd_req_s: chan out, + sync_s: chan out, + ) { + (axi_ar_r, rd_req_s, sync_s) + } + + next(state: State) { + const AXI_DATA_W_LOG2 = std::flog2(AXI_DATA_W) + u32:1; + const RAM_DATA_W_LOG2 = std::flog2(RAM_DATA_W) + u32:1; + const RAM_DATA_W_DIV8 = RAM_DATA_W >> u32:3; + + // receive AXI read request + let (tok, ar_bundle, ar_bundle_valid) = recv_if_non_blocking(join(), axi_ar_r, state.status == Status::IDLE, zero!()); + + // validate bundle + let ar_bundle_ok = ar_bundle_valid && ((ar_bundle.size as u32 + u32:3) < AXI_DATA_W_LOG2); + + let tok = send_if(tok, sync_s, ar_bundle_valid && !ar_bundle_ok, Sync { + id: ar_bundle.id, + resp: AxiReadResp::SLVERR, + last: true, + send_data: true, + ..zero!() + }); + + // send RAM read reqest + let addr_valid = state.addr < ((RAM_SIZE * RAM_DATA_W_DIV8) as uN[AXI_ADDR_W]); + let addr = (state.addr / RAM_DATA_W_DIV8) as uN[RAM_ADDR_W]; + + let do_read_from_ram = ( + (state.status == Status::READ_BURST) && + addr_valid && + (state.ram_rd_req_idx <= state.ar_bundle.len) + ); + let ram_read_req = ReadReq { + addr: addr, + mask: !uN[RAM_NUM_PARTITIONS]:0, + }; + let tok = send_if(join(), rd_req_s, do_read_from_ram, ram_read_req); + if do_read_from_ram { + trace_fmt!("Sent RAM read request {:#x}", ram_read_req); + } else {}; + + // send sync + let resp = if addr_valid { + AxiReadResp::OKAY + } else { + AxiReadResp::DECERR + }; + + // calculate read size and offset + let arsize_bits = AXI_AXSIZE_ENCODING_TO_SIZE[state.ar_bundle.size as u3] as uN[AXI_DATA_W_LOG2]; + + let (read_data_size, read_data_offset) = if (arsize_bits > RAM_DATA_W as uN[AXI_DATA_W_LOG2]) { + ( + RAM_DATA_W as uN[RAM_DATA_W_LOG2], + uN[RAM_DATA_W_LOG2]:0, + ) + } else { + ( + arsize_bits, + ((state.addr % RAM_DATA_W_DIV8) << u32:3) as uN[RAM_DATA_W_LOG2], + ) + }; + + let tok = send_if(tok, sync_s, state.status == Status::READ_BURST, Sync { + do_recv_ram_resp: do_read_from_ram, + read_data_size: read_data_size, + read_data_offset: read_data_offset, + send_data: read_data_size == arsize_bits, + resp: resp, + id: state.ar_bundle.id, + last: state.ram_rd_req_idx == state.ar_bundle.len, + }); + + // update state + match state.status { + Status::IDLE => { + if ar_bundle_ok { + State { + status: AxiRamReaderStatus::READ_BURST, + ar_bundle: ar_bundle, + addr: ar_bundle.addr, + ram_rd_req_idx: u8:0, + read_data_size: u32:0, + } + } else { state } + }, + Status::READ_BURST => { + if (state.ram_rd_req_idx == state.ar_bundle.len) { + State { + status: Status::IDLE, + ..state + } + } else { + let incr = math::logshiftl(uN[AXI_ADDR_W]:1, state.ar_bundle.size as uN[AXI_ADDR_W]); + let addr = match state.ar_bundle.burst { + AxiAxBurst::FIXED => state.addr, + AxiAxBurst::INCR => state.addr + incr, + AxiAxBurst::WRAP => if ((state.addr + incr) >= (RAM_SIZE * RAM_DATA_W_DIV8)) { + uN[AXI_ADDR_W]:0 + } else { + state.addr + incr + }, + _ => fail!("invalid_burst_mode", state.addr), + }; + State { + ram_rd_req_idx: state.ram_rd_req_idx + u8:1, + addr: addr, + ..state + } + } + }, + _ => state, + } + } +} + +// Should translate RAM responses to AXI read responses +proc AxiRamReaderResponder< + // AXI parameters + AXI_ADDR_W: u32, AXI_DATA_W: u32, AXI_DEST_W: u32, AXI_ID_W: u32, + + // FIXME: The parameter below should be calculated correctly, + // but causes deduction errors. The issue is possibly related to: + // https://github.com/google/xls/issues/1523 + + // RAM parameters + RAM_SIZE: u32, + RAM_DATA_W: u32, // = {AXI_DATA_W}, + RAM_ADDR_W: u32, // = {AXI_ADDR_W}, + RAM_NUM_PARTITIONS: u32, // = {AXI_DATA_W / u32:8 }, + + BASE_ADDR: u32, // = {u32:0}, + AXI_DATA_W_DIV8: u32, // = { AXI_DATA_W / u32:8 } +> { + // FIXME: Replace with params + type AxiR = axi::AxiR; + type ReadResp = ram::ReadResp; + + // FIXME: Replace with params + type State = AxiRamReaderResponderState; + // FIXME: Replace with params + type Sync = AxiRamReaderSync; + + rd_resp_r: chan in; + axi_r_s: chan out; + + sync_r: chan in; + + init { zero!() } + + config( + rd_resp_r: chan in, + axi_r_s: chan out, + sync_r: chan in, + ) { + (rd_resp_r, axi_r_s, sync_r) + } + + next(state: State) { + let tok = join(); + + // receive sync + let (tok, sync_data) = recv(tok, sync_r); + trace_fmt!("Received sync {:#x}", sync_data); + + // receive RAM read respose + let (tok, ram_read_resp) = recv_if(tok, rd_resp_r, sync_data.do_recv_ram_resp, zero!()); + if sync_data.do_recv_ram_resp { + trace_fmt!("Received RAM response {:#x}", ram_read_resp); + } else {}; + + let mask = math::logshiftl(uN[RAM_DATA_W]:1, sync_data.read_data_size as uN[RAM_DATA_W]) - uN[RAM_DATA_W]:1; + let mask = math::logshiftl(mask, state.data_size); + + let ram_data_shifted = if (sync_data.read_data_offset > state.data_size) { + math::logshiftr(ram_read_resp.data, sync_data.read_data_offset - state.data_size) as uN[AXI_DATA_W] & mask + } else { + math::logshiftl(ram_read_resp.data, state.data_size - sync_data.read_data_offset) as uN[AXI_DATA_W] & mask + }; + + // update state + let state = State { + data: ram_data_shifted, + data_size: state.data_size + sync_data.read_data_size, + }; + + // send AXI read response + let axi_r_bundle = AxiR { + id: sync_data.id, + data: state.data, + resp: sync_data.resp, + last: sync_data.last, + }; + let tok = send_if(tok, axi_r_s, sync_data.send_data, axi_r_bundle); + + if sync_data.send_data { + zero!() + } else { + state + } + } +} + +proc AxiRamReader< + // AXI parameters + AXI_ADDR_W: u32, + AXI_DATA_W: u32, + AXI_DEST_W: u32, + AXI_ID_W: u32, + + // RAM parameters + RAM_SIZE: u32, + + // FIXME: The parameter below should be calculated correctly, + // but causes deduction errors. The issue is possibly related to: + // https://github.com/google/xls/issues/1523 + + RAM_DATA_W: u32, // = {AXI_DATA_W}, + RAM_ADDR_W: u32, // = {AXI_ADDR_W}, + RAM_NUM_PARTITIONS: u32, // = { AXI_DATA_W / u32:8 }, + + BASE_ADDR: u32, // = {u32:0}, + AXI_DATA_W_DIV8: u32, // = { AXI_DATA_W / u32:8 } +> { + type AxiAr = axi::AxiAr; + type AxiR = axi::AxiR; + + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + + // FIXME: Replace with params + type Sync = AxiRamReaderSync; + + init { } + + config( + // AXI interface + axi_ar_r: chan in, + axi_r_s: chan out, + + // RAM interface + rd_req_s: chan out, + rd_resp_r: chan in, + ) { + let (sync_s, sync_r) = chan("sync"); + + spawn AxiRamReaderRequester< + AXI_ADDR_W, AXI_DATA_W, AXI_DEST_W, AXI_ID_W, + RAM_SIZE, RAM_DATA_W, RAM_ADDR_W, RAM_NUM_PARTITIONS, + BASE_ADDR, AXI_DATA_W_DIV8, + >(axi_ar_r, rd_req_s, sync_s); + spawn AxiRamReaderResponder< + AXI_ADDR_W, AXI_DATA_W, AXI_DEST_W, AXI_ID_W, + RAM_SIZE, RAM_DATA_W, RAM_ADDR_W, RAM_NUM_PARTITIONS, + BASE_ADDR, AXI_DATA_W_DIV8, + >(rd_resp_r, axi_r_s, sync_r); + } + + next(state: ()) { } +} + +const INST_AXI_ADDR_W = u32:32; +const INST_AXI_DATA_W = u32:32; +const INST_AXI_DEST_W = u32:8; +const INST_AXI_ID_W = u32:8; +const INST_AXI_DATA_W_DIV8 = INST_AXI_DATA_W / u32:8; + +const INST_RAM_SIZE = u32:100; +const INST_RAM_DATA_W = INST_AXI_DATA_W; +const INST_RAM_ADDR_W = std::clog2(INST_RAM_SIZE); +const INST_RAM_WORD_PARTITION_SIZE = u32:8; +const INST_RAM_NUM_PARTITIONS = INST_RAM_DATA_W / INST_RAM_WORD_PARTITION_SIZE; + +const INST_BASE_ADDR = u32:0x8000; + +proc AxiRamReaderInst< + FAKE_PARAM: u32 = {u32:0} // FIXME: remove after https://github.com/google/xls/issues/1415 is fixed +> { + type AxiAr = axi::AxiAr; + type AxiR = axi::AxiR; + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + + init { } + + config( + // AXI interface + axi_ar_r: chan in, + axi_r_s: chan out, + // RAM interface + rd_req_s: chan out, + rd_resp_r: chan in, + ) { + spawn AxiRamReader< + INST_AXI_ADDR_W, INST_AXI_DATA_W, INST_AXI_DEST_W, INST_AXI_ID_W, + INST_RAM_SIZE, INST_RAM_DATA_W, INST_RAM_ADDR_W, INST_RAM_NUM_PARTITIONS, + INST_BASE_ADDR, INST_AXI_DATA_W_DIV8 + > (axi_ar_r, axi_r_s, rd_req_s, rd_resp_r); + } + + next(state: ()) { } +} + +// only for RAM rewrite +proc AxiRamReaderInstWithEmptyWrites { + type AxiAr = axi::AxiAr; + type AxiR = axi::AxiR; + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp; + + wr_req_s: chan out; + wr_resp_r: chan in; + + init { } + + config( + // AXI interface + axi_ar_r: chan in, + axi_r_s: chan out, + // RAM interface + rd_req_s: chan out, + rd_resp_r: chan in, + wr_req_s: chan out, + wr_resp_r: chan in, + ) { + spawn AxiRamReader< + INST_AXI_ADDR_W, INST_AXI_DATA_W, INST_AXI_DEST_W, INST_AXI_ID_W, + INST_RAM_SIZE, INST_RAM_DATA_W, INST_RAM_ADDR_W, INST_RAM_NUM_PARTITIONS, + INST_BASE_ADDR, INST_AXI_DATA_W_DIV8 + > (axi_ar_r, axi_r_s, rd_req_s, rd_resp_r); + + ( + wr_req_s, wr_resp_r + ) + } + + next(state: ()) { + send_if(join(), wr_req_s, false, zero!()); + recv_if(join(), wr_resp_r, false, zero!()); + } +} + +const TEST_AXI_ADDR_W = u32:32; +const TEST_AXI_DATA_W = u32:32; +const TEST_AXI_DEST_W = u32:8; +const TEST_AXI_ID_W = u32:8; +const TEST_AXI_DATA_W_DIV8 = TEST_AXI_DATA_W / u32:8; + +const TEST_RAM_SIZE = u32:100; +const TEST_RAM_DATA_W = TEST_AXI_DATA_W; +const TEST_RAM_ADDR_W = std::clog2(TEST_RAM_SIZE); +const TEST_RAM_WORD_PARTITION_SIZE = u32:8; +const TEST_RAM_NUM_PARTITIONS = TEST_RAM_DATA_W / TEST_RAM_WORD_PARTITION_SIZE; +const TEST_RAM_SIZE_BYTES = TEST_RAM_SIZE * (TEST_RAM_DATA_W / u32:8); + +const TEST_BASE_ADDR = u32:0x8000; + +type TestAxiAr = axi::AxiAr; +type TestAxiR = axi::AxiR; + +type TestReadReq = ram::ReadReq; +type TestReadResp = ram::ReadResp; +type TestWriteReq = ram::WriteReq; +type TestWriteResp = ram::WriteResp; + +const ZERO_AXI_AR_BUNDLE = zero!(); + +type TestAxiId = uN[TEST_AXI_ID_W]; +type TestAxiAddr = uN[TEST_AXI_ADDR_W]; +type TestAxiRegion = uN[4]; +type TestAxiLen = uN[8]; +type TestAxiSize = axi::AxiAxSize; +type TestAxiBurst = axi::AxiAxBurst; +type TestAxiCache = axi::AxiArCache; +type TestAxiProt = uN[3]; +type TestAxiQos = uN[4]; + +const TEST_RAM_DATA = u32[TEST_RAM_SIZE]:[ + u32:0xD945_50A5, u32:0xA20C_D8D3, u32:0xB0BE_D046, u32:0xF83C_6D26, u32:0xFAE4_B0C4, + u32:0x9A78_91C4, u32:0xFDA0_9B1E, u32:0x5E66_D76D, u32:0xCB7D_76CB, u32:0x4033_5F2F, + u32:0x2128_9B0B, u32:0xD263_365F, u32:0xD989_DD81, u32:0xE4CB_45C9, u32:0x0425_06B6, + u32:0x5D31_107C, u32:0x2282_7A67, u32:0xCAC7_0C94, u32:0x23A9_5FD8, u32:0x6122_BBC3, + u32:0x1F99_F3D0, u32:0xA70C_FB34, u32:0x3812_5EF2, u32:0x9157_61BC, u32:0x171A_C1B1, + + u32:0xDE6F_1B08, u32:0x420D_F1AF, u32:0xAEE9_F51B, u32:0xB31E_E3A3, u32:0x66AC_09D6, + u32:0x18E9_9703, u32:0xEE87_1E7A, u32:0xB63D_47DE, u32:0x59BF_4F52, u32:0x94D8_5636, + u32:0x2B81_34EE, u32:0x6711_9968, u32:0xFB2B_F8CB, u32:0x173F_CB1B, u32:0xFB94_3A67, + u32:0xF40B_714F, u32:0x383B_82FE, u32:0xA692_055E, u32:0x58A6_2110, u32:0x0185_B5E0, + u32:0x9DF0_9C22, u32:0x54CA_DB57, u32:0xC626_097F, u32:0xEA04_3110, u32:0xF11C_4D36, + + u32:0xB8CC_FAB0, u32:0x7801_3B20, u32:0x8189_BF9C, u32:0xE380_A505, u32:0x4672_AE34, + u32:0x1CD5_1B3A, u32:0x5F95_EE9E, u32:0xBC5C_9931, u32:0xBCE6_50D2, u32:0xC10D_0544, + u32:0x5AB4_DEA1, u32:0x5E20_3394, u32:0x7FDA_0CA1, u32:0x6FEC_112E, u32:0x107A_2F81, + u32:0x86CA_4491, u32:0xEA68_0EB7, u32:0x50F1_AA22, u32:0x3F47_F2CA, u32:0xE407_92F7, + u32:0xF35C_EEE0, u32:0x1D6B_E819, u32:0x3FA7_05FA, u32:0x08BB_A499, u32:0x7C0C_4812, + + u32:0xF5A5_3D5C, u32:0x079A_BE16, u32:0xACA1_F84B, u32:0x4D2B_9402, u32:0x45B1_28FD, + u32:0x2C7C_CBA5, u32:0x6874_FC32, u32:0x95A0_8288, u32:0xFB13_E707, u32:0x61F9_2FEF, + u32:0xF6E3_DAFC, u32:0xDBA0_0A80, u32:0xBB84_831B, u32:0xAD63_2520, u32:0xEFB3_D817, + u32:0xD190_C435, u32:0x9064_1E4F, u32:0x0839_3D28, u32:0x1C07_874C, u32:0xBBEB_D633, + u32:0xB0A9_C751, u32:0x83B9_A340, u32:0x028A_FF8A, u32:0xB4ED_EE5C, u32:0xD700_BD9C, +]; + +const TEST_AXI_AR_BUNDLES = TestAxiAr[16]:[ + AxiAr { + id: TestAxiId:0, + addr: TestAxiAddr:40, + len: TestAxiLen:8, + size: TestAxiSize::MAX_4B_TRANSFER, + burst: TestAxiBurst::FIXED, + ..ZERO_AXI_AR_BUNDLE + }, + AxiAr { + id: TestAxiId:0, + addr: TestAxiAddr:440, + len: TestAxiLen:8, + size: TestAxiSize::MAX_4B_TRANSFER, + burst: TestAxiBurst::FIXED, + ..ZERO_AXI_AR_BUNDLE + }, + AxiAr { + id: TestAxiId:1, + addr: TestAxiAddr:32, + len: TestAxiLen:8, + size: TestAxiSize::MAX_4B_TRANSFER, + burst: TestAxiBurst::FIXED, + ..ZERO_AXI_AR_BUNDLE + }, + AxiAr { + id: TestAxiId:2, + addr: TestAxiAddr:16, + len: TestAxiLen:8, + size: TestAxiSize::MAX_4B_TRANSFER, + burst: TestAxiBurst::INCR, + ..ZERO_AXI_AR_BUNDLE + }, + AxiAr { + id: TestAxiId:3, + addr: TestAxiAddr:92, + len: TestAxiLen:4, + size: TestAxiSize::MAX_4B_TRANSFER, + burst: TestAxiBurst::INCR, + ..ZERO_AXI_AR_BUNDLE + }, + AxiAr { + id: TestAxiId:4, + addr: TestAxiAddr:0, + len: TestAxiLen:2, + size: TestAxiSize::MAX_4B_TRANSFER, + burst: TestAxiBurst::INCR, + ..ZERO_AXI_AR_BUNDLE + }, + AxiAr { + id: TestAxiId:5, + addr: TestAxiAddr:52, + len: TestAxiLen:20, + size: TestAxiSize::MAX_4B_TRANSFER, + burst: TestAxiBurst::INCR, + ..ZERO_AXI_AR_BUNDLE + }, + AxiAr { + id: TestAxiId:6, + addr: TestAxiAddr:96, + len: TestAxiLen:10, + size: TestAxiSize::MAX_4B_TRANSFER, + burst: TestAxiBurst::INCR, + ..ZERO_AXI_AR_BUNDLE + }, + AxiAr { + id: TestAxiId:7, + addr: TestAxiAddr:128, + len: TestAxiLen:16, + size: TestAxiSize::MAX_4B_TRANSFER, + burst: TestAxiBurst::WRAP, + ..ZERO_AXI_AR_BUNDLE + }, + AxiAr { + id: TestAxiId:8, + addr: TestAxiAddr:256, + len: TestAxiLen:2, + size: TestAxiSize::MAX_4B_TRANSFER, + burst: TestAxiBurst::WRAP, + ..ZERO_AXI_AR_BUNDLE + }, + AxiAr { + id: TestAxiId:9, + addr: TestAxiAddr:32, + len: TestAxiLen:4, + size: TestAxiSize::MAX_2B_TRANSFER, + burst: TestAxiBurst::FIXED, + ..ZERO_AXI_AR_BUNDLE + }, + AxiAr { + id: TestAxiId:10, + addr: TestAxiAddr:80, + len: TestAxiLen:4, + size: TestAxiSize::MAX_1B_TRANSFER, + burst: TestAxiBurst::INCR, + ..ZERO_AXI_AR_BUNDLE + }, + AxiAr { + id: TestAxiId:11, + addr: TestAxiAddr:256, + len: TestAxiLen:16, + size: TestAxiSize::MAX_2B_TRANSFER, + burst: TestAxiBurst::WRAP, + ..ZERO_AXI_AR_BUNDLE + }, + AxiAr { + id: TestAxiId:12, + addr: TestAxiAddr:64, + len: TestAxiLen:2, + size: TestAxiSize::MAX_8B_TRANSFER, + burst: TestAxiBurst::FIXED, + ..ZERO_AXI_AR_BUNDLE + }, + AxiAr { + id: TestAxiId:13, + addr: TestAxiAddr:192, + len: TestAxiLen:16, + size: TestAxiSize::MAX_64B_TRANSFER, + burst: TestAxiBurst::INCR, + ..ZERO_AXI_AR_BUNDLE + }, + AxiAr { + id: TestAxiId:14, + addr: TestAxiAddr:16, + len: TestAxiLen:16, + size: TestAxiSize::MAX_128B_TRANSFER, + burst: TestAxiBurst::INCR, + ..ZERO_AXI_AR_BUNDLE + }, +]; + +#[test_proc] +proc AxiRamReaderTest { + terminator: chan out; + + axi_ar_s: chan out; + axi_r_r: chan in; + + wr_req_s: chan out; + wr_resp_r: chan in; + + init {} + + config( + terminator: chan out, + ) { + let (rd_req_s, rd_req_r) = chan("rd_req"); + let (rd_resp_s, rd_resp_r) = chan("rd_resp"); + let (wr_req_s, wr_req_r) = chan("wr_req"); + let (wr_resp_s, wr_resp_r) = chan("wr_resp"); + + spawn ram::RamModel ( + rd_req_r, rd_resp_s, wr_req_r, wr_resp_s + ); + + let (axi_ar_s, axi_ar_r) = chan("axi_ar"); + let (axi_r_s, axi_r_r) = chan("axi_r"); + + spawn AxiRamReader< + TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, + TEST_RAM_SIZE, TEST_RAM_DATA_W, TEST_RAM_ADDR_W, TEST_RAM_NUM_PARTITIONS, + TEST_BASE_ADDR, TEST_AXI_DATA_W_DIV8, + >(axi_ar_r, axi_r_s, rd_req_s, rd_resp_r); + + ( + terminator, + axi_ar_s, axi_r_r, wr_req_s, wr_resp_r, + ) + } + + next(state: ()) { + type RamAddr = bits[TEST_RAM_ADDR_W]; + type RamData = bits[TEST_RAM_DATA_W]; + type RamMask = bits[TEST_RAM_NUM_PARTITIONS]; + + let tok = join(); + + // write test RAM data + let tok = for ((i, data), tok): ((u32, u32), token) in enumerate(TEST_RAM_DATA) { + let tok = send(tok, wr_req_s, TestWriteReq { + addr: i as RamAddr, + data: data, + mask: !bits[TEST_RAM_NUM_PARTITIONS]:0, + }); + let (tok, _) = recv(tok, wr_resp_r); + + tok + }(tok); + + let tok = for ((i, axi_ar_bundle), tok): ((u32, TestAxiAr), token) in enumerate(TEST_AXI_AR_BUNDLES) { + let tok = send(tok, axi_ar_s, axi_ar_bundle); + trace_fmt!("Sent bundle #{} {:#x}", i + u32:1, axi_ar_bundle); + + let size_valid = (u32:1 << (axi_ar_bundle.size as u32 + u32:3)) <= TEST_AXI_DATA_W; + + let data_len = if size_valid { + axi_ar_bundle.len as u32 + } else { + u32:0 + }; + + for (j, tok): (u32, token) in range(u32:0, TEST_RAM_SIZE) { + if (j <= data_len) { + let (tok, data) = recv(tok, axi_r_r); + trace_fmt!("Received data #{} {:#x}", j, data); + // compute address + let araddr = match axi_ar_bundle.burst { + AxiAxBurst::FIXED => { + axi_ar_bundle.addr + }, + AxiAxBurst::INCR => { + axi_ar_bundle.addr + j * (u32:1 << (axi_ar_bundle.size as u32)) + }, + AxiAxBurst::WRAP => { + (axi_ar_bundle.addr + j * (u32:1 << (axi_ar_bundle.size as u32))) % (TEST_RAM_SIZE * (TEST_RAM_DATA_W / u32:8)) + }, + }; + // create expected data using RAM data + let (expected_data, addr_valid) = for (k, (expected_data, addr_valid)): (u32, (uN[TEST_AXI_DATA_W], bool)) in range(u32:0, TEST_AXI_DATA_W / u32:8) { + if k < (u32:1 << (axi_ar_bundle.size as u32)) { + let ram_addr = (araddr + k) / (TEST_RAM_DATA_W / u32:8); + let ram_offset = ((araddr + k) % (TEST_RAM_DATA_W / u32:8)) * u32:8; + if ram_addr < TEST_RAM_SIZE { + ( + expected_data | (((TEST_RAM_DATA[ram_addr] >> ram_offset) & u32:0xFF) << (u32:8 * k)), + addr_valid, + ) + } else { + ( + uN[TEST_AXI_DATA_W]:0, + false, + ) + } + } else { + ( + expected_data, + addr_valid + ) + } + }((uN[TEST_AXI_DATA_W]:0, true)); + + let expected_rresp = if !size_valid { + AxiReadResp::SLVERR + } else if addr_valid { + AxiReadResp::OKAY + } else { + AxiReadResp::DECERR + }; + + assert_eq(expected_rresp, data.resp); + assert_eq(j == data_len, data.last); + assert_eq(axi_ar_bundle.id, data.id); + if expected_rresp == AxiReadResp::OKAY { + // valid read + assert_eq(expected_data, data.data); + } else { }; + tok + } else { tok } + }(tok) + }(tok); + + send(tok, terminator, true); + } +} + + From 34db10a243ba6e1d494ce9e3d6b1b90984e15efc Mon Sep 17 00:00:00 2001 From: Krzysztof Oblonczek Date: Mon, 12 May 2025 19:37:09 +0200 Subject: [PATCH 17/85] Adjust to changes in toolchain --- xls/modules/zstd/axi_csr_accessor.x | 13 +- xls/modules/zstd/memory/BUILD | 2 +- xls/modules/zstd/memory/axi_ram.x | 130 +++++++-------- xls/modules/zstd/sequence_executor.x | 2 +- xls/modules/zstd/zstd_dec.x | 237 +++------------------------ 5 files changed, 95 insertions(+), 289 deletions(-) diff --git a/xls/modules/zstd/axi_csr_accessor.x b/xls/modules/zstd/axi_csr_accessor.x index 01cef68cd9..860b36fe36 100644 --- a/xls/modules/zstd/axi_csr_accessor.x +++ b/xls/modules/zstd/axi_csr_accessor.x @@ -89,15 +89,16 @@ pub proc AxiCsrAccessor< let tok_0 = join(); // write to CSR via AXI let (tok_1_1, axi_aw, axi_aw_valid) = recv_non_blocking(tok_0, axi_aw_r, AxiAw {id: state.w_id, addr: state.w_addr, ..zero!()}); - // validate axi aw - assert!(!(axi_aw_valid && axi_aw.addr as u32 >= REGS_N), "invalid_aw_addr"); + assert!(!(axi_aw_valid && axi_aw.addr as u32 >= (REGS_N << LOG2_DATA_W_DIV8)), "invalid_aw_addr"); assert!(!(axi_aw_valid && axi_aw.len != u8:0), "invalid_aw_len"); let (tok_1_2, axi_w, axi_w_valid) = recv_non_blocking(tok_1_1, axi_w_r, zero!()); // Send WriteRequest to CSRs let data_w = if axi_w_valid { + trace_fmt!("[CSR ACCESSOR] received csr write at {:#x}", axi_w); + let (w_data, _, _) = for (i, (w_data, strb, mask)): (u32, (uN[DATA_W], uN[DATA_W_DIV8], uN[DATA_W])) in range(u32:0, DATA_W_DIV8) { let w_data = if axi_w.strb as u1 { w_data | (axi_w.data & mask) @@ -133,7 +134,7 @@ pub proc AxiCsrAccessor< // Send ReadRequest to CSRs let (tok_3_1, axi_ar, axi_ar_valid) = recv_non_blocking(tok_0, axi_ar_r, AxiAr {id: state.r_id, addr: state.r_addr, ..zero!()}); // validate ar bundle - assert!(!(axi_ar_valid && axi_ar.addr as u32 >= REGS_N), "invalid_ar_addr"); + assert!(!(axi_ar_valid && axi_ar.addr as u32 >= (REGS_N << LOG2_DATA_W_DIV8)), "invalid_ar_addr"); assert!(!(axi_ar_valid && axi_ar.len != u8:0), "invalid_ar_len"); let rd_req = RdReq { csr: (axi_ar.addr >> LOG2_DATA_W_DIV8) as uN[LOG2_REGS_N], @@ -208,7 +209,7 @@ proc AxiCsrAccessorInst { const TEST_ID_W = u32:4; const TEST_DATA_W = u32:32; const TEST_ADDR_W = u32:16; -const TEST_REGS_N = u32:16; +const TEST_REGS_N = u32:4; const TEST_DATA_W_DIV8 = TEST_DATA_W / u32:8; const TEST_LOG2_REGS_N = std::clog2(TEST_REGS_N); const TEST_LOG2_DATA_W_DIV8 = std::clog2(TEST_DATA_W_DIV8); @@ -309,7 +310,7 @@ proc AxiCsrAccessorTest { // write CSR via AXI let axi_aw = TestAxiAw { id: i as uN[TEST_ID_W], - addr: (test_data.csr << TEST_LOG2_DATA_W_DIV8) as uN[TEST_ADDR_W], + addr: (test_data.csr as uN[TEST_ADDR_W]) << TEST_LOG2_DATA_W_DIV8, size: axi::AxiAxSize::MAX_4B_TRANSFER, len: u8:0, burst: axi::AxiAxBurst::FIXED, @@ -346,7 +347,7 @@ proc AxiCsrAccessorTest { // read CSRs via AXI let axi_ar = TestAxiAr { id: i as uN[TEST_ID_W], - addr: (test_data.csr << TEST_LOG2_DATA_W_DIV8) as uN[TEST_ADDR_W], + addr: (test_data.csr as uN[TEST_ADDR_W]) << TEST_LOG2_DATA_W_DIV8, len: u8:0, ..zero!() }; diff --git a/xls/modules/zstd/memory/BUILD b/xls/modules/zstd/memory/BUILD index c8ee0a7fc6..96e824b19c 100644 --- a/xls/modules/zstd/memory/BUILD +++ b/xls/modules/zstd/memory/BUILD @@ -337,7 +337,7 @@ xls_dslx_verilog( library = ":axi_ram_dslx", opt_ir_args = { "inline_procs": "true", - "top": "__axi_ram__AxiRamReaderInstWithEmptyWrites__AxiRamReader_0__AxiRamReaderResponder_0__32_32_4_8_8_32768_7_32_4_100_next", + "top": "__axi_ram__AxiRamReaderInstWithEmptyWrites__AxiRamReader_0__AxiRamReaderResponder_0__32_32_4_5_6_8_8_32768_7_32_5_6_4_100_next", }, tags = ["manual"], verilog_file = "axi_ram.v", diff --git a/xls/modules/zstd/memory/axi_ram.x b/xls/modules/zstd/memory/axi_ram.x index 28326a848d..9683e61fc6 100644 --- a/xls/modules/zstd/memory/axi_ram.x +++ b/xls/modules/zstd/memory/axi_ram.x @@ -31,11 +31,11 @@ enum AxiRamReaderStatus: u1 { READ_BURST = 1, } -// FIXME: add default value for RAM_DATA_W_LOG2 = {std::clog2(AXI_DATA_W + u32:1)} (https://github.com/google/xls/issues/992) -struct AxiRamReaderSync { +// FIXME: add default value for RAM_DATA_W_PLUS1_LOG2 = {std::clog2(AXI_DATA_W + u32:1)} (https://github.com/google/xls/issues/992) +struct AxiRamReaderSync { do_recv_ram_resp: bool, - read_data_size: uN[RAM_DATA_W_LOG2], - read_data_offset: uN[RAM_DATA_W_LOG2], + read_data_size: uN[RAM_DATA_W_PLUS1_LOG2], + read_data_offset: uN[RAM_DATA_W_PLUS1_LOG2], send_data: bool, resp: AxiReadResp, id: uN[AXI_ID_W], @@ -50,10 +50,10 @@ struct AxiRamReaderRequesterState { ram_rd_req_idx: u8, } -// FIXME: add default value for AXI_DATA_W_LOG2 = {std::clog2(AXI_DATA_W + u32:1)} (https://github.com/google/xls/issues/992) -struct AxiRamReaderResponderState { +// FIXME: add default value for AXI_DATA_W_PLUS1_LOG2 = {std::clog2(AXI_DATA_W + u32:1)} (https://github.com/google/xls/issues/992) +struct AxiRamReaderResponderState { data: uN[AXI_DATA_W], - data_size: uN[AXI_DATA_W_LOG2], + data_size: uN[AXI_DATA_W_PLUS1_LOG2], } // Translates RAM requests to AXI read requests @@ -61,26 +61,25 @@ proc AxiRamReaderRequester< // AXI parameters AXI_ADDR_W: u32, AXI_DATA_W: u32, AXI_DEST_W: u32, AXI_ID_W: u32, - // FIXME: The parameter below should be calculated correctly, - // but causes deduction errors. The issue is possibly related to: - // https://github.com/google/xls/issues/1523 - // RAM parameters RAM_SIZE: u32, - RAM_DATA_W: u32, // = {AXI_DATA_W}, - RAM_ADDR_W: u32, // = {AXI_ADDR_W}, - RAM_NUM_PARTITIONS: u32, // = {AXI_DATA_W / u32:8 }, - - BASE_ADDR: u32, // = {u32:0}, - AXI_DATA_W_DIV8: u32, // = { AXI_DATA_W / u32:8 } + BASE_ADDR: u32 = {u32:0}, + RAM_DATA_W: u32 = {AXI_DATA_W}, + RAM_ADDR_W: u32 = {AXI_ADDR_W}, + RAM_NUM_PARTITIONS: u32 = {AXI_DATA_W / u32:8 }, + + AXI_DATA_W_DIV8: u32 = { AXI_DATA_W / u32:8 }, + RAM_DATA_W_LOG2: u32 = { std::clog2(RAM_DATA_W) }, + AXI_DATA_W_LOG2: u32 = { std::clog2(AXI_DATA_W) }, + AXI_DATA_W_PLUS1_LOG2: u32 = { std::clog2(AXI_DATA_W + u32:1) }, + RAM_DATA_W_PLUS1_LOG2: u32 = { std::clog2(RAM_DATA_W + u32:1) }, > { type AxiAr = axi::AxiAr; - // FIXME: Replace with params - type ReadReq = ram::ReadReq; + type ReadReq = ram::ReadReq; type State = AxiRamReaderRequesterState; type Status = AxiRamReaderStatus; - type Sync = AxiRamReaderSync; + type Sync = AxiRamReaderSync; axi_ar_r: chan in; rd_req_s: chan out; @@ -99,16 +98,16 @@ proc AxiRamReaderRequester< } next(state: State) { - const AXI_DATA_W_LOG2 = std::flog2(AXI_DATA_W) + u32:1; - const RAM_DATA_W_LOG2 = std::flog2(RAM_DATA_W) + u32:1; const RAM_DATA_W_DIV8 = RAM_DATA_W >> u32:3; // receive AXI read request let (tok, ar_bundle, ar_bundle_valid) = recv_if_non_blocking(join(), axi_ar_r, state.status == Status::IDLE, zero!()); // validate bundle - let ar_bundle_ok = ar_bundle_valid && ((ar_bundle.size as u32 + u32:3) < AXI_DATA_W_LOG2); - + let ar_bundle_ok = ar_bundle_valid && ((ar_bundle.size as u32 + u32:3) <= AXI_DATA_W_LOG2); + if ar_bundle_valid { + trace_fmt!("{:#x}", ar_bundle); + } else {}; let tok = send_if(tok, sync_s, ar_bundle_valid && !ar_bundle_ok, Sync { id: ar_bundle.id, resp: AxiReadResp::SLVERR, @@ -143,17 +142,17 @@ proc AxiRamReaderRequester< }; // calculate read size and offset - let arsize_bits = AXI_AXSIZE_ENCODING_TO_SIZE[state.ar_bundle.size as u3] as uN[AXI_DATA_W_LOG2]; + let arsize_bits = AXI_AXSIZE_ENCODING_TO_SIZE[state.ar_bundle.size as u3] as uN[AXI_DATA_W_PLUS1_LOG2]; - let (read_data_size, read_data_offset) = if (arsize_bits > RAM_DATA_W as uN[AXI_DATA_W_LOG2]) { + let (read_data_size, read_data_offset) = if (arsize_bits > RAM_DATA_W as uN[AXI_DATA_W_PLUS1_LOG2]) { ( - RAM_DATA_W as uN[RAM_DATA_W_LOG2], - uN[RAM_DATA_W_LOG2]:0, + RAM_DATA_W as uN[RAM_DATA_W_PLUS1_LOG2], + uN[RAM_DATA_W_PLUS1_LOG2]:0, ) } else { ( arsize_bits, - ((state.addr % RAM_DATA_W_DIV8) << u32:3) as uN[RAM_DATA_W_LOG2], + ((state.addr % RAM_DATA_W_DIV8) << u32:3) as uN[RAM_DATA_W_PLUS1_LOG2], ) }; @@ -215,27 +214,24 @@ proc AxiRamReaderResponder< // AXI parameters AXI_ADDR_W: u32, AXI_DATA_W: u32, AXI_DEST_W: u32, AXI_ID_W: u32, - // FIXME: The parameter below should be calculated correctly, - // but causes deduction errors. The issue is possibly related to: - // https://github.com/google/xls/issues/1523 - // RAM parameters RAM_SIZE: u32, - RAM_DATA_W: u32, // = {AXI_DATA_W}, - RAM_ADDR_W: u32, // = {AXI_ADDR_W}, - RAM_NUM_PARTITIONS: u32, // = {AXI_DATA_W / u32:8 }, - - BASE_ADDR: u32, // = {u32:0}, - AXI_DATA_W_DIV8: u32, // = { AXI_DATA_W / u32:8 } + BASE_ADDR: u32 = {u32:0}, + RAM_DATA_W: u32 = {AXI_DATA_W}, + RAM_ADDR_W: u32 = {AXI_ADDR_W}, + RAM_NUM_PARTITIONS: u32 = {AXI_DATA_W / u32:8 }, + + AXI_DATA_W_DIV8: u32 = { AXI_DATA_W / u32:8 }, + AXI_DATA_W_LOG2: u32 = { std::clog2(AXI_DATA_W) }, + RAM_DATA_W_LOG2: u32 = { std::clog2(RAM_DATA_W) }, + AXI_DATA_W_PLUS1_LOG2: u32 = { std::clog2(AXI_DATA_W + u32:1) }, + RAM_DATA_W_PLUS1_LOG2: u32 = { std::clog2(RAM_DATA_W + u32:1) }, > { - // FIXME: Replace with params - type AxiR = axi::AxiR; + type AxiR = axi::AxiR; type ReadResp = ram::ReadResp; - // FIXME: Replace with params - type State = AxiRamReaderResponderState; - // FIXME: Replace with params - type Sync = AxiRamReaderSync; + type State = AxiRamReaderResponderState; + type Sync = AxiRamReaderSync; rd_resp_r: chan in; axi_r_s: chan out; @@ -297,7 +293,7 @@ proc AxiRamReaderResponder< } } -proc AxiRamReader< +pub proc AxiRamReader< // AXI parameters AXI_ADDR_W: u32, AXI_DATA_W: u32, @@ -306,17 +302,14 @@ proc AxiRamReader< // RAM parameters RAM_SIZE: u32, - - // FIXME: The parameter below should be calculated correctly, - // but causes deduction errors. The issue is possibly related to: - // https://github.com/google/xls/issues/1523 - - RAM_DATA_W: u32, // = {AXI_DATA_W}, - RAM_ADDR_W: u32, // = {AXI_ADDR_W}, - RAM_NUM_PARTITIONS: u32, // = { AXI_DATA_W / u32:8 }, - - BASE_ADDR: u32, // = {u32:0}, - AXI_DATA_W_DIV8: u32, // = { AXI_DATA_W / u32:8 } + BASE_ADDR: u32 = {u32:0}, + RAM_DATA_W: u32 = {AXI_DATA_W}, + RAM_ADDR_W: u32 = {AXI_ADDR_W}, + RAM_NUM_PARTITIONS: u32 = { AXI_DATA_W / u32:8 }, + + AXI_DATA_W_DIV8: u32 = { AXI_DATA_W / u32:8 }, + RAM_DATA_W_LOG2: u32 = { std::clog2(RAM_DATA_W) }, + RAM_DATA_W_PLUS1_LOG2: u32 = { std::clog2(RAM_DATA_W + u32:1) }, > { type AxiAr = axi::AxiAr; type AxiR = axi::AxiR; @@ -324,8 +317,7 @@ proc AxiRamReader< type ReadReq = ram::ReadReq; type ReadResp = ram::ReadResp; - // FIXME: Replace with params - type Sync = AxiRamReaderSync; + type Sync = AxiRamReaderSync; init { } @@ -342,13 +334,13 @@ proc AxiRamReader< spawn AxiRamReaderRequester< AXI_ADDR_W, AXI_DATA_W, AXI_DEST_W, AXI_ID_W, - RAM_SIZE, RAM_DATA_W, RAM_ADDR_W, RAM_NUM_PARTITIONS, - BASE_ADDR, AXI_DATA_W_DIV8, + RAM_SIZE, BASE_ADDR, RAM_DATA_W, RAM_ADDR_W, RAM_NUM_PARTITIONS, + AXI_DATA_W_DIV8, >(axi_ar_r, rd_req_s, sync_s); spawn AxiRamReaderResponder< AXI_ADDR_W, AXI_DATA_W, AXI_DEST_W, AXI_ID_W, - RAM_SIZE, RAM_DATA_W, RAM_ADDR_W, RAM_NUM_PARTITIONS, - BASE_ADDR, AXI_DATA_W_DIV8, + RAM_SIZE, BASE_ADDR, RAM_DATA_W, RAM_ADDR_W, RAM_NUM_PARTITIONS, + AXI_DATA_W_DIV8, >(rd_resp_r, axi_r_s, sync_r); } @@ -389,8 +381,8 @@ proc AxiRamReaderInst< ) { spawn AxiRamReader< INST_AXI_ADDR_W, INST_AXI_DATA_W, INST_AXI_DEST_W, INST_AXI_ID_W, - INST_RAM_SIZE, INST_RAM_DATA_W, INST_RAM_ADDR_W, INST_RAM_NUM_PARTITIONS, - INST_BASE_ADDR, INST_AXI_DATA_W_DIV8 + INST_RAM_SIZE, INST_BASE_ADDR, INST_RAM_DATA_W, INST_RAM_ADDR_W, INST_RAM_NUM_PARTITIONS, + INST_AXI_DATA_W_DIV8 > (axi_ar_r, axi_r_s, rd_req_s, rd_resp_r); } @@ -423,8 +415,8 @@ proc AxiRamReaderInstWithEmptyWrites { ) { spawn AxiRamReader< INST_AXI_ADDR_W, INST_AXI_DATA_W, INST_AXI_DEST_W, INST_AXI_ID_W, - INST_RAM_SIZE, INST_RAM_DATA_W, INST_RAM_ADDR_W, INST_RAM_NUM_PARTITIONS, - INST_BASE_ADDR, INST_AXI_DATA_W_DIV8 + INST_RAM_SIZE, INST_BASE_ADDR, INST_RAM_DATA_W, INST_RAM_ADDR_W, INST_RAM_NUM_PARTITIONS, + INST_AXI_DATA_W_DIV8 > (axi_ar_r, axi_r_s, rd_req_s, rd_resp_r); ( @@ -659,8 +651,8 @@ proc AxiRamReaderTest { spawn AxiRamReader< TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, - TEST_RAM_SIZE, TEST_RAM_DATA_W, TEST_RAM_ADDR_W, TEST_RAM_NUM_PARTITIONS, - TEST_BASE_ADDR, TEST_AXI_DATA_W_DIV8, + TEST_RAM_SIZE, TEST_BASE_ADDR, TEST_RAM_DATA_W, TEST_RAM_ADDR_W, TEST_RAM_NUM_PARTITIONS, + TEST_AXI_DATA_W_DIV8, >(axi_ar_r, axi_r_s, rd_req_s, rd_resp_r); ( diff --git a/xls/modules/zstd/sequence_executor.x b/xls/modules/zstd/sequence_executor.x index 78251408c5..1e01f9b03c 100644 --- a/xls/modules/zstd/sequence_executor.x +++ b/xls/modules/zstd/sequence_executor.x @@ -1140,7 +1140,7 @@ pub proc SequenceExecutor in; notify_s: chan<()> out; + reset_s: chan<()> out; init { zero!() @@ -190,6 +191,7 @@ proc ZstdDecoderInternal< rle_resp_r: chan in, notify_s: chan<()> out, + reset_s: chan<()> out, ) { ( csr_rd_req_s, csr_rd_resp_r, csr_wr_req_s, csr_wr_resp_r, csr_change_r, @@ -197,7 +199,7 @@ proc ZstdDecoderInternal< bh_req_s, bh_resp_r, raw_req_s, raw_resp_r, rle_req_s, rle_resp_r, - notify_s, + notify_s, reset_s, ) } @@ -214,6 +216,16 @@ proc ZstdDecoderInternal< let (tok1_0, csr_change, csr_change_valid) = recv_non_blocking(tok0, csr_change_r, zero!()); let is_start = (csr_change_valid && (csr_change.csr == csr(Csr::START))); + let is_reset = (csr_change_valid && (csr_change.csr == csr(Csr::RESET))); + let tok = send_if(tok0, reset_s, is_reset, ()); + if is_reset { + trace_fmt!("[[RESET]]"); + } else {}; + + if csr_change_valid { + trace_fmt!("[CSR CHANGE] {:#x}", csr_change); + } else {}; + let do_send_csr_req = (state.fsm == Fsm::READ_CONFIG) && (!state.conf_send); let csr_req = CSR_REQS[state.conf_cnt]; let tok1_1 = send_if(tok0, csr_rd_req_s, do_send_csr_req, csr_req); @@ -484,26 +496,8 @@ proc ZstdDecoderInternal< const TEST_AXI_DATA_W = u32:64; const TEST_AXI_ADDR_W = u32:32; -const TEST_AXI_ID_W = u32:8; -const TEST_AXI_DEST_W = u32:8; const TEST_REGS_N = u32:5; -const TEST_WINDOW_LOG_MAX = u32:30; - -const TEST_HB_ADDR_W = sequence_executor::ZSTD_RAM_ADDR_WIDTH; -const TEST_HB_DATA_W = sequence_executor::RAM_DATA_WIDTH; -const TEST_HB_NUM_PARTITIONS = sequence_executor::RAM_NUM_PARTITIONS; -const TEST_HB_SIZE_KB = sequence_executor::ZSTD_HISTORY_BUFFER_SIZE_KB; - const TEST_LOG2_REGS_N = std::clog2(TEST_REGS_N); -const TEST_AXI_DATA_W_DIV8 = TEST_AXI_DATA_W / u32:8; -const TEST_HB_RAM_N = u32:8; - -const TEST_HB_SIZE = sequence_executor::ram_size(TEST_HB_SIZE_KB); -const TEST_HB_RAM_WORD_PARTITION_SIZE = sequence_executor::RAM_WORD_PARTITION_SIZE; -const TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = sequence_executor::TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR; -const TEST_HB_RAM_INITIALIZED = sequence_executor::TEST_RAM_INITIALIZED; -const TEST_HB_RAM_ASSERT_VALID_READ:bool = {false}; - #[test_proc] proc ZstdDecoderInternalTest { @@ -562,6 +556,7 @@ proc ZstdDecoderInternalTest { rle_resp_s: chan out; notify_r: chan<()> in; + reset_r: chan<()> in; init {} @@ -585,6 +580,7 @@ proc ZstdDecoderInternalTest { let (rle_resp_s, rle_resp_r) = chan("rle_resp"); let (notify_s, notify_r) = chan<()>("notify"); + let (reset_s, reset_r) = chan<()>("reset"); spawn ZstdDecoderInternal( csr_rd_req_s, csr_rd_resp_r, csr_wr_req_s, csr_wr_resp_r, csr_change_r, @@ -592,7 +588,7 @@ proc ZstdDecoderInternalTest { bh_req_s, bh_resp_r, raw_req_s, raw_resp_r, rle_req_s, rle_resp_r, - notify_s, + notify_s, reset_s, ); ( @@ -602,7 +598,7 @@ proc ZstdDecoderInternalTest { bh_req_r, bh_resp_s, raw_req_r, raw_resp_s, rle_req_r, rle_resp_s, - notify_r, + notify_r, reset_r, ) } @@ -760,7 +756,7 @@ proc ZstdDecoderInternalTest { } -proc ZstdDecoder< +pub proc ZstdDecoder< // AXI parameters AXI_DATA_W: u32, AXI_ADDR_W: u32, AXI_ID_W: u32, AXI_DEST_W: u32, // decoder parameters @@ -875,6 +871,7 @@ proc ZstdDecoder< // Decoder output output_s: chan out, notify_s: chan<()> out, + reset_s: chan<()> out, ) { const CHANNEL_DEPTH = u32:1; @@ -1012,7 +1009,7 @@ proc ZstdDecoder< bh_req_s, bh_resp_r, raw_req_s, raw_resp_r, rle_req_s, rle_resp_r, - notify_s, + notify_s, reset_s, ); (cmp_output_s,) @@ -1023,192 +1020,6 @@ proc ZstdDecoder< } } -//#[test_proc] -//proc ZstdDecoderTest { -// type CsrAxiAr = axi::AxiAr; -// type CsrAxiR = axi::AxiR; -// type CsrAxiAw = axi::AxiAw; -// type CsrAxiW = axi::AxiW; -// type CsrAxiB = axi::AxiB; -// -// type CsrRdReq = csr_config::CsrRdReq; -// type CsrRdResp = csr_config::CsrRdResp; -// type CsrWrReq = csr_config::CsrWrReq; -// type CsrWrResp = csr_config::CsrWrResp; -// type CsrChange = csr_config::CsrChange; -// -// type MemAxiAr = axi::AxiAr; -// type MemAxiR = axi::AxiR; -// type MemAxiAw = axi::AxiAw; -// type MemAxiW = axi::AxiW; -// type MemAxiB = axi::AxiB; -// -// type RamRdReq = ram::ReadReq; -// type RamRdResp = ram::ReadResp; -// type RamWrReq = ram::WriteReq; -// type RamWrResp = ram::WriteResp; -// -// type ZstdDecodedPacket = common::ZstdDecodedPacket; -// terminator: chan out; -// -// init {} -// -// config(terminator: chan out) { -// -// let (csr_axi_aw_s, csr_axi_aw_r) = chan("csr_axi_aw"); -// let (csr_axi_w_s, csr_axi_w_r) = chan("csr_axi_w"); -// let (csr_axi_b_s, csr_axi_b_r) = chan("csr_axi_b"); -// let (csr_axi_ar_s, csr_axi_ar_r) = chan("csr_axi_ar"); -// let (csr_axi_r_s, csr_axi_r_r) = chan("csr_axi_r"); -// -// let (fh_axi_ar_s, fh_axi_ar_r) = chan("fh_axi_ar_s"); -// let (fh_axi_r_s, fh_axi_r_r) = chan("fh_axi_r_r"); -// -// let (bh_axi_ar_s, bh_axi_ar_r) = chan("bh_axi_ar"); -// let (bh_axi_r_s, bh_axi_r_r) = chan("bh_axi_r"); -// -// let (raw_axi_ar_s, raw_axi_ar_r) = chan("raw_axi_ar"); -// let (raw_axi_r_s, raw_axi_r_r) = chan("raw_axi_r"); -// -// let (rle_axi_ar_s, rle_axi_ar_r) = chan("rle_axi_ar"); -// let (rle_axi_r_s, rle_axi_r_r) = chan("rle_axi_r"); -// -// let (ram_rd_req_0_s, ram_rd_req_0_r) = chan("ram_rd_req_0"); -// let (ram_rd_req_1_s, ram_rd_req_1_r) = chan("ram_rd_req_1"); -// let (ram_rd_req_2_s, ram_rd_req_2_r) = chan("ram_rd_req_2"); -// let (ram_rd_req_3_s, ram_rd_req_3_r) = chan("ram_rd_req_3"); -// let (ram_rd_req_4_s, ram_rd_req_4_r) = chan("ram_rd_req_4"); -// let (ram_rd_req_5_s, ram_rd_req_5_r) = chan("ram_rd_req_5"); -// let (ram_rd_req_6_s, ram_rd_req_6_r) = chan("ram_rd_req_6"); -// let (ram_rd_req_7_s, ram_rd_req_7_r) = chan("ram_rd_req_7"); -// -// let (ram_rd_resp_0_s, ram_rd_resp_0_r) = chan("ram_rd_resp_0"); -// let (ram_rd_resp_1_s, ram_rd_resp_1_r) = chan("ram_rd_resp_1"); -// let (ram_rd_resp_2_s, ram_rd_resp_2_r) = chan("ram_rd_resp_2"); -// let (ram_rd_resp_3_s, ram_rd_resp_3_r) = chan("ram_rd_resp_3"); -// let (ram_rd_resp_4_s, ram_rd_resp_4_r) = chan("ram_rd_resp_4"); -// let (ram_rd_resp_5_s, ram_rd_resp_5_r) = chan("ram_rd_resp_5"); -// let (ram_rd_resp_6_s, ram_rd_resp_6_r) = chan("ram_rd_resp_6"); -// let (ram_rd_resp_7_s, ram_rd_resp_7_r) = chan("ram_rd_resp_7"); -// -// let (ram_wr_req_0_s, ram_wr_req_0_r) = chan("ram_wr_req_0"); -// let (ram_wr_req_1_s, ram_wr_req_1_r) = chan("ram_wr_req_1"); -// let (ram_wr_req_2_s, ram_wr_req_2_r) = chan("ram_wr_req_2"); -// let (ram_wr_req_3_s, ram_wr_req_3_r) = chan("ram_wr_req_3"); -// let (ram_wr_req_4_s, ram_wr_req_4_r) = chan("ram_wr_req_4"); -// let (ram_wr_req_5_s, ram_wr_req_5_r) = chan("ram_wr_req_5"); -// let (ram_wr_req_6_s, ram_wr_req_6_r) = chan("ram_wr_req_6"); -// let (ram_wr_req_7_s, ram_wr_req_7_r) = chan("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"); -// -// let (output_s, output_r) = chan("output"); -// let (notify_s, notify_r) = chan<()>("notify"); -// -// spawn ZstdDecoder< -// TEST_AXI_DATA_W, TEST_AXI_ADDR_W, TEST_AXI_ID_W, TEST_AXI_DEST_W, -// TEST_REGS_N, TEST_WINDOW_LOG_MAX, -// TEST_HB_ADDR_W, TEST_HB_DATA_W, TEST_HB_NUM_PARTITIONS, TEST_HB_SIZE_KB, -// >( -// csr_axi_aw_r, csr_axi_w_r, csr_axi_b_s, csr_axi_ar_r, csr_axi_r_s, -// fh_axi_ar_s, fh_axi_r_r, -// bh_axi_ar_s, bh_axi_r_r, -// raw_axi_ar_s, raw_axi_r_r, -// rle_axi_ar_s, rle_axi_r_r, -// 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, -// output_s, notify_s, -// ); -// -// spawn ram::RamModel< -// TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, -// TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, -// TEST_HB_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< -// TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, -// TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, -// TEST_HB_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< -// TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, -// TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_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_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_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< -// TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, -// TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_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< -// TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, -// TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_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< -// TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, -// TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_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< -// TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, -// TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_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); -// -// ( -// terminator, -// csr_axi_aw_s, csr_axi_w_s, csr_axi_b_r, csr_axi_ar_s, csr_axi_s_r, -// fh_axi_ar_r, fh_axi_s_s, -// bh_axi_ar_r, bh_axi_s_s, -// raw_axi_ar_r, raw_axi_s_s, -// rle_axi_ar_r, rle_axi_s_s, -// ram_sd_seq_0_r, ram_sd_seq_1_r, ram_sd_seq_2_r, ram_sd_seq_3_r, -// ram_sd_seq_4_r, ram_sd_seq_5_r, ram_sd_seq_6_r, ram_sd_seq_7_r, -// ram_sd_sesp_0_s, ram_sd_sesp_1_s, ram_sd_sesp_2_s, ram_sd_sesp_3_s, -// ram_sd_sesp_4_s, ram_sd_sesp_5_s, ram_sd_sesp_6_s, ram_sd_sesp_7_s, -// ram_wr_seq_0_r, ram_wr_seq_1_r, ram_wr_seq_2_r, ram_wr_seq_3_r, -// ram_wr_seq_4_r, ram_wr_seq_5_r, ram_wr_seq_6_r, ram_wr_seq_7_r, -// ram_wr_sesp_0_s, ram_wr_sesp_1_s, ram_wr_sesp_2_s, ram_wr_sesp_3_s, -// ram_wr_sesp_4_s, ram_wr_sesp_5_s, ram_wr_sesp_6_s, ram_wr_sesp_7_s, -// output_r, notify_r, -// ) -// } -// -// next (state: ()) { -// -// trace_fmt!("Test start"); -// -// send(join(), terminator, true); -// } -//} - - const INST_AXI_DATA_W = u32:64; const INST_AXI_ADDR_W = u32:16; const INST_AXI_ID_W = u32:4; @@ -1273,6 +1084,7 @@ proc ZstdDecoderInternalInst { // IRQ notify_s: chan<()> out, + reset_s: chan<()> out, ) { spawn ZstdDecoderInternal< INST_AXI_DATA_W, INST_AXI_ADDR_W, INST_REGS_N, @@ -1282,7 +1094,7 @@ proc ZstdDecoderInternalInst { bh_req_s, bh_resp_r, raw_req_s, raw_resp_r, rle_req_s, rle_resp_r, - notify_s, + notify_s, reset_s, ); } @@ -1369,6 +1181,7 @@ proc ZstdDecoderInst { // Decoder output output_s: chan out, notify_s: chan<()> out, + reset_s: chan<()> out, ) { spawn ZstdDecoder< INST_AXI_DATA_W, INST_AXI_ADDR_W, INST_AXI_ID_W, INST_AXI_DEST_W, @@ -1387,7 +1200,7 @@ proc ZstdDecoderInst { 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, - output_s, notify_s, + output_s, notify_s, reset_s, ); } From 009e26518c979a899cfbb160e4914c2b289d084a Mon Sep 17 00:00:00 2001 From: Krzysztof Oblonczek Date: Tue, 13 May 2025 14:33:14 +0200 Subject: [PATCH 18/85] modules/zstd/zstd_dec: Add DSLX tests for ZstdDecoder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Krzysztof Obłonczek --- xls/modules/zstd/BUILD | 53 +++-- xls/modules/zstd/math.x | 88 ++++++++ xls/modules/zstd/zstd_dec_test.x | 367 +++++++++++++++++++++++++++++++ 3 files changed, 492 insertions(+), 16 deletions(-) create mode 100644 xls/modules/zstd/math.x create mode 100644 xls/modules/zstd/zstd_dec_test.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index c086942d22..70bda5af9b 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -45,6 +45,19 @@ common_codegen_args = { "multi_proc": "true", } +xls_dslx_library( + name = "math_dslx", + srcs = [ + "math.x", + ], +) + +xls_dslx_test( + name = "math_dslx_test", + library = ":math_dslx", + tags = ["manual"], +) + xls_dslx_library( name = "buffer_dslx", srcs = [ @@ -848,32 +861,40 @@ place_and_route( target_die_utilization_percentage = "10", ) +zstd_dec_deps = [ + ":axi_csr_accessor_dslx", + ":block_header_dec_dslx", + ":block_header_dslx", + ":common_dslx", + ":csr_config_dslx", + ":dec_mux_dslx", + ":frame_header_dec_dslx", + ":raw_block_dec_dslx", + ":repacketizer_dslx", + ":rle_block_dec_dslx", + ":sequence_executor_dslx", + "//xls/examples:ram_dslx", + "//xls/modules/zstd/memory:mem_reader_dslx", + "//xls/modules/zstd/memory:axi_ram_dslx", +] + xls_dslx_library( name = "zstd_dec_dslx", srcs = [ "zstd_dec.x", ], - deps = [ - ":axi_csr_accessor_dslx", - ":block_header_dec_dslx", - ":block_header_dslx", - ":common_dslx", - ":csr_config_dslx", - ":dec_mux_dslx", - ":frame_header_dec_dslx", - ":raw_block_dec_dslx", - ":repacketizer_dslx", - ":rle_block_dec_dslx", - ":sequence_executor_dslx", - "//xls/examples:ram_dslx", - "//xls/modules/zstd/memory:mem_reader_dslx", - ], + deps = zstd_dec_deps, ) xls_dslx_test( name = "zstd_dec_dslx_test", - library = ":zstd_dec_dslx", + srcs = [ + "zstd_dec.x", + "zstd_dec_test.x", + "zstd_frame_testcases.x", + ], tags = ["manual"], + deps = zstd_dec_deps, ) zstd_dec_internal_codegen_args = common_codegen_args | { diff --git a/xls/modules/zstd/math.x b/xls/modules/zstd/math.x new file mode 100644 index 0000000000..1b9a8dd1db --- /dev/null +++ b/xls/modules/zstd/math.x @@ -0,0 +1,88 @@ +// 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; + +fn fast_if(cond: bool, arg1: uN[N], arg2: uN[N]) -> uN[N] { + let mask = if cond { !bits[N]:0 } else { bits[N]:0 }; + (arg1 & mask) | (arg2 & !mask) +} + +#[test] +fn fast_if_test() { + assert_eq(if true { u32:1 } else { u32:5 }, fast_if(true, u32:1, u32:5)); + assert_eq(if false { u32:1 } else { u32:5 }, fast_if(false, u32:1, u32:5)); +} + +// Log-depth shift bits left +pub fn logshiftl(n: bits[N], r: bits[R]) -> bits[N] { + for (i, y) in u32:0..R { + fast_if(r[i+:u1], { y << (bits[R]:1 << i) }, { y }) + }(n as bits[N]) +} + +#[test] +fn logshiftl_test() { + // Test varying base + assert_eq(logshiftl(bits[64]:0, bits[6]:3), bits[64]:0 << u32:3); + assert_eq(logshiftl(bits[64]:1, bits[6]:3), bits[64]:1 << u32:3); + assert_eq(logshiftl(bits[64]:2, bits[6]:3), bits[64]:2 << u32:3); + assert_eq(logshiftl(bits[64]:3, bits[6]:3), bits[64]:3 << u32:3); + assert_eq(logshiftl(bits[64]:4, bits[6]:3), bits[64]:4 << u32:3); + + // Test varying exponent + assert_eq(logshiftl(bits[64]:50, bits[6]:0), bits[64]:50 << u32:0); + assert_eq(logshiftl(bits[64]:50, bits[6]:1), bits[64]:50 << u32:1); + assert_eq(logshiftl(bits[64]:50, bits[6]:2), bits[64]:50 << u32:2); + assert_eq(logshiftl(bits[64]:50, bits[6]:3), bits[64]:50 << u32:3); + assert_eq(logshiftl(bits[64]:50, bits[6]:4), bits[64]:50 << u32:4); + + // Test overflow + let max = std::unsigned_max_value(); + assert_eq(logshiftl(max, u4:4), max << u4:4); + assert_eq(logshiftl(max, u4:5), max << u4:5); + assert_eq(logshiftl(max, u4:15), max << u4:15); + assert_eq(logshiftl(bits[24]:0xc0ffee, u8:12), bits[24]:0xfee000); +} + +// Log-depth shift bits right +pub fn logshiftr(n: bits[N], r: bits[R]) -> bits[N] { + for (i, y) in u32:0..R { + fast_if(r[i+:u1], { y >> (bits[R]:1 << i) }, { y }) + }(n as bits[N]) +} + +#[test] +fn logshiftr_test() { + // Test varying base + assert_eq(logshiftr(bits[64]:0x0fac4e782, bits[6]:3), bits[64]:0x0fac4e782 >> u32:3); + assert_eq(logshiftr(bits[64]:0x1fac4e782, bits[6]:3), bits[64]:0x1fac4e782 >> u32:3); + assert_eq(logshiftr(bits[64]:0x2fac4e782, bits[6]:3), bits[64]:0x2fac4e782 >> u32:3); + assert_eq(logshiftr(bits[64]:0x3fac4e782, bits[6]:3), bits[64]:0x3fac4e782 >> u32:3); + assert_eq(logshiftr(bits[64]:0x4fac4e782, bits[6]:3), bits[64]:0x4fac4e782 >> u32:3); + + // Test varying exponent + assert_eq(logshiftr(bits[64]:0x50fac4e782, bits[6]:0), bits[64]:0x50fac4e782 >> u32:0); + assert_eq(logshiftr(bits[64]:0x50fac4e782, bits[6]:1), bits[64]:0x50fac4e782 >> u32:1); + assert_eq(logshiftr(bits[64]:0x50fac4e782, bits[6]:2), bits[64]:0x50fac4e782 >> u32:2); + assert_eq(logshiftr(bits[64]:0x50fac4e782, bits[6]:3), bits[64]:0x50fac4e782 >> u32:3); + assert_eq(logshiftr(bits[64]:0x50fac4e782, bits[6]:4), bits[64]:0x50fac4e782 >> u32:4); + + // Test overflow + let max = std::unsigned_max_value(); + assert_eq(logshiftr(max, u4:4), max >> u4:4); + assert_eq(logshiftr(max, u4:5), max >> u4:5); + assert_eq(logshiftr(max, u4:15), max >> u4:15); + assert_eq(logshiftr(bits[24]:0xc0ffee, u8:12), bits[24]:0x000c0f); +} diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x new file mode 100644 index 0000000000..a166e1743d --- /dev/null +++ b/xls/modules/zstd/zstd_dec_test.x @@ -0,0 +1,367 @@ +// 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; +import xls.modules.zstd.common; +import xls.modules.zstd.memory.axi; +import xls.modules.zstd.csr_config; +import xls.modules.zstd.sequence_executor; +import xls.modules.zstd.zstd_frame_testcases; +import xls.modules.zstd.memory.axi_ram; +import xls.modules.zstd.zstd_dec; + +const TEST_WINDOW_LOG_MAX = u32:30; + +const TEST_AXI_DATA_W = u32:64; +const TEST_AXI_ADDR_W = u32:32; +const TEST_AXI_ID_W = u32:8; +const TEST_AXI_DEST_W = u32:8; +const TEST_AXI_DATA_W_DIV8 = TEST_AXI_DATA_W / u32:8; + +const TEST_REGS_N = u32:5; +const TEST_LOG2_REGS_N = std::clog2(TEST_REGS_N); + +const TEST_HB_RAM_N = u32:8; +const TEST_HB_ADDR_W = sequence_executor::ZSTD_RAM_ADDR_WIDTH; +const TEST_HB_DATA_W = sequence_executor::RAM_DATA_WIDTH; +const TEST_HB_NUM_PARTITIONS = sequence_executor::RAM_NUM_PARTITIONS; +const TEST_HB_SIZE_KB = sequence_executor::ZSTD_HISTORY_BUFFER_SIZE_KB; +const TEST_HB_RAM_SIZE = sequence_executor::ZSTD_RAM_SIZE; +const TEST_HB_RAM_WORD_PARTITION_SIZE = sequence_executor::RAM_WORD_PARTITION_SIZE; +const TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = sequence_executor::TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR; +const TEST_HB_RAM_INITIALIZED = sequence_executor::TEST_RAM_INITIALIZED; +const TEST_HB_RAM_ASSERT_VALID_READ:bool = false; + +const TEST_RAM_DATA_W:u32 = TEST_AXI_DATA_W; +const TEST_RAM_SIZE:u32 = u32:16384; +const TEST_RAM_ADDR_W:u32 = std::clog2(TEST_RAM_SIZE); +const TEST_RAM_WORD_PARTITION_SIZE:u32 = u32:8; +const TEST_RAM_NUM_PARTITIONS:u32 = ram::num_partitions(TEST_RAM_WORD_PARTITION_SIZE, TEST_RAM_DATA_W); +const TEST_RAM_BASE_ADDR:u32 = u32:0; +const TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_RAM_INITIALIZED = true; + +fn csr_addr(c: zstd_dec::Csr) -> uN[TEST_AXI_ADDR_W] { + (c as uN[TEST_AXI_ADDR_W]) << 3 +} + +#[test_proc] +proc ZstdDecoderTest { + type CsrAxiAr = axi::AxiAr; + type CsrAxiR = axi::AxiR; + type CsrAxiAw = axi::AxiAw; + type CsrAxiW = axi::AxiW; + type CsrAxiB = axi::AxiB; + + type CsrRdReq = csr_config::CsrRdReq; + type CsrRdResp = csr_config::CsrRdResp; + type CsrWrReq = csr_config::CsrWrReq; + type CsrWrResp = csr_config::CsrWrResp; + type CsrChange = csr_config::CsrChange; + + type MemAxiAr = axi::AxiAr; + type MemAxiR = axi::AxiR; + type MemAxiAw = axi::AxiAw; + type MemAxiW = axi::AxiW; + type MemAxiB = axi::AxiB; + + type RamRdReqHB = ram::ReadReq; + type RamRdRespHB = ram::ReadResp; + type RamWrReqHB = ram::WriteReq; + type RamWrRespHB = ram::WriteResp; + + type RamRdReq = ram::ReadReq; + type RamRdResp = ram::ReadResp; + type RamWrReq = ram::WriteReq; + type RamWrResp = ram::WriteResp; + + type ZstdDecodedPacket = common::ZstdDecodedPacket; + terminator: chan out; + csr_axi_aw_s: chan out; + csr_axi_w_s: chan out; + csr_axi_b_r: chan in; + csr_axi_ar_s: chan out; + csr_axi_r_r: chan in; + fh_axi_ar_r: chan in; + fh_axi_r_s: chan out; + bh_axi_ar_r: chan in; + bh_axi_r_s: chan out; + raw_axi_ar_r: chan in; + raw_axi_r_s: chan out; + + ram_rd_req_r: chan[8] in; + ram_rd_resp_s: chan[8] out; + ram_wr_req_r: chan[8] in; + ram_wr_resp_s: chan[8] out; + + ram_wr_req_fh_s: chan out; + ram_wr_req_bh_s: chan out; + ram_wr_req_raw_s: chan out; + raw_wr_resp_fh_r: chan in; + raw_wr_resp_bh_r: chan in; + raw_wr_resp_raw_r: chan in; + + output_r: chan in; + notify_r: chan<()> in; + reset_r: chan<()> in; + + init {} + + config(terminator: chan out) { + + let (csr_axi_aw_s, csr_axi_aw_r) = chan("csr_axi_aw"); + let (csr_axi_w_s, csr_axi_w_r) = chan("csr_axi_w"); + let (csr_axi_b_s, csr_axi_b_r) = chan("csr_axi_b"); + let (csr_axi_ar_s, csr_axi_ar_r) = chan("csr_axi_ar"); + let (csr_axi_r_s, csr_axi_r_r) = chan("csr_axi_r"); + + let (fh_axi_ar_s, fh_axi_ar_r) = chan("fh_axi_ar"); + let (fh_axi_r_s, fh_axi_r_r) = chan("fh_axi_r"); + + let (bh_axi_ar_s, bh_axi_ar_r) = chan("bh_axi_ar"); + let (bh_axi_r_s, bh_axi_r_r) = chan("bh_axi_r"); + + let (raw_axi_ar_s, raw_axi_ar_r) = chan("raw_axi_ar"); + let (raw_axi_r_s, raw_axi_r_r) = chan("raw_axi_r"); + + let (ram_rd_req_s, ram_rd_req_r) = chan[8]("ram_rd_req"); + let (ram_rd_resp_s, ram_rd_resp_r) = chan[8]("ram_rd_resp"); + let (ram_wr_req_s, ram_wr_req_r) = chan[8]("ram_wr_req"); + let (ram_wr_resp_s, ram_wr_resp_r) = chan[8]("ram_wr_resp"); + + let (ram_rd_req_fh_s, ram_rd_req_fh_r) = chan("ram_rd_req_fh"); + let (ram_rd_req_bh_s, ram_rd_req_bh_r) = chan("ram_rd_req_bh"); + let (ram_rd_req_raw_s, ram_rd_req_raw_r) = chan("ram_rd_req_raw"); + let (ram_rd_resp_fh_s, ram_rd_resp_fh_r) = chan("ram_rd_resp_fh"); + let (ram_rd_resp_bh_s, ram_rd_resp_bh_r) = chan("ram_rd_resp_bh"); + let (ram_rd_resp_raw_s, ram_rd_resp_raw_r) = chan("ram_rd_resp_raw"); + + let (ram_wr_req_fh_s, ram_wr_req_fh_r) = chan("ram_wr_req_fh"); + let (ram_wr_req_bh_s, ram_wr_req_bh_r) = chan("ram_wr_req_bh"); + let (ram_wr_req_raw_s, ram_wr_req_raw_r) = chan("ram_wr_req_raw"); + let (ram_wr_resp_fh_s, ram_wr_resp_fh_r) = chan("ram_wr_resp_fh"); + let (ram_wr_resp_bh_s, ram_wr_resp_bh_r) = chan("ram_wr_resp_bh"); + let (ram_wr_resp_raw_s, ram_wr_resp_raw_r) = chan("ram_wr_resp_raw"); + + let (output_s, output_r) = chan("output"); + let (notify_s, notify_r) = chan<()>("notify"); + let (reset_s, reset_r) = chan<()>("reset"); + + spawn zstd_dec::ZstdDecoder< + TEST_AXI_DATA_W, TEST_AXI_ADDR_W, TEST_AXI_ID_W, TEST_AXI_DEST_W, + TEST_REGS_N, TEST_WINDOW_LOG_MAX, + TEST_HB_ADDR_W, TEST_HB_DATA_W, TEST_HB_NUM_PARTITIONS, TEST_HB_SIZE_KB, + >( + csr_axi_aw_r, csr_axi_w_r, csr_axi_b_s, csr_axi_ar_r, csr_axi_r_s, + fh_axi_ar_s, fh_axi_r_r, + bh_axi_ar_s, bh_axi_r_r, + raw_axi_ar_s, raw_axi_r_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], + 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], + output_s, notify_s, reset_s, + ); + + spawn ram::RamModel< + TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, + TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, + TEST_HB_RAM_ASSERT_VALID_READ + > (ram_rd_req_r[0], ram_rd_resp_s[0], ram_wr_req_r[0], ram_wr_resp_s[0]); + + spawn ram::RamModel< + TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, + TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, + TEST_HB_RAM_ASSERT_VALID_READ + > (ram_rd_req_r[1], ram_rd_resp_s[1], ram_wr_req_r[1], ram_wr_resp_s[1]); + + spawn ram::RamModel< + TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, + TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, + TEST_HB_RAM_ASSERT_VALID_READ + > (ram_rd_req_r[2], ram_rd_resp_s[2], ram_wr_req_r[2], ram_wr_resp_s[2]); + + spawn ram::RamModel< + TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, + TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, + TEST_HB_RAM_ASSERT_VALID_READ + > (ram_rd_req_r[3], ram_rd_resp_s[3], ram_wr_req_r[3], ram_wr_resp_s[3]); + + spawn ram::RamModel< + TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, + TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, + TEST_HB_RAM_ASSERT_VALID_READ + > (ram_rd_req_r[4], ram_rd_resp_s[4], ram_wr_req_r[4], ram_wr_resp_s[4]); + + spawn ram::RamModel< + TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, + TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, + TEST_HB_RAM_ASSERT_VALID_READ + > (ram_rd_req_r[5], ram_rd_resp_s[5], ram_wr_req_r[5], ram_wr_resp_s[5]); + + spawn ram::RamModel< + TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, + TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, + TEST_HB_RAM_ASSERT_VALID_READ + > (ram_rd_req_r[6], ram_rd_resp_s[6], ram_wr_req_r[6], ram_wr_resp_s[6]); + + spawn ram::RamModel< + TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, + TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, + TEST_HB_RAM_ASSERT_VALID_READ + > (ram_rd_req_r[7], ram_rd_resp_s[7], ram_wr_req_r[7], ram_wr_resp_s[7]); + + spawn ram::RamModel< + TEST_RAM_DATA_W, TEST_RAM_SIZE, TEST_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, + > (ram_rd_req_fh_r, ram_rd_resp_fh_s, ram_wr_req_fh_r, ram_wr_resp_fh_s); + + spawn ram::RamModel< + TEST_RAM_DATA_W, TEST_RAM_SIZE, TEST_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, + > (ram_rd_req_bh_r, ram_rd_resp_bh_s, ram_wr_req_bh_r, ram_wr_resp_bh_s); + + spawn ram::RamModel< + TEST_RAM_DATA_W, TEST_RAM_SIZE, TEST_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, + > (ram_rd_req_raw_r, ram_rd_resp_raw_s, ram_wr_req_raw_r, ram_wr_resp_raw_s); + + spawn axi_ram::AxiRamReader< + TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, + TEST_RAM_SIZE, TEST_RAM_BASE_ADDR, TEST_RAM_DATA_W, TEST_RAM_ADDR_W, + >(fh_axi_ar_r, fh_axi_r_s, ram_rd_req_fh_s, ram_rd_resp_fh_r); + + spawn axi_ram::AxiRamReader< + TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, + TEST_RAM_SIZE, TEST_RAM_BASE_ADDR, TEST_RAM_DATA_W, TEST_RAM_ADDR_W, + >(bh_axi_ar_r, bh_axi_r_s, ram_rd_req_bh_s, ram_rd_resp_bh_r); + + spawn axi_ram::AxiRamReader< + TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, + TEST_RAM_SIZE, TEST_RAM_BASE_ADDR, TEST_RAM_DATA_W, TEST_RAM_ADDR_W, + >(raw_axi_ar_r, raw_axi_r_s, ram_rd_req_raw_s, ram_rd_resp_raw_r); + + ( + terminator, + csr_axi_aw_s, csr_axi_w_s, csr_axi_b_r, csr_axi_ar_s, csr_axi_r_r, + fh_axi_ar_r, fh_axi_r_s, + bh_axi_ar_r, bh_axi_r_s, + raw_axi_ar_r, raw_axi_r_s, + ram_rd_req_r, ram_rd_resp_s, ram_wr_req_r, ram_wr_resp_s, + ram_wr_req_fh_s, ram_wr_req_bh_s, ram_wr_req_raw_s, + ram_wr_resp_fh_r, ram_wr_resp_bh_r, ram_wr_resp_raw_r, + output_r, notify_r, reset_r, + ) + } + + next (state: ()) { + trace_fmt!("Test start"); + let frames_count = array_size(zstd_frame_testcases::FRAMES); + + let tok = join(); + let tok = unroll_for! (test_i, tok): (u32, token) in range(u32:0, frames_count) { + trace_fmt!("Loading testcase {:x}", test_i); + let frame = zstd_frame_testcases::FRAMES[test_i]; + let tok = for (i, tok): (u32, token) in range(u32:0, frame.array_length) { + let req = RamWrReq { + addr: i as uN[TEST_RAM_ADDR_W], + data: frame.data[i] as uN[TEST_RAM_DATA_W], + mask: uN[TEST_RAM_NUM_PARTITIONS]:0xFF + }; + let tok = send(tok, ram_wr_req_fh_s, req); + let tok = send(tok, ram_wr_req_bh_s, req); + let tok = send(tok, ram_wr_req_raw_s, req); + tok + }(tok); + + trace_fmt!("Running decoder on testcase {:x}", test_i); + let addr_req = axi::AxiAw { + id: uN[TEST_AXI_ID_W]:0, + addr: uN[TEST_AXI_ADDR_W]:0, + size: axi::AxiAxSize::MAX_4B_TRANSFER, + len: u8:0, + burst: axi::AxiAxBurst::FIXED, + }; + let data_req = axi::AxiW { + data: uN[TEST_AXI_DATA_W]:0, + strb: uN[TEST_AXI_DATA_W_DIV8]:0xFF, + last: u1:1, + }; + + // reset the decoder + trace_fmt!("Sending reset"); + let tok = send(tok, csr_axi_aw_s, axi::AxiAw { + addr: csr_addr(zstd_dec::Csr::RESET), + ..addr_req + }); + let tok = send(tok, csr_axi_w_s, axi::AxiW { + data: uN[TEST_AXI_DATA_W]:0x1, + ..data_req + }); + trace_fmt!("Sent reset"); + let (tok, _) = recv(tok, csr_axi_b_r); + // Wait for reset notification before issuing further CSR writes + let (tok, _) = recv(tok, reset_r); + // configure input buffer address + let tok = send(tok, csr_axi_aw_s, axi::AxiAw { + addr: csr_addr(zstd_dec::Csr::INPUT_BUFFER), + ..addr_req + }); + let tok = send(tok, csr_axi_w_s, axi::AxiW { + data: uN[TEST_AXI_DATA_W]:0x0, + ..data_req + }); + let (tok, _) = recv(tok, csr_axi_b_r); + // configure output buffer address + let tok = send(tok, csr_axi_aw_s, axi::AxiAw { + addr: csr_addr(zstd_dec::Csr::OUTPUT_BUFFER), + ..addr_req + }); + let tok = send(tok, csr_axi_w_s, axi::AxiW { + data: uN[TEST_AXI_DATA_W]:0x1000, + ..data_req + }); + let (tok, _) = recv(tok, csr_axi_b_r); + // start decoder + let tok = send(tok, csr_axi_aw_s, axi::AxiAw { + addr: csr_addr(zstd_dec::Csr::START), + ..addr_req + }); + let tok = send(tok, csr_axi_w_s, axi::AxiW { + data: uN[TEST_AXI_DATA_W]:0x1, + ..data_req + }); + let (tok, _) = recv(tok, csr_axi_b_r); + + let decomp_frame = zstd_frame_testcases::DECOMPRESSED_FRAMES[test_i]; + let tok = for (decomp_i, tok): (u32, token) in range(u32:0, decomp_frame.array_length) { + let (tok, decomp_packet) = recv(tok, output_r); + assert_eq(decomp_packet.data, decomp_frame.data[decomp_i]); + assert_eq(decomp_packet.last, decomp_i == (decomp_frame.length - u32:1) / u32:8); + tok + }(tok); + + let (tok, ()) = recv(tok, notify_r); + trace_fmt!("Finished decoding testcase {:x} correctly", test_i); + tok + }(tok); + send(tok, terminator, true); + } +} + From a979e29ba0b792ac38261efd5c32ff4143286f13 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 22 Oct 2024 15:38:22 +0200 Subject: [PATCH 19/85] modules/zstd/zstd_dec: handle contents of the Status CSR Internal-tag: [#66955] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/zstd_dec.x | 85 +++++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 17 deletions(-) diff --git a/xls/modules/zstd/zstd_dec.x b/xls/modules/zstd/zstd_dec.x index be58763dd3..528d16d599 100644 --- a/xls/modules/zstd/zstd_dec.x +++ b/xls/modules/zstd/zstd_dec.x @@ -50,12 +50,19 @@ enum ZstdDecoderInternalFsm: u4 { INVALID = 15, } -enum ZstdDecoderStatus: u3 { - OKAY = 0, - FINISHED = 1, - FRAME_HEADER_CORRUPTED = 2, - FRAME_HEADER_UNSUPPORTED_WINDOW_SIZE = 3, - BLOCK_HEADER_CORRUPTED = 4, +enum ZstdDecoderStatus: u5 { + IDLE = 0, + RUNNING = 1, + READ_CONFIG_OK = 2, + FRAME_HEADER_OK = 3, + FRAME_HEADER_CORRUPTED = 4, + FRAME_HEADER_UNSUPPORTED_WINDOW_SIZE = 5, + BLOCK_HEADER_OK = 6, + BLOCK_HEADER_CORRUPTED = 7, + BLOCK_HEADER_MEMORY_ACCESS_ERROR = 8, + RAW_BLOCK_OK = 9, + RAW_BLOCK_ERROR = 10, + RLE_BLOCK_OK = 11, } pub enum Csr: u3 { @@ -317,7 +324,15 @@ proc ZstdDecoderInternal< Fsm::IDLE => { trace_fmt!("[IDLE]"); if is_start { - State { fsm: Fsm::READ_CONFIG, conf_cnt: CSR_REQS_MAX, ..zero!() } + let status = ZstdDecoderStatus::RUNNING; + + let csr_wr_req_valid = true; + let csr_wr_req = CsrWrReq { + csr: csr(Csr::STATUS), + value: checked_cast(status), + }; + + State { fsm: Fsm::READ_CONFIG, csr_wr_req, csr_wr_req_valid, conf_cnt: CSR_REQS_MAX, ..zero!() } } else { zero!() } }, @@ -337,8 +352,19 @@ proc ZstdDecoderInternal< let conf_send = (state.conf_cnt == Reg:0); let conf_cnt = if conf_send { Reg:0 } else {state.conf_cnt - Reg:1}; + let status = match(all_collected) { + true => ZstdDecoderStatus::READ_CONFIG_OK, + _ => ZstdDecoderStatus::RUNNING, + }; + + let csr_wr_req_valid = all_collected; + let csr_wr_req = CsrWrReq { + csr: csr(Csr::STATUS), + value: checked_cast(status), + }; + State { - fsm, conf_cnt, conf_send, input_buffer, input_buffer_valid, output_buffer, output_buffer_valid, + fsm, csr_wr_req, csr_wr_req_valid, conf_cnt, conf_send, input_buffer, input_buffer_valid, output_buffer, output_buffer_valid, ..zero!() } }, @@ -347,10 +373,17 @@ proc ZstdDecoderInternal< trace_fmt!("[DECODE_FRAME_HEADER]"); let error = (fh_resp.status != FrameHeaderDecoderStatus::OKAY); - let csr_wr_req_valid = (fh_resp_valid && error); + let status = match(fh_resp_valid, fh_resp.status) { + (true, FrameHeaderDecoderStatus::OKAY) => ZstdDecoderStatus::FRAME_HEADER_OK, + (true, FrameHeaderDecoderStatus::CORRUPTED) => ZstdDecoderStatus::FRAME_HEADER_CORRUPTED, + (true, FrameHeaderDecoderStatus::UNSUPPORTED_WINDOW_SIZE) => ZstdDecoderStatus::FRAME_HEADER_UNSUPPORTED_WINDOW_SIZE, + (_, _) => ZstdDecoderStatus::RUNNING, + }; + + let csr_wr_req_valid = (fh_resp_valid); let csr_wr_req = CsrWrReq { csr: csr(Csr::STATUS), - value: checked_cast(fh_resp.status), + value: checked_cast(status), }; let fsm = match (fh_resp_valid, error) { @@ -368,10 +401,17 @@ proc ZstdDecoderInternal< trace_fmt!("[DECODE_BLOCK_HEADER]"); let error = (bh_resp.status != BlockHeaderDecoderStatus::OKAY); - let csr_wr_req_valid = (bh_resp_valid && error); + let status = match(bh_resp_valid, bh_resp.status) { + (true, BlockHeaderDecoderStatus::OKAY) => ZstdDecoderStatus::BLOCK_HEADER_OK, + (true, BlockHeaderDecoderStatus::CORRUPTED) => ZstdDecoderStatus::BLOCK_HEADER_CORRUPTED, + (true, BlockHeaderDecoderStatus::MEMORY_ACCESS_ERROR) => ZstdDecoderStatus::BLOCK_HEADER_MEMORY_ACCESS_ERROR, + (_, _) => ZstdDecoderStatus::RUNNING, + }; + + let csr_wr_req_valid = (bh_resp_valid); let csr_wr_req = CsrWrReq { csr: csr(Csr::STATUS), - value: checked_cast(bh_resp.status), + value: checked_cast(status), }; let fsm = match (bh_resp_valid, error, bh_resp.header.btype) { @@ -413,10 +453,16 @@ proc ZstdDecoderInternal< let error = (raw_resp.status != RawBlockDecoderStatus::OKAY); - let csr_wr_req_valid = (raw_resp_valid && error); + let status = match(raw_resp_valid, raw_resp.status) { + (true, RawBlockDecoderStatus::OKAY) => ZstdDecoderStatus::RAW_BLOCK_OK, + (true, RawBlockDecoderStatus::ERROR) => ZstdDecoderStatus::RAW_BLOCK_ERROR, + (_, _) => ZstdDecoderStatus::RUNNING, + }; + + let csr_wr_req_valid = (raw_resp_valid); let csr_wr_req = CsrWrReq { csr: csr(Csr::STATUS), - value: checked_cast(raw_resp.status), + value: checked_cast(status), }; let fsm = match (raw_resp_valid, error, state.block_last) { @@ -441,10 +487,15 @@ proc ZstdDecoderInternal< trace_fmt!("[DECODE_RLE_BLOCK]"); let error = (rle_resp.status != RleBlockDecoderStatus::OKAY); - let csr_wr_req_valid = (rle_resp_valid && error); + let status = match(rle_resp_valid, rle_resp.status) { + (true, RleBlockDecoderStatus::OKAY) => ZstdDecoderStatus::RLE_BLOCK_OK, + (_, _) => ZstdDecoderStatus::RUNNING, + }; + + let csr_wr_req_valid = (rle_resp_valid); let csr_wr_req = CsrWrReq { csr: csr(Csr::STATUS), - value: checked_cast(rle_resp.status), + value: checked_cast(status), }; let fsm = match (rle_resp_valid, error, state.block_last) { @@ -481,7 +532,7 @@ proc ZstdDecoderInternal< let csr_wr_req_valid = true; let csr_wr_req = CsrWrReq { csr: csr(Csr::STATUS), - value: checked_cast(fh_resp.status), + value: checked_cast(ZstdDecoderStatus::IDLE), }; State { fsm: Fsm::IDLE, csr_wr_req, csr_wr_req_valid, ..zero!() } From 7560da5a8ffe49b03c3281ca28d3b207994bee02 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Mon, 18 Nov 2024 14:04:39 +0100 Subject: [PATCH 20/85] modules/zstd/data_generator: fix formatting Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/data_generator.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/xls/modules/zstd/data_generator.cc b/xls/modules/zstd/data_generator.cc index 81ffe95ed9..98b1eb4dba 100644 --- a/xls/modules/zstd/data_generator.cc +++ b/xls/modules/zstd/data_generator.cc @@ -60,9 +60,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/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()); From 09ea65b98658fe82f21d070db4c8058c1e8b5341 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 29 Oct 2024 16:47:45 +0100 Subject: [PATCH 21/85] modules/zstd/memory:axi_stream_remove_empty: Fix byte ordering * Fix byte ordering when receiving a series of non-empty packets * Adjust MemReader DSLX tests Internal-tag: [#67272] Signed-off-by: Pawel Czarnecki --- .../zstd/memory/axi_stream_remove_empty.x | 64 ++++++++++++++++++- xls/modules/zstd/memory/mem_reader.x | 9 ++- 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/xls/modules/zstd/memory/axi_stream_remove_empty.x b/xls/modules/zstd/memory/axi_stream_remove_empty.x index a1b48f37a7..1c295159ba 100644 --- a/xls/modules/zstd/memory/axi_stream_remove_empty.x +++ b/xls/modules/zstd/memory/axi_stream_remove_empty.x @@ -130,7 +130,6 @@ pub proc AxiStreamRemoveEmpty< let exact_transfer = (empty_input_bytes == state.len); let combined_state_data = state.data | data << state.len; - let combined_input_data = data | state.data << len; let overflow_len = get_overflow_len(state.len, len); let sum_len = state.len + len; @@ -172,7 +171,7 @@ pub proc AxiStreamRemoveEmpty< // store ( State { - data: combined_input_data, + data: combined_state_data, len: sum_len, ..state }, @@ -405,6 +404,67 @@ proc AxiStreamRemoveEmptyTest { dest: Dest:0, }); + // Test 6: Some bits set, last set in the last transfer. + + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0x0000_00B9, + str: Str:0b0001, + keep: Keep:0b0001, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0x0000_007F, + str: Str:0b0001, + keep: Keep:0b0001, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0x0000_0069, + str: Str:0b0001, + keep: Keep:0b0001, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0x00DF_5EF7, + str: Str:0b0111, + keep: Keep:0b0111, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0x0000_C735, + str: Str:0b0011, + keep: Keep:0b0011, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0xF769_7FB9, + str: Str:0xF, + keep: Keep:0xF, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0xC735_DF5E, + str: Str:0xF, + keep: Keep:0xF, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); send(tok, terminator, true); } } diff --git a/xls/modules/zstd/memory/mem_reader.x b/xls/modules/zstd/memory/mem_reader.x index c0d6c5709d..f96257663c 100644 --- a/xls/modules/zstd/memory/mem_reader.x +++ b/xls/modules/zstd/memory/mem_reader.x @@ -579,7 +579,8 @@ proc MemReaderTest { let tok = send(tok, axi_r_s, AxiR { id: AxiId:0x0, - data: AxiData:0x1122_3344_5566_7788_9900_AABB_CCDD_EEFF, + data: AxiData:0x1122_3344_5566_7788_9900_AABB_CCDD_EE55, + // Addresses: ^ 0xFFF ^ 0xFF0 resp: AxiResp::OKAY, last: AxiLast:true }); @@ -599,7 +600,8 @@ proc MemReaderTest { let tok = send(tok, axi_r_s, AxiR { id: AxiId:0x0, - data: AxiData:0x1122_3344_5566_7788_9900_AABB_CCDD_EEFF, + data: AxiData:0x5522_3344_5566_7788_9900_AABB_CCDD_EEFF, + // Addresses: ^ 0x100F ^ 0x1000 resp: AxiResp::OKAY, last: AxiLast:true }); @@ -607,7 +609,8 @@ proc MemReaderTest { let (tok, resp) = recv(tok, resp_r); assert_eq(resp, Resp { status: Status::OKAY, - data: Data:0x11FF, + data: Data:0xFF11, + // 0x1000 ^ ^ 0x0FFF length: Length:2, last: true }); From ed2120036799b7fefc971f501ddf5d0878229103 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Mon, 4 Nov 2024 16:22:34 +0100 Subject: [PATCH 22/85] modules/zstd/memory/axi_stream_remove_empty: Extract remove_empty_bytes function into a separate proc * Extract the operation of removing not-strobed bytes from input frames to a separate proc * Extract control logic to AxiStreamRemoveEmptyInternal proc * Optimize strobe calculation Internal-tag: [#67272] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/memory/BUILD | 139 ++++- .../zstd/memory/axi_stream_remove_empty.x | 555 ++++++++++++++++-- 2 files changed, 629 insertions(+), 65 deletions(-) diff --git a/xls/modules/zstd/memory/BUILD b/xls/modules/zstd/memory/BUILD index 96e824b19c..2368579c26 100644 --- a/xls/modules/zstd/memory/BUILD +++ b/xls/modules/zstd/memory/BUILD @@ -58,8 +58,6 @@ CLOCK_PERIOD_PS = "750" # Clock periods for modules that exceed the 750ps critical path in IR benchmark AXI_READER_CLOCK_PERIOD_PS = "1800" -AXI_STREAM_REMOVE_EMPTY_CLOCK_PERIOD_PS = "1300" - MEM_READER_CLOCK_PERIOD_PS = "2600" common_codegen_args = { @@ -166,9 +164,68 @@ xls_dslx_test( tags = ["manual"], ) +axi_stream_remove_empty_internal_codegen_args = common_codegen_args | { + "module_name": "axi_stream_remove_empty_internal", + "pipeline_stages": "1", +} + +xls_dslx_verilog( + name = "axi_stream_remove_empty_internal_verilog", + codegen_args = axi_stream_remove_empty_internal_codegen_args, + dslx_top = "AxiStreamRemoveEmptyInternalInst", + library = ":axi_stream_remove_empty_dslx", + tags = ["manual"], + verilog_file = "axi_stream_remove_empty_internal.v", +) + +xls_benchmark_ir( + name = "axi_stream_remove_empty_internal_opt_ir_benchmark", + src = ":axi_stream_remove_empty_internal_verilog.opt.ir", + benchmark_ir_args = axi_stream_remove_empty_internal_codegen_args | { + "pipeline_stages": "10", + "top": "__axi_stream_remove_empty__AxiStreamRemoveEmptyInternalInst__AxiStreamRemoveEmptyInternal_0__32_4_6_32_32_next", + }, + tags = ["manual"], +) + +verilog_library( + name = "axi_stream_remove_empty_internal_verilog_lib", + srcs = [ + ":axi_stream_remove_empty_internal.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "axi_stream_remove_empty_internal_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "axi_stream_remove_empty_internal", + deps = [ + ":axi_stream_remove_empty_internal_verilog_lib", + ], +) + +benchmark_synth( + name = "axi_stream_remove_empty_internal_benchmark_synth", + synth_target = ":axi_stream_remove_empty_internal_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "axi_stream_remove_empty_internal_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":axi_stream_remove_empty_internal_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + axi_stream_remove_empty_codegen_args = common_codegen_args | { "module_name": "axi_stream_remove_empty", - "clock_period_ps": AXI_STREAM_REMOVE_EMPTY_CLOCK_PERIOD_PS, "pipeline_stages": "2", } @@ -181,16 +238,6 @@ xls_dslx_verilog( verilog_file = "axi_stream_remove_empty.v", ) -xls_benchmark_ir( - name = "axi_stream_remove_empty_opt_ir_benchmark", - src = ":axi_stream_remove_empty_verilog.opt.ir", - benchmark_ir_args = axi_stream_remove_empty_codegen_args | { - "pipeline_stages": "10", - "top": "__axi_stream_remove_empty__AxiStreamRemoveEmptyInst__AxiStreamRemoveEmpty_0__32_4_6_32_32_next", - }, - tags = ["manual"], -) - verilog_library( name = "axi_stream_remove_empty_verilog_lib", srcs = [ @@ -227,6 +274,66 @@ place_and_route( target_die_utilization_percentage = "10", ) +remove_empty_bytes_codegen_args = common_codegen_args | { + "module_name": "remove_empty_bytes", + "pipeline_stages": "2", +} + +xls_dslx_verilog( + name = "remove_empty_bytes_verilog", + codegen_args = remove_empty_bytes_codegen_args, + dslx_top = "RemoveEmptyBytesInst", + library = ":axi_stream_remove_empty_dslx", + tags = ["manual"], + verilog_file = "remove_empty_bytes.v", +) + +xls_benchmark_ir( + name = "remove_empty_bytes_opt_ir_benchmark", + src = ":remove_empty_bytes_verilog.opt.ir", + benchmark_ir_args = remove_empty_bytes_codegen_args | { + "top": "__axi_stream_remove_empty__RemoveEmptyBytesInst__RemoveEmptyBytes_0__32_4_6_32_9_32_next", + "pipeline_stages": "10", + }, + tags = ["manual"], +) + +verilog_library( + name = "remove_empty_bytes_verilog_lib", + srcs = [ + ":remove_empty_bytes.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "remove_empty_bytes_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "remove_empty_bytes", + deps = [ + ":remove_empty_bytes_verilog_lib", + ], +) + +benchmark_synth( + name = "remove_empty_bytes_benchmark_synth", + synth_target = ":remove_empty_bytes_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "remove_empty_bytes_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":remove_empty_bytes_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + xls_dslx_library( name = "axi_stream_downscaler_dslx", srcs = ["axi_stream_downscaler.x"], @@ -468,13 +575,12 @@ place_and_route( ) mem_reader_codegen_args = common_codegen_args | { + "clock_period_ps": MEM_READER_CLOCK_PERIOD_PS, "module_name": "mem_reader", "pipeline_stages": "4", "streaming_channel_data_suffix": "_data", "flop_inputs_kind": "skid", "flop_outputs_kind": "skid", - "clock_period_ps": MEM_READER_CLOCK_PERIOD_PS, - "fifo_module": "", "materialize_internal_fifos": "true", } @@ -524,13 +630,12 @@ place_and_route( ) mem_reader_adv_codegen_args = common_codegen_args | { + "clock_period_ps": MEM_READER_CLOCK_PERIOD_PS, "module_name": "mem_reader_adv", "pipeline_stages": "4", "streaming_channel_data_suffix": "_data", "flop_inputs_kind": "skid", "flop_outputs_kind": "skid", - "clock_period_ps": MEM_READER_CLOCK_PERIOD_PS, - "fifo_module": "", "materialize_internal_fifos": "true", } diff --git a/xls/modules/zstd/memory/axi_stream_remove_empty.x b/xls/modules/zstd/memory/axi_stream_remove_empty.x index 1c295159ba..40c8aa9208 100644 --- a/xls/modules/zstd/memory/axi_stream_remove_empty.x +++ b/xls/modules/zstd/memory/axi_stream_remove_empty.x @@ -29,34 +29,185 @@ struct AxiStreamRemoveEmptyState< dest: uN[DEST_W], } +pub struct ContinuousStream< + DATA_W: u32, + DEST_W: u32, + ID_W: u32, + DATA_W_LOG2: u32 = {std::clog2(DATA_W + u32:1)}, +> { + data: uN[DATA_W], + len: uN[DATA_W_LOG2], + id: uN[ID_W], + dest: uN[DEST_W], + last: u1 +} + +const INST_DATA_W = u32:32; +const INST_DATA_W_DIV8 = INST_DATA_W / u32:8; +const INST_DATA_W_LOG2 = std::clog2(INST_DATA_W + u32:1); +const INST_DEST_W = u32:32; +const INST_ID_W = u32:32; +const TEST_DATA_W = u32:32; +const TEST_DATA_W_DIV8 = TEST_DATA_W / u32:8; +const TEST_DATA_W_LOG2 = std::clog2(TEST_DATA_W + u32:1); +const TEST_DEST_W = u32:32; +const TEST_ID_W = u32:32; // Returns a tuple containing data and length, afer removing non-data // bytes from the in_data varaiable, using information from keep and str fields -fn remove_empty_bytes ( - in_data: uN[DATA_W], keep: uN[DATA_W_DIV8], str: uN[DATA_W_DIV8] -) -> (uN[DATA_W], uN[DATA_W_LOG2]) { - - const EXT_OFFSET_W = DATA_W_LOG2 + u32:3; - +pub proc RemoveEmptyBytes< + DATA_W: u32, DEST_W: u32, ID_W: u32, + DATA_W_DIV8: u32 = {DATA_W / u32:8}, + DATA_W_LOG2: u32 = {std::clog2(DATA_W + u32:1)}, + EXT_OFFSET_W: u32 = {(std::clog2(DATA_W + u32:1)) + u32:3}, +> { type Data = uN[DATA_W]; type Str = uN[DATA_W_DIV8]; - type Keep = uN[DATA_W_DIV8]; type Offset = uN[DATA_W_LOG2]; type OffsetExt = uN[EXT_OFFSET_W]; type Length = uN[DATA_W_LOG2]; - let (data, len, _) = for (i, (data, len, offset)): (u32, (Data, Length, Offset)) in u32:0..DATA_W_DIV8 { - if str[i +: u1] & keep[i +: u1] { - ( - data | (in_data & (Data:0xFF << (u32:8 * i))) >> (OffsetExt:8 * offset as OffsetExt), - len + Length:8, - offset, - ) - } else { - (data, len, offset + Offset:1) - } - }((Data:0, Length:0, Offset:0)); - (data, len) + type AxiStream = axi_st::AxiStream; + type StrobedStream = ContinuousStream; + + stream_r: chan in; + continuous_stream_s: chan out; + + config ( + stream_r: chan in, + continuous_stream_s: chan out, + ) { + (stream_r, continuous_stream_s) + } + + init { () } + + next (state: ()) { + let (tok, frame) = recv(join(), stream_r); + let (in_data, str) = (frame.data, frame.str); + + let (data, len, _) = unroll_for! (i, (data, len, offset)): (u32, (Data, Length, Offset)) in range(u32:0, DATA_W_DIV8) { + if str[i +: u1] { + ( + data | (in_data & (Data:0xFF << (u32:8 * i))) >> (OffsetExt:8 * offset as OffsetExt), + len + Length:8, + offset, + ) + } else { + (data, len, offset + Offset:1) + } + }((Data:0, Length:0, Offset:0)); + + let continuous_stream = StrobedStream { + data: data, + len: len, + id: frame.id, + dest: frame.dest, + last: frame.last, + }; + send(tok, continuous_stream_s, continuous_stream); + } +} + +pub proc RemoveEmptyBytesInst { + type AxiStream = axi_st::AxiStream; + type StrobedStream = ContinuousStream; + + config ( + stream_r: chan in, + continuous_stream_s: chan out, + ) { + spawn RemoveEmptyBytes( + stream_r, continuous_stream_s + ); + } + + init { () } + + next (state: ()) {} +} + +#[test_proc] +proc RemoveEmptyBytesTest { + type TestAxiStream = axi_st::AxiStream; + type TestStrobedStream = ContinuousStream; + terminator: chan out; + stream_s: chan out; + continuous_stream_r: chan in; + + config ( + terminator: chan out, + ) { + let (stream_s, stream_r) = chan("frame_data"); + let (continuous_stream_s, continuous_stream_r) = chan("bare_data"); + + spawn RemoveEmptyBytes( + stream_r, continuous_stream_s + ); + + (terminator, stream_s, continuous_stream_r) + } + + init { } + + next (state: ()) { + type Data = uN[TEST_DATA_W]; + type Str = uN[TEST_DATA_W_DIV8]; + type Id = uN[TEST_ID_W]; + type Dest = uN[TEST_DEST_W]; + type Length = uN[TEST_DATA_W_LOG2]; + + let tok = join(); + + let data = Data:0xDEADBEEF; + let input_data: TestAxiStream[16] = [ + TestAxiStream{data: data, str: Str:0b0000, keep: Str:0b0000, id: Id:0, dest: Dest:0, last: false}, + TestAxiStream{data: data, str: Str:0b0001, keep: Str:0b0001, id: Id:0, dest: Dest:0, last: false}, + TestAxiStream{data: data, str: Str:0b0010, keep: Str:0b0010, id: Id:0, dest: Dest:0, last: false}, + TestAxiStream{data: data, str: Str:0b0011, keep: Str:0b0011, id: Id:0, dest: Dest:0, last: false}, + TestAxiStream{data: data, str: Str:0b0100, keep: Str:0b0100, id: Id:0, dest: Dest:0, last: false}, + TestAxiStream{data: data, str: Str:0b0101, keep: Str:0b0101, id: Id:0, dest: Dest:0, last: false}, + TestAxiStream{data: data, str: Str:0b0110, keep: Str:0b0110, id: Id:0, dest: Dest:0, last: false}, + TestAxiStream{data: data, str: Str:0b0111, keep: Str:0b0111, id: Id:0, dest: Dest:0, last: false}, + TestAxiStream{data: data, str: Str:0b1000, keep: Str:0b1000, id: Id:0, dest: Dest:0, last: false}, + TestAxiStream{data: data, str: Str:0b1001, keep: Str:0b1001, id: Id:0, dest: Dest:0, last: false}, + TestAxiStream{data: data, str: Str:0b1010, keep: Str:0b1010, id: Id:0, dest: Dest:0, last: false}, + TestAxiStream{data: data, str: Str:0b1011, keep: Str:0b1011, id: Id:0, dest: Dest:0, last: false}, + TestAxiStream{data: data, str: Str:0b1100, keep: Str:0b1100, id: Id:0, dest: Dest:0, last: false}, + TestAxiStream{data: data, str: Str:0b1101, keep: Str:0b1101, id: Id:0, dest: Dest:0, last: false}, + TestAxiStream{data: data, str: Str:0b1110, keep: Str:0b1110, id: Id:0, dest: Dest:0, last: false}, + TestAxiStream{data: data, str: Str:0b1111, keep: Str:0b1111, id: Id:0, dest: Dest:0, last: true} + ]; + let expected_output: TestStrobedStream[16] = [ + TestStrobedStream{data: Data:0x00, len: Length:0, id: Id:0, dest: Dest:0, last: false}, + TestStrobedStream{data: Data:0xEF, len: Length:8, id: Id:0, dest: Dest:0, last: false}, + TestStrobedStream{data: Data:0xBE, len: Length:8, id: Id:0, dest: Dest:0, last: false}, + TestStrobedStream{data: Data:0xBEEF, len: Length:16, id: Id:0, dest: Dest:0, last: false}, + TestStrobedStream{data: Data:0xAD, len: Length:8, id: Id:0, dest: Dest:0, last: false}, + TestStrobedStream{data: Data:0xADEF, len: Length:16, id: Id:0, dest: Dest:0, last: false}, + TestStrobedStream{data: Data:0xADBE, len: Length:16, id: Id:0, dest: Dest:0, last: false}, + TestStrobedStream{data: Data:0xADBEEF, len: Length:24, id: Id:0, dest: Dest:0, last: false}, + TestStrobedStream{data: Data:0xDE, len: Length:8, id: Id:0, dest: Dest:0, last: false}, + TestStrobedStream{data: Data:0xDEEF, len: Length:16, id: Id:0, dest: Dest:0, last: false}, + TestStrobedStream{data: Data:0xDEBE, len: Length:16, id: Id:0, dest: Dest:0, last: false}, + TestStrobedStream{data: Data:0xDEBEEF, len: Length:24, id: Id:0, dest: Dest:0, last: false}, + TestStrobedStream{data: Data:0xDEAD, len: Length:16, id: Id:0, dest: Dest:0, last: false}, + TestStrobedStream{data: Data:0xDEADEF, len: Length:24, id: Id:0, dest: Dest:0, last: false}, + TestStrobedStream{data: Data:0xDEADBE, len: Length:24, id: Id:0, dest: Dest:0, last: false}, + TestStrobedStream{data: Data:0xDEADBEEF, len: Length:32, id: Id:0, dest: Dest:0, last: true} + ]; + + let tok = for (i, tok): (u32, token) in range(u32:0, u32:16) { + let tok = send(tok, stream_s, input_data[i]); + trace_fmt!("TestRemoveEmptyBytes: Sent #{} strobed packet: {:#x}", i + u32:1, input_data[i]); + let (tok, continuous_stream) = recv(tok, continuous_stream_r); + trace_fmt!("TestRemoveEmptyBytes: Received #{} continuous packet: {:#x}", i + u32:1, continuous_stream); + assert_eq(continuous_stream, expected_output[i]); + (tok) + } (tok); + + send(tok, terminator, true); + } } // Returns the number of bytes that should be soted in the state in case we @@ -71,22 +222,23 @@ fn get_overflow_len(len1: uN[LENGTH_W], len2: uN[LEN // Return the new mask for keep and str fields, calculated using new data length fn get_mask(len: uN[DATA_W_LOG2]) -> uN[DATA_W_DIV8] { - const MAX_LEN = DATA_W as uN[DATA_W_LOG2]; - const MASK = !uN[DATA_W_DIV8]:0; + let len_bytes = std::div_pow2(len, uN[DATA_W_LOG2]:8); + let mask = (uN[DATA_W_DIV8]:1 << len_bytes as uN[DATA_W_DIV8]) - uN[DATA_W_DIV8]:1; - let shift = std::div_pow2((MAX_LEN - len), uN[DATA_W_LOG2]:8); - MASK >> shift + mask } // A proc that removes empty bytes from the Axi Stream and provides aligned data // to other procs, allowing for a simpler implementation of the receiving side // of the design. -pub proc AxiStreamRemoveEmpty< +pub proc AxiStreamRemoveEmptyInternal< DATA_W: u32, DEST_W: u32, ID_W: u32, DATA_W_DIV8: u32 = {DATA_W / u32:8}, DATA_W_LOG2: u32 = {std::clog2(DATA_W + u32:1)}, > { type AxiStream = axi_st::AxiStream; + type StrobedStream = ContinuousStream; + type State = AxiStreamRemoveEmptyState; type Offset = uN[DATA_W_LOG2]; @@ -95,11 +247,11 @@ pub proc AxiStreamRemoveEmpty< type Str = uN[DATA_W_DIV8]; type Data = uN[DATA_W]; - stream_in_r: chan in; + stream_in_r: chan in; stream_out_s: chan out; config ( - stream_in_r: chan in, + stream_in_r: chan in, stream_out_s: chan out, ) { (stream_in_r, stream_out_s) @@ -112,17 +264,13 @@ pub proc AxiStreamRemoveEmpty< const MAX_MASK = !uN[DATA_W_DIV8]:0; let do_recv = !state.last; - let (tok, stream_in) = recv_if(join(), stream_in_r, !state.last, zero!()); - let (id, dest) = if !state.last { - (stream_in.id, stream_in.dest) + let (tok, stream_in) = recv_if(join(), stream_in_r, do_recv, zero!()); + let (id, dest, data, len) = if do_recv { + (stream_in.id, stream_in.dest, stream_in.data, stream_in.len) } else { - (state.id, state.dest) + (state.id, state.dest, Data:0, Length:0) }; - let (data, len) = remove_empty_bytes( - stream_in.data, stream_in.keep, stream_in.str - ); - let empty_input_bytes = MAX_LEN - len; let empty_state_bytes = MAX_LEN - state.len; @@ -131,12 +279,11 @@ pub proc AxiStreamRemoveEmpty< let combined_state_data = state.data | data << state.len; - let overflow_len = get_overflow_len(state.len, len); let sum_len = state.len + len; - let sum_mask = get_mask(sum_len); let (next_state, do_send, data) = if !state.last & exceeds_transfer { // flush and store + let overflow_len = get_overflow_len(state.len, len); ( State { data: data >> empty_state_bytes, @@ -156,6 +303,7 @@ pub proc AxiStreamRemoveEmpty< ) } else if state.last | stream_in.last | exact_transfer { // flush only + let sum_mask = get_mask(sum_len); ( zero!(), true, @@ -185,15 +333,55 @@ pub proc AxiStreamRemoveEmpty< } } +type InstAxiStream = axi_st::AxiStream; +type InstStrobedStream = ContinuousStream; -const INST_DATA_W = u32:32; -const INST_DEST_W = u32:32; -const INST_ID_W = u32:32; +proc AxiStreamRemoveEmptyInternalInst { + config ( + stream_in_r: chan in, + stream_out_s: chan out, + ) { + spawn AxiStreamRemoveEmptyInternal ( + stream_in_r, + stream_out_s + ); + } -const INST_DATA_W_DIV8 = INST_DATA_W / u32:8; -const INST_DATA_W_LOG2 = std::clog2(INST_DATA_W + u32:1); + init { } -type InstAxiStream = axi_st::AxiStream; + next (state:()) { } +} + +pub proc AxiStreamRemoveEmpty< + DATA_W: u32, DEST_W: u32, ID_W: u32, + DATA_W_DIV8: u32 = {DATA_W / u32:8}, + DATA_W_LOG2: u32 = {std::clog2(DATA_W + u32:1)}, +> { + type AxiStream = axi_st::AxiStream; + type StrobedStream = ContinuousStream; + + config ( + stream_in_r: chan in, + stream_out_s: chan out, + ) { + let (continuous_stream_s, continuous_stream_r) = chan("continuous_stream"); + + spawn RemoveEmptyBytes( + stream_in_r, + continuous_stream_s + ); + spawn AxiStreamRemoveEmptyInternal ( + continuous_stream_r, + stream_out_s + ); + + () + } + + init { () } + + next (state: ()) {} +} proc AxiStreamRemoveEmptyInst { config ( @@ -211,12 +399,6 @@ proc AxiStreamRemoveEmptyInst { next (state:()) { } } - -const TEST_DATA_W = u32:32; -const TEST_DEST_W = u32:32; -const TEST_ID_W = u32:32; -const TEST_DATA_W_DIV8 = TEST_DATA_W / u32:8; - type TestAxiStream = axi_st::AxiStream; #[test_proc] @@ -465,6 +647,283 @@ proc AxiStreamRemoveEmptyTest { id: Id:0, dest: Dest:0, }); + + // Test 7: Some bits set, last set in the last transfer. + + + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0xf7697fb9, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0xc735df5e, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0x70d3da1f, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0x0000001d, + str: Str:0b0001, + keep: Keep:0b0001, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0x01eaf614, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0x00001734, + str: Str:0b0011, + keep: Keep:0b0011, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0xe935b870, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0x00f149f5, + str: Str:0b0111, + keep: Keep:0b0111, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0xf073eed1, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0xce97b5bd, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0x950cddd9, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0x08f0ebd4, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0xABEB9592, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0xB16E2D5C, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0x157CF9C6, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0x00000019, + str: Str:0b0001, + keep: Keep:0b0001, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0xf7697fb9, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0xc735df5e, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0x70d3da1f, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0x0000001d, + str: Str:0b0001, + keep: Keep:0b0001, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0x01eaf614, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0x00001734, + str: Str:0b0011, + keep: Keep:0b0011, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0xe935b870, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0x00f149f5, + str: Str:0b0111, + keep: Keep:0b0111, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0xf073eed1, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0xce97b5bd, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0x950cddd9, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0x08f0ebd4, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0xABEB9592, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0xB16E2D5C, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0x157CF9C6, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0x00000019, + str: Str:0b0001, + keep: Keep:0b0001, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + send(tok, terminator, true); } } From c5ec87b5847183dadf710b2c8a9e58226d0f7c8d Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Wed, 13 Nov 2024 14:14:00 +0100 Subject: [PATCH 23/85] modules/zstd/memory/axi_writer: Assign parameterized max lane value Fix paramaterization of the proc Internal-tag: [#67272] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/memory/axi_writer.x | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xls/modules/zstd/memory/axi_writer.x b/xls/modules/zstd/memory/axi_writer.x index 58ed5dfbf3..1a5e7c5eef 100644 --- a/xls/modules/zstd/memory/axi_writer.x +++ b/xls/modules/zstd/memory/axi_writer.x @@ -124,6 +124,7 @@ pub proc AxiWriter< next(state: State) { const BYTES_IN_TRANSFER = DATA_W_DIV8 as Addr; const MAX_AXI_BURST_BYTES = Addr:256 * BYTES_IN_TRANSFER; + const MAX_LANE = std::unsigned_max_value(); let tok_0 = join(); @@ -273,7 +274,7 @@ pub proc AxiWriter< Fsm::AXI_WRITE_W => { let last = state.burst_counter == state.burst_end; let low_lane = state.req_low_lane; - let high_lane = if (last) { state.req_high_lane } else {Lane:3}; + let high_lane = if (last) { state.req_high_lane } else {MAX_LANE}; let mask = common::lane_mask(low_lane, high_lane); AxiW { From 70384f360daa810ae17e5e19dfafc088f55775a7 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Mon, 12 May 2025 19:53:06 +0200 Subject: [PATCH 24/85] modules/zstd/memory/mem_writer: Add support for not-full input data packets Add AxiStreamRemoveEmpty proc to the processing pipeline. It removes non-strobed bytes from the input AXI Stream frames and forms full frames (ensures that only the last input data packet won't be full). Internal-tag: [#67272] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/memory/BUILD | 5 +- xls/modules/zstd/memory/mem_writer.x | 86 +++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/xls/modules/zstd/memory/BUILD b/xls/modules/zstd/memory/BUILD index 2368579c26..f80c064ead 100644 --- a/xls/modules/zstd/memory/BUILD +++ b/xls/modules/zstd/memory/BUILD @@ -847,6 +847,7 @@ xls_dslx_library( ":axi_dslx", ":axi_st_dslx", ":axi_stream_add_empty_dslx", + ":axi_stream_remove_empty_dslx", ":axi_writer_dslx", ":common_dslx", ], @@ -859,13 +860,11 @@ xls_dslx_test( mem_writer_codegen_args = common_codegen_args | { "module_name": "mem_writer", - "pipeline_stages": "2", + "pipeline_stages": "3", "streaming_channel_data_suffix": "_data", "multi_proc": "true", "flop_inputs_kind": "skid", "flop_outputs_kind": "skid", - "worst_case_throughput": "1", - "fifo_module": "", "materialize_internal_fifos": "true", } diff --git a/xls/modules/zstd/memory/mem_writer.x b/xls/modules/zstd/memory/mem_writer.x index 277c9910ef..8e53155b05 100644 --- a/xls/modules/zstd/memory/mem_writer.x +++ b/xls/modules/zstd/memory/mem_writer.x @@ -35,6 +35,7 @@ import xls.modules.zstd.memory.axi; import xls.modules.zstd.memory.axi_st; import xls.modules.zstd.memory.common; import xls.modules.zstd.memory.axi_writer; +import xls.modules.zstd.memory.axi_stream_remove_empty; import xls.modules.zstd.memory.axi_stream_add_empty; pub struct MemWriterReq { @@ -108,11 +109,15 @@ proc MemWriter< let (axi_writer_req_s, axi_writer_req_r) = chan("axi_writer_req"); let (padding_req_s, padding_req_r) = chan("padding_req"); let (axi_st_raw_s, axi_st_raw_r) = chan("axi_st_raw"); + let (axi_st_clean_s, axi_st_clean_r) = chan("axi_st_clean"); let (axi_st_padded_s, axi_st_padded_r) = chan("axi_st_padded"); + spawn axi_stream_remove_empty::AxiStreamRemoveEmpty< + DATA_W, DEST_W, ID_W + >(axi_st_raw_r, axi_st_clean_s); spawn axi_stream_add_empty::AxiStreamAddEmpty< DATA_W, DEST_W, ID_W, ADDR_W - >(padding_req_r, axi_st_raw_r, axi_st_padded_s); + >(padding_req_r, axi_st_clean_r, axi_st_padded_s); spawn axi_writer::AxiWriter< ADDR_W, DATA_W, DEST_W, ID_W >(axi_writer_req_r, resp_s, axi_aw_s, axi_w_s, axi_b_r, axi_st_padded_r); @@ -147,7 +152,7 @@ proc MemWriter< } }, Fsm::SEND_DATA => { - let next_req_len = state.req_len - sLength:4; + let next_req_len = state.req_len - data_in.length as sLength; State { fsm: if (next_req_len <= sLength:0) {Fsm::RECV_REQ} else {Fsm::SEND_DATA}, req_len: next_req_len, @@ -162,7 +167,7 @@ proc MemWriter< let raw_axi_st_frame = match(state.fsm) { Fsm::SEND_DATA => { - let next_req_len = state.req_len - sLength:4; + let next_req_len = next_state.req_len; let str_keep = ((Length:1 << data_in.length) - Length:1) as Strobe; AxiStream { data: data_in.data, @@ -631,6 +636,81 @@ proc MemWriterTest { let (tok, resp) = recv(tok, resp_r); assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + // Unligned 3 transfers + let tok = send(tok, req_in_s, TestReq { + addr: TestAddr:0x1f3, + length: TestLength:15 + }); + let tok = send(tok, data_in_s, TestData { + data: TestDataBits:0x11223344, + length: TestLength:4, + last: false, + }); + let tok = send(tok, data_in_s, TestData { + data: TestDataBits:0x00005566, + length: TestLength:2, + last: false, + }); + let tok = send(tok, data_in_s, TestData { + data: TestDataBits:0x778899aa, + length: TestLength:4, + last: false, + }); + let tok = send(tok, data_in_s, TestData { + data: TestDataBits:0x00bbccdd, + length: TestLength:3, + last: false, + }); + let tok = send(tok, data_in_s, TestData { + data: TestDataBits:0x0000eeff, + length: TestLength:2, + last: true, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAW { + id: TestId:12, + addr: TestAddr:0x1f0, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:4, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x44000000, + strb: TestStrobe:0x8, + last: false, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x66112233, + strb: TestStrobe:0xF, + last: false, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x8899aa55, + strb: TestStrobe:0xf, + last: false, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0xbbccdd77, + strb: TestStrobe:0xf, + last: false, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x0000eeff, + strb: TestStrobe:0x3, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:12, + }); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + send(tok, terminator, true); } } From 4e7350a997f28951b8fedc0748e506d8b5266f4c Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Mon, 12 May 2025 20:42:00 +0200 Subject: [PATCH 25/85] modules/zstd/memory/mem_writer: Add MemWriterInternal proc * Extract control logic to MemWriterInternal proc * Create alias for the MemWriter response type Internal-tag: [#67272] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/memory/BUILD | 63 ++++++++++- xls/modules/zstd/memory/mem_writer.x | 153 +++++++++++++++++++-------- 2 files changed, 168 insertions(+), 48 deletions(-) diff --git a/xls/modules/zstd/memory/BUILD b/xls/modules/zstd/memory/BUILD index f80c064ead..39a111dfe5 100644 --- a/xls/modules/zstd/memory/BUILD +++ b/xls/modules/zstd/memory/BUILD @@ -858,11 +858,70 @@ xls_dslx_test( library = ":mem_writer_dslx", ) +mem_writer_internal_codegen_args = common_codegen_args | { + "module_name": "mem_writer_internal", + "pipeline_stages": "2", +} + +xls_dslx_verilog( + name = "mem_writer_internal_verilog", + codegen_args = mem_writer_internal_codegen_args, + dslx_top = "MemWriterInternalInst", + library = ":mem_writer_dslx", + tags = ["manual"], + verilog_file = "mem_writer_internal.v", +) + +xls_benchmark_ir( + name = "mem_writer_internal_opt_ir_benchmark", + src = ":mem_writer_internal_verilog.opt.ir", + benchmark_ir_args = common_codegen_args | { + "pipeline_stages": "10", + "top": "__mem_writer__MemWriterInternalInst__MemWriterInternal_0__16_32_4_4_4_2_next", + }, + tags = ["manual"], +) + +verilog_library( + name = "mem_writer_internal_verilog_lib", + srcs = [ + ":mem_writer_internal.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "mem_writer_internal_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "mem_writer_internal", + deps = [ + ":mem_writer_internal_verilog_lib", + ], +) + +benchmark_synth( + name = "mem_writer_internal_benchmark_synth", + synth_target = ":mem_writer_internal_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "mem_writer_internal_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":mem_writer_internal_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + mem_writer_codegen_args = common_codegen_args | { "module_name": "mem_writer", - "pipeline_stages": "3", + "pipeline_stages": "10", "streaming_channel_data_suffix": "_data", - "multi_proc": "true", "flop_inputs_kind": "skid", "flop_outputs_kind": "skid", "materialize_internal_fifos": "true", diff --git a/xls/modules/zstd/memory/mem_writer.x b/xls/modules/zstd/memory/mem_writer.x index 8e53155b05..f49d147785 100644 --- a/xls/modules/zstd/memory/mem_writer.x +++ b/xls/modules/zstd/memory/mem_writer.x @@ -43,6 +43,9 @@ pub struct MemWriterReq { length: uN[ADDR_W], } +pub type MemWriterResp = axi_writer::AxiWriterResp; +pub type MemWriterRespStatus = axi_writer::AxiWriterRespStatus; + pub struct MemWriterDataPacket { data: uN[DATA_W], length: uN[ADDR_W], // Expressed in bytes @@ -68,20 +71,15 @@ struct MemWriterState< axi_writer_req: axi_writer::AxiWriterRequest, } -proc MemWriter< +proc MemWriterInternal< ADDR_W: u32, DATA_W: u32, DEST_W: u32, ID_W: u32, WRITER_ID: u32, - DATA_W_DIV8: u32 = {DATA_W / u32:8}, - DATA_W_LOG2: u32 = {std::clog2(DATA_W / u32:8)} + DATA_W_DIV8: u32 = {DATA_W / u32:8} > { type Req = MemWriterReq; type Data = MemWriterDataPacket; type AxiWriterReq = axi_writer::AxiWriterRequest; - type AxiWriterResp = axi_writer::AxiWriterResp; type PaddingReq = axi_writer::AxiWriterRequest; type AxiStream = axi_st::AxiStream; - type AxiAW = axi::AxiAw; - type AxiW = axi::AxiW; - type AxiB = axi::AxiB; type State = MemWriterState; type Fsm = MemWriterFsm; @@ -96,33 +94,16 @@ proc MemWriter< axi_writer_req_s: chan out; padding_req_s: chan out; axi_st_raw_s: chan out; - resp_s: chan out; config( req_in_r: chan in, data_in_r: chan in, - axi_aw_s: chan out, - axi_w_s: chan out, - axi_b_r: chan in, - resp_s: chan out, + axi_writer_req_s: chan out, + padding_req_s: chan out, + axi_st_raw_s: chan out, ) { - let (axi_writer_req_s, axi_writer_req_r) = chan("axi_writer_req"); - let (padding_req_s, padding_req_r) = chan("padding_req"); - let (axi_st_raw_s, axi_st_raw_r) = chan("axi_st_raw"); - let (axi_st_clean_s, axi_st_clean_r) = chan("axi_st_clean"); - let (axi_st_padded_s, axi_st_padded_r) = chan("axi_st_padded"); - - spawn axi_stream_remove_empty::AxiStreamRemoveEmpty< - DATA_W, DEST_W, ID_W - >(axi_st_raw_r, axi_st_clean_s); - spawn axi_stream_add_empty::AxiStreamAddEmpty< - DATA_W, DEST_W, ID_W, ADDR_W - >(padding_req_r, axi_st_clean_r, axi_st_padded_s); - spawn axi_writer::AxiWriter< - ADDR_W, DATA_W, DEST_W, ID_W - >(axi_writer_req_r, resp_s, axi_aw_s, axi_w_s, axi_b_r, axi_st_padded_r); - (req_in_r, data_in_r, axi_writer_req_s, padding_req_s, axi_st_raw_s, resp_s) + (req_in_r, data_in_r, axi_writer_req_s, padding_req_s, axi_st_raw_s) } init { zero!() } @@ -194,9 +175,90 @@ const INST_DATA_W = u32:32; const INST_DATA_W_DIV8 = INST_DATA_W / u32:8; const INST_DEST_W = INST_DATA_W / u32:8; const INST_ID_W = INST_DATA_W / u32:8; -const INST_DATA_W_LOG2 = u32:6; const INST_WRITER_ID = u32:2; +proc MemWriterInternalInst { + type Req = MemWriterReq; + type Data = MemWriterDataPacket; + type AxiWriterReq = axi_writer::AxiWriterRequest; + type PaddingReq = axi_writer::AxiWriterRequest; + type AxiStream = axi_st::AxiStream; + + config( + req_in_r: chan in, + data_in_r: chan in, + axi_writer_req_s: chan out, + padding_req_s: chan out, + axi_st_raw_s: chan out, + ) { + + spawn MemWriterInternal< + INST_ADDR_W, INST_DATA_W, INST_DEST_W, INST_ID_W, INST_WRITER_ID + >(req_in_r, data_in_r, axi_writer_req_s, padding_req_s, axi_st_raw_s); + () + } + + init {} + + next(state: ()) {} +} + +pub proc MemWriter< + ADDR_W: u32, DATA_W: u32, DEST_W: u32, ID_W: u32, WRITER_ID: u32, + DATA_W_DIV8: u32 = {DATA_W / u32:8} +> { + type Req = MemWriterReq; + type Data = MemWriterDataPacket; + type AxiWriterReq = axi_writer::AxiWriterRequest; + type PaddingReq = axi_writer::AxiWriterRequest; + type AxiStream = axi_st::AxiStream; + type AxiAW = axi::AxiAw; + type AxiW = axi::AxiW; + type AxiB = axi::AxiB; + type State = MemWriterState; + type Fsm = MemWriterFsm; + + type Length = uN[ADDR_W]; + type sLength = sN[ADDR_W]; + type Strobe = uN[DATA_W_DIV8]; + type Id = uN[ID_W]; + type Dest = uN[DEST_W]; + + config( + req_in_r: chan in, + data_in_r: chan in, + axi_aw_s: chan out, + axi_w_s: chan out, + axi_b_r: chan in, + resp_s: chan out, + ) { + let (axi_writer_req_s, axi_writer_req_r) = chan("axi_writer_req"); + let (padding_req_s, padding_req_r) = chan("padding_req"); + let (axi_st_raw_s, axi_st_raw_r) = chan("axi_st_raw"); + let (axi_st_clean_s, axi_st_clean_r) = chan("axi_st_clean"); + let (axi_st_padded_s, axi_st_padded_r) = chan("axi_st_padded"); + + spawn MemWriterInternal< + ADDR_W, DATA_W, DEST_W, ID_W, WRITER_ID + >(req_in_r, data_in_r, axi_writer_req_s, padding_req_s, axi_st_raw_s); + spawn axi_stream_remove_empty::AxiStreamRemoveEmpty< + DATA_W, DEST_W, ID_W + >(axi_st_raw_r, axi_st_clean_s); + spawn axi_stream_add_empty::AxiStreamAddEmpty< + DATA_W, DEST_W, ID_W, ADDR_W + >(padding_req_r, axi_st_clean_r, axi_st_padded_s); + spawn axi_writer::AxiWriter< + ADDR_W, DATA_W, DEST_W, ID_W + >(axi_writer_req_r, resp_s, axi_aw_s, axi_w_s, axi_b_r, axi_st_padded_r); + + () + } + + init {} + + next(state: ()) {} +} + proc MemWriterInst { type InstReq = MemWriterReq; type InstData = MemWriterDataPacket; @@ -204,7 +266,7 @@ proc MemWriterInst { type InstAxiAW = axi::AxiAw; type InstAxiW = axi::AxiW; type InstAxiB = axi::AxiB; - type InstAxiWriterResp = axi_writer::AxiWriterResp; + type InstMemWriterResp = MemWriterResp; config( req_in_r: chan in, @@ -212,7 +274,7 @@ proc MemWriterInst { axi_aw_s: chan out, axi_w_s: chan out, axi_b_r: chan in, - resp_s: chan out + resp_s: chan out ) { spawn MemWriter< INST_ADDR_W, INST_DATA_W, INST_DEST_W, INST_ID_W, INST_WRITER_ID @@ -230,13 +292,12 @@ const TEST_DATA_W = u32:32; const TEST_DATA_W_DIV8 = TEST_DATA_W / u32:8; const TEST_DEST_W = TEST_DATA_W / u32:8; const TEST_ID_W = TEST_DATA_W / u32:8; -const TEST_DATA_W_LOG2 = u32:6; const TEST_WRITER_ID = u32:2; type TestReq = MemWriterReq; type TestData = MemWriterDataPacket; -type TestAxiWriterResp = axi_writer::AxiWriterResp; -type TestAxiWriterRespStatus = axi_writer::AxiWriterRespStatus; +type TestMemWriterResp = MemWriterResp; +type TestMemWriterRespStatus = MemWriterRespStatus; type TestAxiStream = axi_st::AxiStream; type TestAxiAW = axi::AxiAw; type TestAxiW = axi::AxiW; @@ -260,7 +321,7 @@ proc MemWriterTest { axi_aw_r: chan in; axi_w_r: chan in; axi_b_s: chan out; - resp_r: chan in; + resp_r: chan in; config( terminator: chan out, @@ -270,7 +331,7 @@ proc MemWriterTest { let (axi_aw_s, axi_aw_r) = chan("axi_aw"); let (axi_w_s, axi_w_r) = chan("axi_w"); let (axi_b_s, axi_b_r) = chan("axi_b"); - let (resp_s, resp_r) = chan("resp"); + let (resp_s, resp_r) = chan("resp"); spawn MemWriter< TEST_ADDR_W, TEST_DATA_W, TEST_DEST_W, TEST_ID_W, TEST_WRITER_ID >(req_in_r, data_in_r, axi_aw_s, axi_w_s, axi_b_r, resp_s); @@ -311,7 +372,7 @@ proc MemWriterTest { id: TestId:1, }); let (tok, resp) = recv(tok, resp_r); - assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + assert_eq(resp, TestMemWriterResp{status: TestMemWriterRespStatus::OKAY}); // Unaligned single transfer let tok = send(tok, req_in_s, TestReq { @@ -342,7 +403,7 @@ proc MemWriterTest { id: TestId:2, }); let (tok, resp) = recv(tok, resp_r); - assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + assert_eq(resp, TestMemWriterResp{status: TestMemWriterRespStatus::OKAY}); // Unaligned single transfer let tok = send(tok, req_in_s, TestReq { @@ -373,7 +434,7 @@ proc MemWriterTest { id: TestId:3, }); let (tok, resp) = recv(tok, resp_r); - assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + assert_eq(resp, TestMemWriterResp{status: TestMemWriterRespStatus::OKAY}); // Unaligned single transfer let tok = send(tok, req_in_s, TestReq { @@ -404,7 +465,7 @@ proc MemWriterTest { id: TestId:4, }); let (tok, resp) = recv(tok, resp_r); - assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + assert_eq(resp, TestMemWriterResp{status: TestMemWriterRespStatus::OKAY}); // Unaligned single transfer let tok = send(tok, req_in_s, TestReq { @@ -435,7 +496,7 @@ proc MemWriterTest { id: TestId:5, }); let (tok, resp) = recv(tok, resp_r); - assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + assert_eq(resp, TestMemWriterResp{status: TestMemWriterRespStatus::OKAY}); // Unaligned 2 transfers let tok = send(tok, req_in_s, TestReq { @@ -472,7 +533,7 @@ proc MemWriterTest { id: TestId:6, }); let (tok, resp) = recv(tok, resp_r); - assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + assert_eq(resp, TestMemWriterResp{status: TestMemWriterRespStatus::OKAY}); // Unligned 3 transfers let tok = send(tok, req_in_s, TestReq { @@ -520,7 +581,7 @@ proc MemWriterTest { id: TestId:7, }); let (tok, resp) = recv(tok, resp_r); - assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + assert_eq(resp, TestMemWriterResp{status: TestMemWriterRespStatus::OKAY}); // Crossing AXI 4kB boundary, aligned 2 burst transfers let tok = send(tok, req_in_s, TestReq { @@ -574,7 +635,7 @@ proc MemWriterTest { id: TestId:9, }); let (tok, resp) = recv(tok, resp_r); - assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + assert_eq(resp, TestMemWriterResp{status: TestMemWriterRespStatus::OKAY}); // Crossing AXI 4kB boundary, unaligned 2 burst transfers let tok = send(tok, req_in_s, TestReq { @@ -634,7 +695,7 @@ proc MemWriterTest { id: TestId:11, }); let (tok, resp) = recv(tok, resp_r); - assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + assert_eq(resp, TestMemWriterResp{status: TestMemWriterRespStatus::OKAY}); // Unligned 3 transfers let tok = send(tok, req_in_s, TestReq { @@ -709,7 +770,7 @@ proc MemWriterTest { id: TestId:12, }); let (tok, resp) = recv(tok, resp_r); - assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + assert_eq(resp, TestMemWriterResp{status: TestMemWriterRespStatus::OKAY}); send(tok, terminator, true); } From 7bc669bd8fc671388f7d702a2d1564662a2a54ae Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 13 May 2025 14:44:07 +0200 Subject: [PATCH 26/85] modules/zstd/zstd_dec: Write decoded data to the memory SequenceExecutor: * Add output channel in the format compliant with MemWriter input data channel type ZstdDecoder: * Add MemWriter proc: * Write request formed based on the address of the OutputBuffer CSR and FrameContentSize field from the Frame Header * Data to write is sent out to the proc by the SequenceExecutor * Transition to the FINISH state (and triggers notify channel) only after receiving the response from the MemWriter * DSLX tests: * Receive decoded data sent out on the AXI interface by the MemWriter proc * Mock the output memory buffer as a DSLX array Internal-tag: [#67272] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 5 +- xls/modules/zstd/sequence_executor.x | 75 ++++++++++++++-- xls/modules/zstd/zstd_dec.x | 89 +++++++++++++++++- xls/modules/zstd/zstd_dec_test.x | 129 +++++++++++++++++++++------ 4 files changed, 260 insertions(+), 38 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 70bda5af9b..77c0270bd9 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -607,6 +607,7 @@ xls_dslx_library( ":common_dslx", ":ram_printer_dslx", "//xls/examples:ram_dslx", + "//xls/modules/zstd/memory:mem_writer_dslx", ], ) @@ -651,7 +652,8 @@ xls_dslx_verilog( dslx_top = "SequenceExecutorZstd", library = ":sequence_executor_dslx", opt_ir_args = { - "top": "__sequence_executor__SequenceExecutorZstd__SequenceExecutor_0__64_0_0_0_13_8192_65536_next", + "inline_procs": "true", + "top": "__sequence_executor__SequenceExecutorZstd__SequenceExecutor_0__16_64_64_0_0_0_13_8192_65536_next", }, tags = ["manual"], verilog_file = "sequence_executor.v", @@ -875,6 +877,7 @@ zstd_dec_deps = [ ":sequence_executor_dslx", "//xls/examples:ram_dslx", "//xls/modules/zstd/memory:mem_reader_dslx", + "//xls/modules/zstd/memory:mem_writer_dslx", "//xls/modules/zstd/memory:axi_ram_dslx", ] diff --git a/xls/modules/zstd/sequence_executor.x b/xls/modules/zstd/sequence_executor.x index 1e01f9b03c..d87bfc1bd6 100644 --- a/xls/modules/zstd/sequence_executor.x +++ b/xls/modules/zstd/sequence_executor.x @@ -14,6 +14,7 @@ import std; import xls.modules.zstd.common as common; +import xls.modules.zstd.memory.mem_writer as mem_writer; import xls.modules.zstd.ram_printer as ram_printer; import xls.examples.ram; @@ -53,6 +54,8 @@ 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_DATA_W = u32:64; +const TEST_ADDR_W = u32:16; 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; @@ -114,6 +117,36 @@ fn test_decode_literal_packet() { }) } +fn convert_output_packet(packet: ZstdDecodedPacket) -> mem_writer::MemWriterDataPacket { + type MemWriterDataPacket = mem_writer::MemWriterDataPacket; + MemWriterDataPacket { + data: packet.data as uN[DATA_W], + length: std::div_pow2(packet.length, u32:8) as uN[ADDR_W], + last: packet.last + } +} + +#[test] +fn test_convert_output_packet() { + const DATA_W = u32:64; + const ADDR_W = u32:16; + + type MemWriterDataPacket = mem_writer::MemWriterDataPacket; + + let packet = ZstdDecodedPacket { + data: CopyOrMatchContent:0xAA00BB11CC22DD33, + length: BlockPacketLength:64, + last: false + }; + let expected = MemWriterDataPacket { + data: uN[DATA_W]:0xAA00BB11CC22DD33, + length: uN[ADDR_W]:8, + last: false + }; + + assert_eq(convert_output_packet(packet), expected) +} + 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; @@ -808,14 +841,18 @@ fn handle_reapeated_offset_for_sequences } pub proc SequenceExecutor { + type MemWriterDataPacket = mem_writer::MemWriterDataPacket; + input_r: chan in; output_s: chan out; + output_mem_wr_data_in_s: chan out; ram_comp_input_s: chan> out; ram_comp_output_r: chan> in; ram_resp_input_s: chan out; @@ -840,6 +877,7 @@ pub proc SequenceExecutor in, output_s: chan out, + output_mem_wr_data_in_s: chan out, ram_resp_output_r: chan in, ram_resp_output_s: chan out, rd_req_m0_s: chan> out, @@ -890,7 +928,7 @@ pub proc SequenceExecutor(output_data); + let tok2_10_1 = send_if(tok1, output_mem_wr_data_in_s, do_write_output, output_mem_wr_data_in); // Ask for response let tok2_11 = send_if(tok1, rd_req_m0_s, (read_reqs[0]).mask != RAM_REQ_MASK_NONE, read_reqs[0]); @@ -1142,14 +1182,18 @@ pub proc SequenceExecutor; init { } config( input_r: chan in, output_s: chan out, + output_mem_wr_data_in_s: chan out, looped_channel_r: chan in, looped_channel_s: chan out, rd_req_m0_s: chan> out, @@ -1185,8 +1229,9 @@ pub proc SequenceExecutorZstd { wr_resp_m6_r: chan in, wr_resp_m7_r: chan in ) { - spawn SequenceExecutor ( - input_r, output_s, + spawn SequenceExecutor ( + input_r, output_s, output_mem_wr_data_in_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, @@ -1298,10 +1343,12 @@ const LITERAL_TEST_MEMORY_CONTENT:(TestRamAddr, RamData)[3][RAM_NUM] = [ #[test_proc] proc SequenceExecutorLiteralsTest { + type MemWriterDataPacket = mem_writer::MemWriterDataPacket; terminator: chan out; input_s: chan> out; output_r: chan in; + output_mem_wr_data_in_r: chan in; print_start_s: chan<()> out; print_finish_r: chan<()> in; @@ -1314,6 +1361,7 @@ proc SequenceExecutorLiteralsTest { config(terminator: chan out) { let (input_s, input_r) = chan>("input"); let (output_s, output_r) = chan("output"); + let (output_mem_wr_data_in_s, output_mem_wr_data_in_r) = chan("output_mem_wr_data_in"); let (looped_channel_s, looped_channel_r) = chan("looped_channels"); @@ -1328,11 +1376,12 @@ proc SequenceExecutorLiteralsTest { let INIT_HB_PTR_ADDR = u32:127; spawn SequenceExecutor< TEST_HISTORY_BUFFER_SIZE_KB, + TEST_DATA_W, TEST_ADDR_W, TEST_RAM_SIZE, TEST_RAM_ADDR_WIDTH, INIT_HB_PTR_ADDR, > ( - input_r, output_s, + input_r, output_s, output_mem_wr_data_in_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], @@ -1384,7 +1433,7 @@ proc SequenceExecutorLiteralsTest { ( terminator, - input_s, output_r, + input_s, output_r, output_mem_wr_data_in_r, print_start_s, print_finish_r, ram_rd_req_s, ram_rd_resp_r, ram_wr_req_s, ram_wr_resp_r @@ -1404,6 +1453,9 @@ proc SequenceExecutorLiteralsTest { let (tok, recv_data) = recv(tok, output_r); let expected = decode_literal_packet(LITERAL_TEST_INPUT_DATA[i]); assert_eq(expected, recv_data); + let (tok, recv_mem_writer_data) = recv(tok, output_mem_wr_data_in_r); + let expected_mem_writer_data = convert_output_packet(expected); + assert_eq(expected_mem_writer_data, recv_mem_writer_data); } else {} }(()); @@ -1554,10 +1606,12 @@ const SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS:ZstdDecodedPacket[11] = [ #[test_proc] proc SequenceExecutorSequenceTest { + type MemWriterDataPacket = mem_writer::MemWriterDataPacket; terminator: chan out; input_s: chan out; output_r: chan in; + output_mem_wr_data_in_r: chan in; print_start_s: chan<()> out; print_finish_r: chan<()> in; @@ -1570,6 +1624,7 @@ proc SequenceExecutorSequenceTest { config(terminator: chan out) { let (input_s, input_r) = chan("input"); let (output_s, output_r) = chan("output"); + let (output_mem_wr_data_in_s, output_mem_wr_data_in_r) = chan("output_mem_wr_data_in"); let (looped_channel_s, looped_channel_r) = chan("looped_channel"); @@ -1584,11 +1639,12 @@ proc SequenceExecutorSequenceTest { let INIT_HB_PTR_ADDR = u32:127; spawn SequenceExecutor< TEST_HISTORY_BUFFER_SIZE_KB, + TEST_DATA_W, TEST_ADDR_W, TEST_RAM_SIZE, TEST_RAM_ADDR_WIDTH, INIT_HB_PTR_ADDR, > ( - input_r, output_s, + input_r, output_s, output_mem_wr_data_in_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], @@ -1640,7 +1696,7 @@ proc SequenceExecutorSequenceTest { ( terminator, - input_s, output_r, + input_s, output_r, output_mem_wr_data_in_r, print_start_s, print_finish_r, ram_rd_req_s, ram_rd_resp_r, ram_wr_req_s, ram_wr_resp_r ) @@ -1659,6 +1715,9 @@ proc SequenceExecutorSequenceTest { let (tok, recv_data) = recv(tok, output_r); let expected = decode_literal_packet(LITERAL_TEST_INPUT_DATA[i]); assert_eq(expected, recv_data); + let (tok, recv_mem_writer_data) = recv(tok, output_mem_wr_data_in_r); + let expected_mem_writer_data = convert_output_packet(expected); + assert_eq(expected_mem_writer_data, recv_mem_writer_data); } else {} }(()); diff --git a/xls/modules/zstd/zstd_dec.x b/xls/modules/zstd/zstd_dec.x index 528d16d599..7fc5fb260e 100644 --- a/xls/modules/zstd/zstd_dec.x +++ b/xls/modules/zstd/zstd_dec.x @@ -23,6 +23,7 @@ import xls.modules.zstd.common; import xls.modules.zstd.memory.axi; import xls.modules.zstd.csr_config; import xls.modules.zstd.memory.mem_reader; +import xls.modules.zstd.memory.mem_writer; import xls.modules.zstd.frame_header_dec; import xls.modules.zstd.block_header; import xls.modules.zstd.block_header_dec; @@ -45,7 +46,8 @@ enum ZstdDecoderInternalFsm: u4 { DECODE_RLE_BLOCK = 5, DECODE_COMPRESSED_BLOCK = 6, DECODE_CHECKSUM = 7, - FINISH = 8, + WRITE_OUTPUT = 8, + FINISH = 9, ERROR = 13, INVALID = 15, } @@ -128,6 +130,9 @@ proc ZstdDecoderInternal< type MemReaderReq = mem_reader::MemReaderReq; type MemReaderResp = mem_reader::MemReaderResp; + type MemWriterReq = mem_writer::MemWriterReq; + type MemWriterResp = mem_writer::MemWriterResp; + type FrameHeaderDecoderStatus = frame_header_dec::FrameHeaderDecoderStatus; type FrameHeaderDecoderReq = frame_header_dec::FrameHeaderDecoderReq; type FrameHeaderDecoderResp = frame_header_dec::FrameHeaderDecoderResp; @@ -167,6 +172,10 @@ proc ZstdDecoderInternal< rle_req_s: chan out; rle_resp_r: chan in; + // Output MemWriter + output_mem_wr_req_s: chan out; + output_mem_wr_resp_r: chan in; + notify_s: chan<()> out; reset_s: chan<()> out; @@ -197,6 +206,10 @@ proc ZstdDecoderInternal< rle_req_s: chan out, rle_resp_r: chan in, + // Output MemWriter + output_mem_wr_req_s: chan out, + output_mem_wr_resp_r: chan in, + notify_s: chan<()> out, reset_s: chan<()> out, ) { @@ -206,6 +219,7 @@ proc ZstdDecoderInternal< bh_req_s, bh_resp_r, raw_req_s, raw_resp_r, rle_req_s, rle_resp_r, + output_mem_wr_req_s, output_mem_wr_resp_r, notify_s, reset_s, ) } @@ -259,6 +273,15 @@ proc ZstdDecoderInternal< trace_fmt!("[DECODE_FRAME_HEADER]: Received FH {:#x}", fh_resp); } else {}; + let output_mem_wr_req = MemWriterReq {addr: state.output_buffer, length: fh_resp.header.frame_content_size as uN[AXI_ADDR_W]}; + let tok = send_if(tok0, output_mem_wr_req_s, fh_resp_valid, output_mem_wr_req); + + let do_recv_output_mem_wr_resp = (state.fsm == Fsm::WRITE_OUTPUT); + let (tok_x, output_write_resp, output_write_done) = recv_if_non_blocking(tok0, output_mem_wr_resp_r, do_recv_output_mem_wr_resp, zero!()); + if output_write_done { + trace_fmt!("[WRITE_OUTPUT]: Received response {:#x}", output_write_resp); + } else {}; + let do_send_notify = (state.fsm == Fsm::ERROR || state.fsm == Fsm::FINISH); let tok = send_if(tok0, notify_s, do_send_notify, ()); if do_send_notify { @@ -518,10 +541,22 @@ proc ZstdDecoderInternal< Fsm::DECODE_CHECKSUM => { trace_fmt!("[DECODE_CHECKSUM]"); - State {fsm: Fsm::FINISH, ..zero!() } + State {fsm: Fsm::WRITE_OUTPUT, ..zero!() } }, + Fsm::WRITE_OUTPUT => { + trace_fmt!("[WRITE_OUTPUT]"); + let error = (output_write_resp.status != mem_writer::MemWriterRespStatus::OKAY); + let fsm = match (output_write_done, error) { + (true, false) => Fsm::FINISH, + (true, true) => Fsm::ERROR, + ( _, _) => Fsm::WRITE_OUTPUT, + }; + + State {fsm: fsm, ..zero!() } + }, + Fsm::ERROR => { trace_fmt!("[ERROR]"); State { fsm: Fsm::IDLE, ..zero!() } @@ -586,6 +621,9 @@ proc ZstdDecoderInternalTest { type RleBlockDecoderResp = rle_block_dec::RleBlockDecoderResp; type RleBlockDecoderStatus = rle_block_dec::RleBlockDecoderStatus; + type MemWriterReq = mem_writer::MemWriterReq; + type MemWriterResp = mem_writer::MemWriterResp; + terminator: chan out; csr_rd_req_r: chan in; @@ -606,6 +644,9 @@ proc ZstdDecoderInternalTest { rle_req_r: chan in; rle_resp_s: chan out; + output_mem_wr_req_r: chan in; + output_mem_wr_resp_s: chan out; + notify_r: chan<()> in; reset_r: chan<()> in; @@ -630,6 +671,9 @@ proc ZstdDecoderInternalTest { let (rle_req_s, rle_req_r) = chan("rle_req"); let (rle_resp_s, rle_resp_r) = chan("rle_resp"); + let (output_mem_wr_req_s, output_mem_wr_req_r) = chan("output_mem_wr_req"); + let (output_mem_wr_resp_s, output_mem_wr_resp_r) = chan("output_mem_wr_resp"); + let (notify_s, notify_r) = chan<()>("notify"); let (reset_s, reset_r) = chan<()>("reset"); @@ -639,6 +683,7 @@ proc ZstdDecoderInternalTest { bh_req_s, bh_resp_r, raw_req_s, raw_resp_r, rle_req_s, rle_resp_r, + output_mem_wr_req_s, output_mem_wr_resp_r, notify_s, reset_s, ); @@ -649,6 +694,7 @@ proc ZstdDecoderInternalTest { bh_req_r, bh_resp_s, raw_req_r, raw_resp_s, rle_req_r, rle_resp_s, + output_mem_wr_req_r, output_mem_wr_resp_s, notify_r, reset_r, ) } @@ -802,6 +848,9 @@ proc ZstdDecoderInternalTest { let (tok, ()) = recv(tok, notify_r); + let (tok, _) = recv(tok, output_mem_wr_req_r); + let tok = send(tok, output_mem_wr_resp_s, MemWriterResp {status: mem_writer::MemWriterRespStatus::OKAY}); + send(tok, terminator, true); } } @@ -817,6 +866,7 @@ pub proc ZstdDecoder< AXI_DATA_W_DIV8: u32 = {AXI_DATA_W / u32:8}, LOG2_REGS_N: u32 = {std::clog2(REGS_N)}, HB_RAM_N: u32 = {u32:8}, + MEM_WRITER_ID: u32 = {u32:0}, > { type CsrAxiAr = axi::AxiAr; type CsrAxiR = axi::AxiR; @@ -838,6 +888,9 @@ pub proc ZstdDecoder< type MemReaderReq = mem_reader::MemReaderReq; type MemReaderResp = mem_reader::MemReaderResp; + type MemWriterReq = mem_writer::MemWriterReq; + type MemWriterResp = mem_writer::MemWriterResp; + type MemWriterDataPacket = mem_writer::MemWriterDataPacket; type FrameHeaderDecoderReq = frame_header_dec::FrameHeaderDecoderReq; type FrameHeaderDecoderResp = frame_header_dec::FrameHeaderDecoderResp; @@ -885,6 +938,11 @@ pub proc ZstdDecoder< raw_axi_ar_s: chan out, raw_axi_r_r: chan in, + //// AXI Output Writer (manager) + output_axi_aw_s: chan out, + output_axi_w_s: chan out, + output_axi_b_r: chan in, + // History Buffer ram_rd_req_0_s: chan out, ram_rd_req_1_s: chan out, @@ -1031,12 +1089,13 @@ pub proc ZstdDecoder< ); // Sequence Execution - let (seq_exec_looped_s, seq_exec_looped_r) = chan("seq_exec_looped"); let (seq_exec_output_s, seq_exec_output_r) = chan("seq_exec_output"); + let (output_mem_wr_data_in_s, output_mem_wr_data_in_r) = chan("output_mem_wr_data_in"); - spawn sequence_executor::SequenceExecutor( + spawn sequence_executor::SequenceExecutor( seq_exec_input_r, seq_exec_output_s, + output_mem_wr_data_in_s, seq_exec_looped_r, seq_exec_looped_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, @@ -1053,6 +1112,13 @@ pub proc ZstdDecoder< spawn repacketizer::Repacketizer(seq_exec_output_r, output_s); // Zstd Decoder Control + let (output_mem_wr_req_s, output_mem_wr_req_r) = chan("output_mem_wr_req"); + let (output_mem_wr_resp_s, output_mem_wr_resp_r) = chan("output_mem_wr_resp"); + + spawn mem_writer::MemWriter( + output_mem_wr_req_r, output_mem_wr_data_in_r, + output_axi_aw_s, output_axi_w_s, output_axi_b_r, output_mem_wr_resp_s + ); spawn ZstdDecoderInternal ( csr_rd_req_s, csr_rd_resp_r, csr_wr_req_s, csr_wr_resp_r, csr_change_r, @@ -1060,6 +1126,7 @@ pub proc ZstdDecoder< bh_req_s, bh_resp_r, raw_req_s, raw_resp_r, rle_req_s, rle_resp_r, + output_mem_wr_req_s, output_mem_wr_resp_r, notify_s, reset_s, ); @@ -1108,6 +1175,9 @@ proc ZstdDecoderInternalInst { type RleBlockDecoderReq = rle_block_dec::RleBlockDecoderReq; type RleBlockDecoderResp = rle_block_dec::RleBlockDecoderResp; + type MemWriterReq = mem_writer::MemWriterReq; + type MemWriterResp = mem_writer::MemWriterResp; + init { } config( @@ -1133,6 +1203,10 @@ proc ZstdDecoderInternalInst { rle_req_s: chan out, rle_resp_r: chan in, + // Output MemWriter + output_mem_wr_req_s: chan out, + output_mem_wr_resp_r: chan in, + // IRQ notify_s: chan<()> out, reset_s: chan<()> out, @@ -1145,6 +1219,7 @@ proc ZstdDecoderInternalInst { bh_req_s, bh_resp_r, raw_req_s, raw_resp_r, rle_req_s, rle_resp_r, + output_mem_wr_req_s, output_mem_wr_resp_r, notify_s, reset_s, ); @@ -1195,6 +1270,11 @@ proc ZstdDecoderInst { raw_axi_ar_s: chan out, raw_axi_r_r: chan in, + //// AXI Output Writer (manager) + output_axi_aw_s: chan out, + output_axi_w_s: chan out, + output_axi_b_r: chan in, + // History Buffer ram_rd_req_0_s: chan out, ram_rd_req_1_s: chan out, @@ -1243,6 +1323,7 @@ proc ZstdDecoderInst { fh_axi_ar_s, fh_axi_r_r, bh_axi_ar_s, bh_axi_r_r, raw_axi_ar_s, raw_axi_r_r, + output_axi_aw_s, output_axi_w_s, output_axi_b_r, 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, diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x index a166e1743d..a57f7e499e 100644 --- a/xls/modules/zstd/zstd_dec_test.x +++ b/xls/modules/zstd/zstd_dec_test.x @@ -53,6 +53,8 @@ const TEST_RAM_BASE_ADDR:u32 = u32:0; const TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; const TEST_RAM_INITIALIZED = true; +const TEST_MOCK_OUTPUT_RAM_SIZE:u32 = TEST_RAM_SIZE / TEST_AXI_DATA_W_DIV8; + fn csr_addr(c: zstd_dec::Csr) -> uN[TEST_AXI_ADDR_W] { (c as uN[TEST_AXI_ADDR_W]) << 3 } @@ -64,19 +66,19 @@ proc ZstdDecoderTest { type CsrAxiAw = axi::AxiAw; type CsrAxiW = axi::AxiW; type CsrAxiB = axi::AxiB; - + type CsrRdReq = csr_config::CsrRdReq; type CsrRdResp = csr_config::CsrRdResp; type CsrWrReq = csr_config::CsrWrReq; type CsrWrResp = csr_config::CsrWrResp; type CsrChange = csr_config::CsrChange; - + type MemAxiAr = axi::AxiAr; type MemAxiR = axi::AxiR; type MemAxiAw = axi::AxiAw; type MemAxiW = axi::AxiW; type MemAxiB = axi::AxiB; - + type RamRdReqHB = ram::ReadReq; type RamRdRespHB = ram::ReadResp; type RamWrReqHB = ram::WriteReq; @@ -86,7 +88,7 @@ proc ZstdDecoderTest { type RamRdResp = ram::ReadResp; type RamWrReq = ram::WriteReq; type RamWrResp = ram::WriteResp; - + type ZstdDecodedPacket = common::ZstdDecodedPacket; terminator: chan out; csr_axi_aw_s: chan out; @@ -100,6 +102,9 @@ proc ZstdDecoderTest { bh_axi_r_s: chan out; raw_axi_ar_r: chan in; raw_axi_r_s: chan out; + output_axi_aw_r: chan in; + output_axi_w_r: chan in; + output_axi_b_s: chan out; ram_rd_req_r: chan[8] in; ram_rd_resp_s: chan[8] out; @@ -116,31 +121,35 @@ proc ZstdDecoderTest { output_r: chan in; notify_r: chan<()> in; reset_r: chan<()> in; - + init {} - + config(terminator: chan out) { - + let (csr_axi_aw_s, csr_axi_aw_r) = chan("csr_axi_aw"); let (csr_axi_w_s, csr_axi_w_r) = chan("csr_axi_w"); let (csr_axi_b_s, csr_axi_b_r) = chan("csr_axi_b"); let (csr_axi_ar_s, csr_axi_ar_r) = chan("csr_axi_ar"); let (csr_axi_r_s, csr_axi_r_r) = chan("csr_axi_r"); - + let (fh_axi_ar_s, fh_axi_ar_r) = chan("fh_axi_ar"); let (fh_axi_r_s, fh_axi_r_r) = chan("fh_axi_r"); - + let (bh_axi_ar_s, bh_axi_ar_r) = chan("bh_axi_ar"); let (bh_axi_r_s, bh_axi_r_r) = chan("bh_axi_r"); - + let (raw_axi_ar_s, raw_axi_ar_r) = chan("raw_axi_ar"); let (raw_axi_r_s, raw_axi_r_r) = chan("raw_axi_r"); + let (output_axi_aw_s, output_axi_aw_r) = chan("output_axi_aw"); + let (output_axi_w_s, output_axi_w_r) = chan("output_axi_w"); + let (output_axi_b_s, output_axi_b_r) = chan("output_axi_b"); + let (ram_rd_req_s, ram_rd_req_r) = chan[8]("ram_rd_req"); let (ram_rd_resp_s, ram_rd_resp_r) = chan[8]("ram_rd_resp"); let (ram_wr_req_s, ram_wr_req_r) = chan[8]("ram_wr_req"); let (ram_wr_resp_s, ram_wr_resp_r) = chan[8]("ram_wr_resp"); - + let (ram_rd_req_fh_s, ram_rd_req_fh_r) = chan("ram_rd_req_fh"); let (ram_rd_req_bh_s, ram_rd_req_bh_r) = chan("ram_rd_req_bh"); let (ram_rd_req_raw_s, ram_rd_req_raw_r) = chan("ram_rd_req_raw"); @@ -158,7 +167,7 @@ proc ZstdDecoderTest { let (output_s, output_r) = chan("output"); let (notify_s, notify_r) = chan<()>("notify"); let (reset_s, reset_r) = chan<()>("reset"); - + spawn zstd_dec::ZstdDecoder< TEST_AXI_DATA_W, TEST_AXI_ADDR_W, TEST_AXI_ID_W, TEST_AXI_DEST_W, TEST_REGS_N, TEST_WINDOW_LOG_MAX, @@ -168,6 +177,7 @@ proc ZstdDecoderTest { fh_axi_ar_s, fh_axi_r_r, bh_axi_ar_s, bh_axi_r_r, raw_axi_ar_s, raw_axi_r_r, + output_axi_aw_s, output_axi_w_s, output_axi_b_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], @@ -178,49 +188,49 @@ proc ZstdDecoderTest { ram_wr_resp_r[4], ram_wr_resp_r[5], ram_wr_resp_r[6], ram_wr_resp_r[7], output_s, notify_s, reset_s, ); - + spawn ram::RamModel< TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, TEST_HB_RAM_ASSERT_VALID_READ > (ram_rd_req_r[0], ram_rd_resp_s[0], ram_wr_req_r[0], ram_wr_resp_s[0]); - + spawn ram::RamModel< TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, TEST_HB_RAM_ASSERT_VALID_READ > (ram_rd_req_r[1], ram_rd_resp_s[1], ram_wr_req_r[1], ram_wr_resp_s[1]); - + spawn ram::RamModel< TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, TEST_HB_RAM_ASSERT_VALID_READ > (ram_rd_req_r[2], ram_rd_resp_s[2], ram_wr_req_r[2], ram_wr_resp_s[2]); - + spawn ram::RamModel< TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, TEST_HB_RAM_ASSERT_VALID_READ > (ram_rd_req_r[3], ram_rd_resp_s[3], ram_wr_req_r[3], ram_wr_resp_s[3]); - + spawn ram::RamModel< TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, TEST_HB_RAM_ASSERT_VALID_READ > (ram_rd_req_r[4], ram_rd_resp_s[4], ram_wr_req_r[4], ram_wr_resp_s[4]); - + spawn ram::RamModel< TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, TEST_HB_RAM_ASSERT_VALID_READ > (ram_rd_req_r[5], ram_rd_resp_s[5], ram_wr_req_r[5], ram_wr_resp_s[5]); - + spawn ram::RamModel< TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, TEST_HB_RAM_ASSERT_VALID_READ > (ram_rd_req_r[6], ram_rd_resp_s[6], ram_wr_req_r[6], ram_wr_resp_s[6]); - + spawn ram::RamModel< TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, @@ -231,7 +241,7 @@ proc ZstdDecoderTest { TEST_RAM_DATA_W, TEST_RAM_SIZE, TEST_RAM_WORD_PARTITION_SIZE, TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, > (ram_rd_req_fh_r, ram_rd_resp_fh_s, ram_wr_req_fh_r, ram_wr_resp_fh_s); - + spawn ram::RamModel< TEST_RAM_DATA_W, TEST_RAM_SIZE, TEST_RAM_WORD_PARTITION_SIZE, TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, @@ -263,20 +273,21 @@ proc ZstdDecoderTest { fh_axi_ar_r, fh_axi_r_s, bh_axi_ar_r, bh_axi_r_s, raw_axi_ar_r, raw_axi_r_s, + output_axi_aw_r, output_axi_w_r, output_axi_b_s, ram_rd_req_r, ram_rd_resp_s, ram_wr_req_r, ram_wr_resp_s, ram_wr_req_fh_s, ram_wr_req_bh_s, ram_wr_req_raw_s, ram_wr_resp_fh_r, ram_wr_resp_bh_r, ram_wr_resp_raw_r, output_r, notify_r, reset_r, ) } - + next (state: ()) { trace_fmt!("Test start"); let frames_count = array_size(zstd_frame_testcases::FRAMES); let tok = join(); let tok = unroll_for! (test_i, tok): (u32, token) in range(u32:0, frames_count) { - trace_fmt!("Loading testcase {:x}", test_i); + trace_fmt!("Loading testcase {:x}", test_i + u32:1); let frame = zstd_frame_testcases::FRAMES[test_i]; let tok = for (i, tok): (u32, token) in range(u32:0, frame.array_length) { let req = RamWrReq { @@ -290,7 +301,7 @@ proc ZstdDecoderTest { tok }(tok); - trace_fmt!("Running decoder on testcase {:x}", test_i); + trace_fmt!("Running decoder on testcase {:x}", test_i + u32:1); let addr_req = axi::AxiAw { id: uN[TEST_AXI_ID_W]:0, addr: uN[TEST_AXI_ADDR_W]:0, @@ -357,10 +368,78 @@ proc ZstdDecoderTest { tok }(tok); + // Test ZstdDecoder memory output interface + // Mock the output memory buffer as a DSLX array + // It is required to handle AXI write transactions and to write the incoming data to + // the DSXL array. + // The number of AXI transactions is not known beforehand because it depends on the + // length of the decoded data and the address of the output buffer. The same goes + // with the lengths of the particular AXI burst transactions (the number of transfers). + // Because of that we cannot write for loops to handle AXI transactions dynamically. + // As a workaround, the loops are constrained with upper bounds for AXI transactions + // required for writing maximal supported payload and maximal possible burst transfer + // size. + + // It is possible to decode payloads up to 16kB + // The smallest possible AXI transaction will transfer 1 byte of data + let MAX_AXI_TRANSACTIONS = u32:16384; + // The maximal number if beats in AXI burst transaction + let MAX_AXI_TRANSFERS = u32:256; + // Actual size of decompressed payload for current test + let DECOMPRESSED_BYTES = zstd_frame_testcases::DECOMPRESSED_FRAMES[test_i].length; + trace_fmt!("ZstdDecTest: Start receiving output"); + let (tok, final_output_memory, final_output_memory_id, final_transfered_bytes) = + for (axi_transaction, (tok, output_memory, output_memory_id, transfered_bytes)): + (u32, (token, uN[TEST_AXI_DATA_W][TEST_MOCK_OUTPUT_RAM_SIZE], u32, u32)) + in range(u32:0, MAX_AXI_TRANSACTIONS) { + if (transfered_bytes < DECOMPRESSED_BYTES) { + trace_fmt!("ZstdDecTest: Handle AXI Write transaction #{}", axi_transaction); + let (tok, axi_aw) = recv(tok, output_axi_aw_r); + trace_fmt!("ZstdDecTest: Received AXI AW: {:#x}", axi_aw); + let (tok, internal_output_memory, internal_output_memory_id, internal_transfered_bytes) = + for (axi_transfer, (tok, out_mem, out_mem_id, transf_bytes)): + (u32, (token, uN[TEST_AXI_DATA_W][TEST_MOCK_OUTPUT_RAM_SIZE], u32, u32)) + in range(u32:0, MAX_AXI_TRANSFERS) { + if (axi_transfer as u8 <= axi_aw.len) { + // Receive AXI burst beat transfers + let (tok, axi_w) = recv(tok, output_axi_w_r); + trace_fmt!("ZstdDecTest: Received AXI W #{}: {:#x}", axi_transfer, axi_w); + let strobe_cnt = std::popcount(axi_w.strb) as u32; + // Assume continuous strobe, e.g.: 0b1111; 0b0111; 0b0011; 0b0001; 0b0000 + let strobe_mask = (uN[TEST_AXI_DATA_W]:1 << (strobe_cnt * u32:8) as uN[TEST_AXI_DATA_W]) - uN[TEST_AXI_DATA_W]:1; + let strobed_data = axi_w.data & strobe_mask; + trace_fmt!("ZstdDecTest: write out_mem[{}] = {:#x}", out_mem_id, strobed_data); + let mem = update(out_mem, out_mem_id, (out_mem[out_mem_id] & !strobe_mask) | strobed_data); + let id = out_mem_id + u32:1; + let bytes_written = transf_bytes + strobe_cnt; + trace_fmt!("ZstdDecTest: bytes written: {}", bytes_written); + (tok, mem, id, bytes_written) + } else { + (tok, out_mem, out_mem_id, transf_bytes) + } + // Pass outer loop accumulator as initial accumulator for inner loop + }((tok, output_memory, output_memory_id, transfered_bytes)); + let axi_b = axi::AxiB{resp: axi::AxiWriteResp::OKAY, id: axi_aw.id}; + let tok = send(tok, output_axi_b_s, axi_b); + trace_fmt!("ZstdDecTest: Sent AXI B #{}: {:#x}", axi_transaction, axi_b); + (tok, internal_output_memory, internal_output_memory_id, internal_transfered_bytes) + } else { + (tok, output_memory, output_memory_id, transfered_bytes) + } + }((tok, uN[TEST_AXI_DATA_W][TEST_MOCK_OUTPUT_RAM_SIZE]:[uN[TEST_AXI_DATA_W]:0, ...], u32:0, u32:0)); + trace_fmt!("ZstdDecTest: Finished receiving output"); + + assert_eq(final_transfered_bytes, DECOMPRESSED_BYTES); + assert_eq(final_output_memory_id, decomp_frame.array_length); + for (memory_id, _): (u32, ()) in range(u32:0, decomp_frame.array_length) { + assert_eq(final_output_memory[memory_id], decomp_frame.data[memory_id]); + }(()); + let (tok, ()) = recv(tok, notify_r); - trace_fmt!("Finished decoding testcase {:x} correctly", test_i); + trace_fmt!("Finished decoding testcase {:x} correctly", test_i + u32:1); tok }(tok); + send(tok, terminator, true); } } From 63372d4cc0c8e732afe6739c49a07b115bffb656 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Mon, 12 May 2025 19:57:48 +0200 Subject: [PATCH 27/85] modules/zstd/zstd_dec: Remove stream-based output interface * Remove Repacketizer proc * Remove stream-based output channels from * SequenceExecutor * ZstdDecoder Internal-tag: [#67272] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 1 - xls/modules/zstd/repacketizer.x | 215 --------------------------- xls/modules/zstd/sequence_executor.x | 124 +++++++-------- xls/modules/zstd/zstd_dec.x | 25 ++-- xls/modules/zstd/zstd_dec_test.x | 13 +- 5 files changed, 67 insertions(+), 311 deletions(-) delete mode 100644 xls/modules/zstd/repacketizer.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 77c0270bd9..355afd913d 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -872,7 +872,6 @@ zstd_dec_deps = [ ":dec_mux_dslx", ":frame_header_dec_dslx", ":raw_block_dec_dslx", - ":repacketizer_dslx", ":rle_block_dec_dslx", ":sequence_executor_dslx", "//xls/examples:ram_dslx", diff --git a/xls/modules/zstd/repacketizer.x b/xls/modules/zstd/repacketizer.x deleted file mode 100644 index f2abd638d1..0000000000 --- a/xls/modules/zstd/repacketizer.x +++ /dev/null @@ -1,215 +0,0 @@ -// 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}; - -pub 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); - } -} diff --git a/xls/modules/zstd/sequence_executor.x b/xls/modules/zstd/sequence_executor.x index d87bfc1bd6..b46180a307 100644 --- a/xls/modules/zstd/sequence_executor.x +++ b/xls/modules/zstd/sequence_executor.x @@ -851,7 +851,6 @@ pub proc SequenceExecutor; input_r: chan in; - output_s: chan out; output_mem_wr_data_in_s: chan out; ram_comp_input_s: chan> out; ram_comp_output_r: chan> in; @@ -876,7 +875,6 @@ pub proc SequenceExecutor in, - output_s: chan out, output_mem_wr_data_in_s: chan out, ram_resp_output_r: chan in, ram_resp_output_s: chan out, @@ -928,7 +926,7 @@ pub proc SequenceExecutor(output_data); + let output_mem_wr_data_in = convert_output_packet(decode_literal_packet(packet)); + if do_write_output { trace_fmt!("Sending output MemWriter data: {:#x}", output_mem_wr_data_in); } else { }; let tok2_10_1 = send_if(tok1, output_mem_wr_data_in_s, do_write_output, output_mem_wr_data_in); // Ask for response @@ -1192,7 +1188,6 @@ pub proc SequenceExecutorZstd { config( input_r: chan in, - output_s: chan out, output_mem_wr_data_in_s: chan out, looped_channel_r: chan in, looped_channel_s: chan out, @@ -1231,7 +1226,7 @@ pub proc SequenceExecutorZstd { ) { spawn SequenceExecutor ( - input_r, output_s, output_mem_wr_data_in_s, + input_r, output_mem_wr_data_in_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, @@ -1347,7 +1342,6 @@ proc SequenceExecutorLiteralsTest { terminator: chan out; input_s: chan> out; - output_r: chan in; output_mem_wr_data_in_r: chan in; print_start_s: chan<()> out; @@ -1360,7 +1354,6 @@ proc SequenceExecutorLiteralsTest { config(terminator: chan out) { let (input_s, input_r) = chan>("input"); - let (output_s, output_r) = chan("output"); let (output_mem_wr_data_in_s, output_mem_wr_data_in_r) = chan("output_mem_wr_data_in"); let (looped_channel_s, looped_channel_r) = chan("looped_channels"); @@ -1381,7 +1374,7 @@ proc SequenceExecutorLiteralsTest { TEST_RAM_ADDR_WIDTH, INIT_HB_PTR_ADDR, > ( - input_r, output_s, output_mem_wr_data_in_s, + input_r, output_mem_wr_data_in_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], @@ -1433,7 +1426,7 @@ proc SequenceExecutorLiteralsTest { ( terminator, - input_s, output_r, output_mem_wr_data_in_r, + input_s, output_mem_wr_data_in_r, print_start_s, print_finish_r, ram_rd_req_s, ram_rd_resp_r, ram_wr_req_s, ram_wr_resp_r @@ -1450,11 +1443,9 @@ proc SequenceExecutorLiteralsTest { 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); - let (tok, recv_mem_writer_data) = recv(tok, output_mem_wr_data_in_r); let expected_mem_writer_data = convert_output_packet(expected); + let (tok, recv_mem_writer_data) = recv(tok, output_mem_wr_data_in_r); assert_eq(expected_mem_writer_data, recv_mem_writer_data); } else {} }(()); @@ -1546,60 +1537,61 @@ const SEQUENCE_TEST_INPUT_SEQUENCES = SequenceExecutorPacket[11]: [ }, ]; -const SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS:ZstdDecodedPacket[11] = [ - ZstdDecodedPacket { - data: BlockData:0x8C_7E_B8_B9_7C_A3_9D_AF, - length: BlockPacketLength:64, +type TestMemWriterDataPacket = mem_writer::MemWriterDataPacket; +const SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS:TestMemWriterDataPacket[11] = [ + TestMemWriterDataPacket { + data: uN[TEST_DATA_W]:0x8C_7E_B8_B9_7C_A3_9D_AF, + length: uN[TEST_ADDR_W]:8, last: false }, - ZstdDecodedPacket { - data: BlockData:0x7D, - length: BlockPacketLength:8, + TestMemWriterDataPacket { + data: uN[TEST_DATA_W]:0x7D, + length: uN[TEST_ADDR_W]:1, last: false }, - ZstdDecodedPacket { - data: BlockData:0xB8, - length: BlockPacketLength:8, + TestMemWriterDataPacket { + data: uN[TEST_DATA_W]:0xB8, + length: uN[TEST_ADDR_W]:1, last: false }, - ZstdDecodedPacket { - data: BlockData:0xB8, - length: BlockPacketLength:8, + TestMemWriterDataPacket { + data: uN[TEST_DATA_W]:0xB8, + length: uN[TEST_ADDR_W]:1, last: false }, - ZstdDecodedPacket { - data: BlockData:0xB8_B9_7C_A3_9D, - length: BlockPacketLength:40, + TestMemWriterDataPacket { + data: uN[TEST_DATA_W]:0xB8_B9_7C_A3_9D, + length: uN[TEST_ADDR_W]:5, last: false }, - ZstdDecodedPacket { - data: BlockData:0xB9_7C_A3, - length: BlockPacketLength:24, + TestMemWriterDataPacket { + data: uN[TEST_DATA_W]:0xB9_7C_A3, + length: uN[TEST_ADDR_W]:3, last: false }, - ZstdDecodedPacket { - data: BlockData:0xB8, - length: BlockPacketLength:8, + TestMemWriterDataPacket { + data: uN[TEST_DATA_W]:0xB8, + length: uN[TEST_ADDR_W]:1, last: false }, - ZstdDecodedPacket { - data: BlockData:0x7C, - length: BlockPacketLength:8, + TestMemWriterDataPacket { + data: uN[TEST_DATA_W]:0x7C, + length: uN[TEST_ADDR_W]:1, last: false }, - ZstdDecodedPacket { - data: BlockData:0xB9_7C_A3_B8_B9_7C_A3_9D, - length: BlockPacketLength:64, + TestMemWriterDataPacket { + data: uN[TEST_DATA_W]:0xB9_7C_A3_B8_B9_7C_A3_9D, + length: uN[TEST_ADDR_W]:8, last: false }, - ZstdDecodedPacket { - data: BlockData:0x7C_B8, - length: BlockPacketLength:16, + TestMemWriterDataPacket { + data: uN[TEST_DATA_W]:0x7C_B8, + length: uN[TEST_ADDR_W]:2, last: true }, - ZstdDecodedPacket { - data: BlockData:0x9D, - length: BlockPacketLength:8, + TestMemWriterDataPacket { + data: uN[TEST_DATA_W]:0x9D, + length: uN[TEST_ADDR_W]:1, last: false } ]; @@ -1610,7 +1602,6 @@ proc SequenceExecutorSequenceTest { terminator: chan out; input_s: chan out; - output_r: chan in; output_mem_wr_data_in_r: chan in; print_start_s: chan<()> out; @@ -1623,7 +1614,6 @@ proc SequenceExecutorSequenceTest { config(terminator: chan out) { let (input_s, input_r) = chan("input"); - let (output_s, output_r) = chan("output"); let (output_mem_wr_data_in_s, output_mem_wr_data_in_r) = chan("output_mem_wr_data_in"); let (looped_channel_s, looped_channel_r) = chan("looped_channel"); @@ -1644,7 +1634,7 @@ proc SequenceExecutorSequenceTest { TEST_RAM_ADDR_WIDTH, INIT_HB_PTR_ADDR, > ( - input_r, output_s, output_mem_wr_data_in_s, + input_r, output_mem_wr_data_in_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], @@ -1696,7 +1686,7 @@ proc SequenceExecutorSequenceTest { ( terminator, - input_s, output_r, output_mem_wr_data_in_r, + input_s, output_mem_wr_data_in_r, print_start_s, print_finish_r, ram_rd_req_s, ram_rd_resp_r, ram_wr_req_s, ram_wr_resp_r ) @@ -1712,11 +1702,9 @@ proc SequenceExecutorSequenceTest { 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); - let (tok, recv_mem_writer_data) = recv(tok, output_mem_wr_data_in_r); let expected_mem_writer_data = convert_output_packet(expected); + let (tok, recv_mem_writer_data) = recv(tok, output_mem_wr_data_in_r); assert_eq(expected_mem_writer_data, recv_mem_writer_data); } else {} }(()); @@ -1726,45 +1714,45 @@ proc SequenceExecutorSequenceTest { 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); + let (tok, recv_data) = recv(tok, output_mem_wr_data_in_r); assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[0], recv_data); - let (tok, recv_data) = recv(tok, output_r); + let (tok, recv_data) = recv(tok, output_mem_wr_data_in_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); + let (tok, recv_data) = recv(tok, output_mem_wr_data_in_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); + let (tok, recv_data) = recv(tok, output_mem_wr_data_in_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); + let (tok, recv_data) = recv(tok, output_mem_wr_data_in_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); + let (tok, recv_data) = recv(tok, output_mem_wr_data_in_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); + let (tok, recv_data) = recv(tok, output_mem_wr_data_in_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); + let (tok, recv_data) = recv(tok, output_mem_wr_data_in_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); + let (tok, recv_data) = recv(tok, output_mem_wr_data_in_r); assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[8], recv_data); - let (tok, recv_data) = recv(tok, output_r); + let (tok, recv_data) = recv(tok, output_mem_wr_data_in_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); + let (tok, recv_data) = recv(tok, output_mem_wr_data_in_r); assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[10], recv_data); // Print RAM content diff --git a/xls/modules/zstd/zstd_dec.x b/xls/modules/zstd/zstd_dec.x index 7fc5fb260e..a5f5460905 100644 --- a/xls/modules/zstd/zstd_dec.x +++ b/xls/modules/zstd/zstd_dec.x @@ -31,7 +31,6 @@ import xls.modules.zstd.raw_block_dec; import xls.modules.zstd.rle_block_dec; import xls.modules.zstd.dec_mux; import xls.modules.zstd.sequence_executor; -import xls.modules.zstd.repacketizer; type BlockSize = common::BlockSize; type BlockType = common::BlockType; @@ -846,11 +845,15 @@ proc ZstdDecoderInternalTest { status: RawBlockDecoderStatus::OKAY, }); - let (tok, ()) = recv(tok, notify_r); - - let (tok, _) = recv(tok, output_mem_wr_req_r); + let (tok, mem_wr_req) = recv(tok, output_mem_wr_req_r); + assert_eq(mem_wr_req, MemWriterReq { + addr: uN[TEST_AXI_ADDR_W]:0x2000, + length: uN[TEST_AXI_ADDR_W]:200 + }); let tok = send(tok, output_mem_wr_resp_s, MemWriterResp {status: mem_writer::MemWriterRespStatus::OKAY}); + let (tok, ()) = recv(tok, notify_r); + send(tok, terminator, true); } } @@ -977,8 +980,6 @@ pub proc ZstdDecoder< ram_wr_resp_6_r: chan in, ram_wr_resp_7_r: chan in, - // Decoder output - output_s: chan out, notify_s: chan<()> out, reset_s: chan<()> out, ) { @@ -1090,12 +1091,10 @@ pub proc ZstdDecoder< // Sequence Execution let (seq_exec_looped_s, seq_exec_looped_r) = chan("seq_exec_looped"); - let (seq_exec_output_s, seq_exec_output_r) = chan("seq_exec_output"); let (output_mem_wr_data_in_s, output_mem_wr_data_in_r) = chan("output_mem_wr_data_in"); spawn sequence_executor::SequenceExecutor( - seq_exec_input_r, seq_exec_output_s, - output_mem_wr_data_in_s, + seq_exec_input_r, output_mem_wr_data_in_s, seq_exec_looped_r, seq_exec_looped_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, @@ -1107,10 +1106,6 @@ pub proc ZstdDecoder< ram_wr_resp_4_r, ram_wr_resp_5_r, ram_wr_resp_6_r, ram_wr_resp_7_r ); - // Repacketizer - - spawn repacketizer::Repacketizer(seq_exec_output_r, output_s); - // Zstd Decoder Control let (output_mem_wr_req_s, output_mem_wr_req_r) = chan("output_mem_wr_req"); let (output_mem_wr_resp_s, output_mem_wr_resp_r) = chan("output_mem_wr_resp"); @@ -1309,8 +1304,6 @@ proc ZstdDecoderInst { ram_wr_resp_6_r: chan in, ram_wr_resp_7_r: chan in, - // Decoder output - output_s: chan out, notify_s: chan<()> out, reset_s: chan<()> out, ) { @@ -1332,7 +1325,7 @@ proc ZstdDecoderInst { 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, - output_s, notify_s, reset_s, + notify_s, reset_s, ); } diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x index a57f7e499e..bd90210ef1 100644 --- a/xls/modules/zstd/zstd_dec_test.x +++ b/xls/modules/zstd/zstd_dec_test.x @@ -118,7 +118,6 @@ proc ZstdDecoderTest { raw_wr_resp_bh_r: chan in; raw_wr_resp_raw_r: chan in; - output_r: chan in; notify_r: chan<()> in; reset_r: chan<()> in; @@ -164,7 +163,6 @@ proc ZstdDecoderTest { let (ram_wr_resp_bh_s, ram_wr_resp_bh_r) = chan("ram_wr_resp_bh"); let (ram_wr_resp_raw_s, ram_wr_resp_raw_r) = chan("ram_wr_resp_raw"); - let (output_s, output_r) = chan("output"); let (notify_s, notify_r) = chan<()>("notify"); let (reset_s, reset_r) = chan<()>("reset"); @@ -186,7 +184,7 @@ proc ZstdDecoderTest { 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], - output_s, notify_s, reset_s, + notify_s, reset_s, ); spawn ram::RamModel< @@ -277,7 +275,7 @@ proc ZstdDecoderTest { ram_rd_req_r, ram_rd_resp_s, ram_wr_req_r, ram_wr_resp_s, ram_wr_req_fh_s, ram_wr_req_bh_s, ram_wr_req_raw_s, ram_wr_resp_fh_r, ram_wr_resp_bh_r, ram_wr_resp_raw_r, - output_r, notify_r, reset_r, + notify_r, reset_r, ) } @@ -361,13 +359,6 @@ proc ZstdDecoderTest { let (tok, _) = recv(tok, csr_axi_b_r); let decomp_frame = zstd_frame_testcases::DECOMPRESSED_FRAMES[test_i]; - let tok = for (decomp_i, tok): (u32, token) in range(u32:0, decomp_frame.array_length) { - let (tok, decomp_packet) = recv(tok, output_r); - assert_eq(decomp_packet.data, decomp_frame.data[decomp_i]); - assert_eq(decomp_packet.last, decomp_i == (decomp_frame.length - u32:1) / u32:8); - tok - }(tok); - // Test ZstdDecoder memory output interface // Mock the output memory buffer as a DSLX array // It is required to handle AXI write transactions and to write the incoming data to From d9bb1015381c3da979c22c344803227579b9fc17 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 19 Nov 2024 12:31:15 +0100 Subject: [PATCH 28/85] modules/zstd/README: Update output interface description Internal-tag: [#67272] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/README.md | 43 ++++++++---------- xls/modules/zstd/img/ZSTD_decoder.png | Bin 315264 -> 312837 bytes xls/modules/zstd/img/ZSTD_decoder_wrapper.png | Bin 126231 -> 123219 bytes 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/xls/modules/zstd/README.md b/xls/modules/zstd/README.md index e606a02918..0842d65c49 100644 --- a/xls/modules/zstd/README.md +++ b/xls/modules/zstd/README.md @@ -5,19 +5,17 @@ It implements the [RFC 8878](https://www.rfc-editor.org/rfc/rfc8878.html) decomp An overview of the decoder architecture is presented in the diagram below. The decoder comprises: * Memory Readers -* Memory Writer[^2], +* Memory Writer, * Control and Status Registers, * Frame Header Decoder, * Block Header Decoder, * 3 types of processing units: RAW-, RLE-, and Compressed Block Decoders[^1], * Command Aggregator, -* Repacketizer. The Decoder interacts with the environment through a set of ports: * Memory Interface (AXI) * CSR Interface (AXI) * Notify line -* Stream Output The software controls the core through registers accessible through the `CSR Interface`. The CSRs are used to configure the decoder and to start the decoding process. @@ -33,8 +31,8 @@ Once the decoding process is started, the decoder: 4. Decodes the Block Data with the correct processing unit picked based on the Block Type from the Block Header, 4. Aggregates the processing unit results in the correct order into a stream and routes it to the history buffer, 5. Assembles the data block outputs based on the history buffer contents and updates the history, -6. Prepares the final output of the decoder, -7. (Optional) Calculates checksum and compares it against the checksum read from the frame.[^3] +6. Prepares the final output of the decoder and writes it to the memory, +7. (Optional) Calculates checksum and compares it against the checksum read from the frame.[^2] ![](img/ZSTD_decoder.png) @@ -115,31 +113,41 @@ stateDiagram [*] --> IDLE IDLE --> READ_CONFIG: Start + IDLE --> mem_write_done READ_CONFIG --> DECODE_FRAME_HEADER + READ_CONFIG --> mem_write_done DECODE_FRAME_HEADER --> DECODE_BLOCK_HEADER DECODE_FRAME_HEADER --> ERROR + DECODE_FRAME_HEADER --> mem_write_done DECODE_BLOCK_HEADER --> DECODE_RAW_BLOCK DECODE_BLOCK_HEADER --> DECODE_RLE_BLOCK DECODE_BLOCK_HEADER --> DECODE_COMPRESED_BLOCK DECODE_BLOCK_HEADER --> ERROR + DECODE_BLOCK_HEADER --> mem_write_done state if_block_last <> DECODE_RAW_BLOCK --> ERROR DECODE_RAW_BLOCK --> if_block_last + DECODE_RAW_BLOCK --> mem_write_done DECODE_RLE_BLOCK --> ERROR DECODE_RLE_BLOCK --> if_block_last + DECODE_RLE_BLOCK --> mem_write_done DECODE_COMPRESSED_BLOCK --> ERROR DECODE_COMPRESSED_BLOCK --> if_block_last + DECODE_COMPRESSED_BLOCK --> mem_write_done if_block_last --> DECODE_BLOCK_HEADER: Not last block in the frame if_block_last --> DECODE_CHECKSUM: Last block in the frame - DECODE_CHECKSUM --> FINISH + DECODE_CHECKSUM --> mem_write_done + + state mem_write_done <> + mem_write_done --> FINISH: Frame written to the memory FINISH --> IDLE ERROR --> IDLE @@ -154,9 +162,9 @@ Once received, the module fetches the data from the memory starting from the add `MemReader` procs are used by those modules to communicate with the external memory through the AXI interface. Internal modules decode the acquired parts of the frame and return responses with the results back to the top level proc. -The processing units also output the decoded blocks of data through a stream-based interface to `SequenceExecutor` and further to `Repacketizer` procs. -Those procs perform the last steps of the decoding before the final output is sent out back to the memory under the address stored in the `Output Buffer` CSR or through the stream-based output channel. -Once the decoding process is completed, the decoder sends the `Notify` signal and transitions back to the `IDLE` state. +The processing units also output the decoded blocks of data through a stream-based interface to the `SequenceExecutor` proc. +This proc performs the last step of the decoding before the final output is sent out back to the memory under the address stored in the `Output Buffer` CSR by the `MemWriter` proc. +Once the decoding process is completed and the decoded frame is written back to the memory, the decoder sends the `Notify` signal and transitions back to the `IDLE` state. ### Internal modules @@ -212,19 +220,6 @@ This stage will show the following behavior, depending on the tag: * 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. -#### 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 the 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 that 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. - ### Compressed block decoder architecture[^1] 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 the expected data output. @@ -357,8 +352,8 @@ Currently, due to the restrictions from the ZSTD frame generator, it is possible ZstdDecoder's main communication interfaces are the AXI buses. Due to the way XLS handles the codegen of DSLX channels that model the AXI channels, the particular ports of the AXI channels are not represented correctly. This enforces the introduction of a Verilog wrapper that maps the ports generated by XLS into proper AXI ports (see AXI peripherals [README](memory/README.md) for more information). -Additionally, the wrapper is used to mux multiple AXI interfaces from `Memory Readers` into a single outside-facing AXI interface (`Memory Interface`) that can be connected to the external memory. -The mux is implemented by a third-party [AXI Interconnect](https://github.com/alexforencich/verilog-axi). +Additionally, the wrapper is used to mux multiple AXI interfaces from `Memory Readers` and `Memory Writer` into a single outside-facing AXI interface (`Memory Interface`) that can be connected to the external memory. +The mux is implemented by a third-party [AXI Crossbar](https://github.com/alexforencich/verilog-axi). ![](img/ZSTD_decoder_wrapper.png) diff --git a/xls/modules/zstd/img/ZSTD_decoder.png b/xls/modules/zstd/img/ZSTD_decoder.png index 494926d89f7108623b2a4d958d1980b9a34f75cc..d52c11b3d8b8df3be0739ec33277925846f107f0 100644 GIT binary patch literal 312837 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℞*iAO_5(;El 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†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 z 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 literal 315264 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

      )^@@|1jzus^eSWD7<{w!; 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

    1. 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

      5#S z|G?f&49-w3G&}b4l^!i7vDPEiejn;zS?Or2=1?gdPb9~*e? z*HCf@G%J~>;qv{zt)2Ga?no?;Or!|K+-S~#bI~%YdAj$WP$4lPQbm-Zu`w+sbEFDh z;kXt;K_331;im4V*3w{;f13z;51aUrHM)F!@vu_H5tB@<9X)T7EU4>($L% zEVdkYcmIp+mCupx6Lo%p7X>QW{loY$4&ZG1uE_2#FHOor+~gwLVG~CuUoj#qfd)NdVwr(dP(aCdDNKeT^io6o_@{MHfLi@dJ%RCNCmWNia?GzavE`m za5JJOY-^9Zz%(4(x4*Vr1n2_UN?8I@gY{WQAXtg+B#X3_tpCaDP34ab22179V-}Mmmgh(A1n+zW2kg51 z_S*vLM>_o8mlJ}~%>mCzqh)L{SSzs}A~&`nc_(8rrH2M4a>T2fHD?1mGMd~Agh%Hh zGw0vqYVtlkf+HqL!CIA8O=6IkT@yp`nsFFBGH;w3`0+H!Jf6AOTRmt;r%fGHA@AS! zykd=glU@TZm_r)=fU36dh`m8Ro5e#>(*f-D)VF0N=qWyRl!>LBIdd_DZTcR-5-Cl{!a=u=AIna3BJLD)R;#v?;IU74K_|fXflit&9bN<2|G4Hpo`IGI5RQOaq z@*7~5!lywNW!LFByc@Hy!Hj0er$D};1IRY*f*a)xR5{`$MQ1-x{#OwEy()uIlK+{; zrstuyW?=8NXGFvu36d0n!|ly5m#%DIs&e5W(hE?qZGR~tCztaGhy8VNm(2 zxOw~jX4iDs(RRY;c*p6*-3o4;@2`SpX`&B*1;04=^S!rDno6{QYmxGsZ#_dA$03qW zYnWrkIX0Y+k9XOec4aO0GtV0cIZf>u9*fzPhF-k!X>v_NIH1a@{We1UGZ%n*+oy(b zi7eF^rG4Uy%)tlN{clauJ^}RN+1!faVg%5G(bOS}IpPb%g;>5*0rC#ecEbVc&%!^7 zsb7mjz!5Z}upt+4y>iTOp_q*0jxvWZ6?jnD%&(QauWpgs1&Z`zC|X#f3mWlAAJ_F1Vk#<}_6&Xxe}6HR9rY zo*$O7HB30bT+2Q;X{=Lz|Fy*BWhINQLSYf7>RVuF-HdVO4_r4h~hu;L;icg)3CI^`he_ur_RJovJTc0!(u)lZ&y^ zUy7?ER>+!!oKt^ZlY4*5TT>jHEm*i{+W6RT-}B-_#-hf97kHcF8BnaNkeI%uFL3w8 z2l?Uh%@Ur)-Hwstt%E@aTG48cE?2R8V(m@uC&EOIzs49<|4fuG*)4JLly^@#l;1wy zIyJtvofw{qb&l0=v3MzKc^P-fJBx5ntPigy?A%*S+D)Y1ej9Rc*?Op+6-_d+ymF(9l~(SuBk|ixG24|o zJdWS^+Y&42n`P1(f6#PfZiH&_2*iH40w{j50HLTnDvYt#(+3nj<&uIw_=;swz5pJu z*;+Owi1;D*>hz&J|EZ7nu}szTD|!O@6<+ubW^HvJ*!f7#_>G>&h5sR^lFYKxBpy&( zcpHS(msn?etMG0Ymr=DwU;2c7wn8FVt5ktO>}WDIg2$31=l;)LE0N=}qM1rs9!$kN z^~Dq0vHsyMF-tQSTzm7)oT+#X$x%utEAd1x)F|D9HmvXZZre67s|)|Keo+92ba#=RWn*BUO9PXi

      ?64~!?O2TK%w?4tgl5& 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 diff --git a/xls/modules/zstd/img/ZSTD_decoder_wrapper.png b/xls/modules/zstd/img/ZSTD_decoder_wrapper.png index fec37b51a4c73771c92a9ed7e9df707ede385aa7..293420234fbe5d0d33195a3e10a6fc4bf331271d 100644 GIT binary patch delta 71433 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 YpuMjmWa5&w1N^6WLG65&tcm~s0}H=bM*si- delta 73872 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

        8DD_W@5MMJbjD5;*X% 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 TPBVD`{HJh9RW|#g$=&}2y!p-? From e72ee793f31356d2f72e661123bfb9b5a5fea4f7 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 31 Dec 2024 17:24:34 +0100 Subject: [PATCH 29/85] modules/zstd/memory/axi_stream_remove_empty: add fifo module definition for verilog library Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/memory/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/xls/modules/zstd/memory/BUILD b/xls/modules/zstd/memory/BUILD index 39a111dfe5..e9ab9c0119 100644 --- a/xls/modules/zstd/memory/BUILD +++ b/xls/modules/zstd/memory/BUILD @@ -242,6 +242,7 @@ verilog_library( name = "axi_stream_remove_empty_verilog_lib", srcs = [ ":axi_stream_remove_empty.v", + "//xls/modules/zstd:xls_fifo_wrapper.v", ], tags = ["manual"], ) From f8a83dab36cc8c8cd6885b2d145be30a85942b43 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Mon, 13 Jan 2025 13:39:49 +0100 Subject: [PATCH 30/85] modules/zstd/zstd_dec: use regular cast instead of checked_cast on status enums Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/zstd_dec.x | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/xls/modules/zstd/zstd_dec.x b/xls/modules/zstd/zstd_dec.x index a5f5460905..7bbd356cf4 100644 --- a/xls/modules/zstd/zstd_dec.x +++ b/xls/modules/zstd/zstd_dec.x @@ -351,7 +351,7 @@ proc ZstdDecoderInternal< let csr_wr_req_valid = true; let csr_wr_req = CsrWrReq { csr: csr(Csr::STATUS), - value: checked_cast(status), + value: status as Data, }; State { fsm: Fsm::READ_CONFIG, csr_wr_req, csr_wr_req_valid, conf_cnt: CSR_REQS_MAX, ..zero!() } @@ -382,7 +382,7 @@ proc ZstdDecoderInternal< let csr_wr_req_valid = all_collected; let csr_wr_req = CsrWrReq { csr: csr(Csr::STATUS), - value: checked_cast(status), + value: status as Data, }; State { @@ -405,7 +405,7 @@ proc ZstdDecoderInternal< let csr_wr_req_valid = (fh_resp_valid); let csr_wr_req = CsrWrReq { csr: csr(Csr::STATUS), - value: checked_cast(status), + value: status as Data, }; let fsm = match (fh_resp_valid, error) { @@ -433,7 +433,7 @@ proc ZstdDecoderInternal< let csr_wr_req_valid = (bh_resp_valid); let csr_wr_req = CsrWrReq { csr: csr(Csr::STATUS), - value: checked_cast(status), + value: status as Data, }; let fsm = match (bh_resp_valid, error, bh_resp.header.btype) { @@ -484,7 +484,7 @@ proc ZstdDecoderInternal< let csr_wr_req_valid = (raw_resp_valid); let csr_wr_req = CsrWrReq { csr: csr(Csr::STATUS), - value: checked_cast(status), + value: status as Data, }; let fsm = match (raw_resp_valid, error, state.block_last) { @@ -517,7 +517,7 @@ proc ZstdDecoderInternal< let csr_wr_req_valid = (rle_resp_valid); let csr_wr_req = CsrWrReq { csr: csr(Csr::STATUS), - value: checked_cast(status), + value: status as Data, }; let fsm = match (rle_resp_valid, error, state.block_last) { @@ -566,7 +566,7 @@ proc ZstdDecoderInternal< let csr_wr_req_valid = true; let csr_wr_req = CsrWrReq { csr: csr(Csr::STATUS), - value: checked_cast(ZstdDecoderStatus::IDLE), + value: ZstdDecoderStatus::IDLE as Data, }; State { fsm: Fsm::IDLE, csr_wr_req, csr_wr_req_valid, ..zero!() } From c2471c099b6340c4ed3132e9b1a0232b8ba642b3 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Thu, 28 Nov 2024 11:51:03 +0100 Subject: [PATCH 31/85] modules/zstd: express data length in bytes Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/dec_mux.x | 144 +++++++++++++++---------------- xls/modules/zstd/raw_block_dec.x | 8 +- xls/modules/zstd/rle_block_dec.x | 12 +-- 3 files changed, 82 insertions(+), 82 deletions(-) diff --git a/xls/modules/zstd/dec_mux.x b/xls/modules/zstd/dec_mux.x index 429a908c44..ef24e9b542 100644 --- a/xls/modules/zstd/dec_mux.x +++ b/xls/modules/zstd/dec_mux.x @@ -234,19 +234,19 @@ proc DecoderMuxTest { 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 }); + 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:4 }}); + 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:4 }}); + 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:4 }}); + 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:4 }}); + 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:4 }}); + 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:4 }}); + + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x11111111, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x22222222, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x33333333, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xAAAAAAAA, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xBBBBBBBB, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: true, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x00000000, length: CopyOrMatchLength:4 }); send(tok, terminator, true); } @@ -274,16 +274,16 @@ proc DecoderMuxEmptyRawBlocksTest { 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:0, last: bool: true, last_block: bool: false, data: BlockData:0x11111111, length: BlockPacketLength:4 }}); + 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:4 }}); 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:3, last: bool: true, last_block: bool: false, data: BlockData:0x33333333, length: BlockPacketLength:4 }}); 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:0x11111111, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x22222222, length: CopyOrMatchLength:4 }); 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: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x33333333, length: CopyOrMatchLength:4 }); 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); @@ -312,16 +312,16 @@ proc DecoderMuxEmptyRleBlocksTest { 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:0, last: bool: true, last_block: bool: false, data: BlockData:0x11111111, length: BlockPacketLength:4 }}); + 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:4 }}); 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:3, last: bool: true, last_block: bool: false, data: BlockData:0x33333333, length: BlockPacketLength:4 }}); 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:0x11111111, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x22222222, length: CopyOrMatchLength:4 }); 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: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x33333333, length: CopyOrMatchLength:4 }); 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); @@ -350,21 +350,21 @@ proc DecoderMuxEmptyBlockBetweenRegularBlocksOnTheSameInputChannelTest { 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:0, last: bool: false, last_block: bool: false, data: BlockData:0x11111111, length: BlockPacketLength:4 }}); + 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:4 }}); + 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:4 }}); 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 = 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:4 }}); + 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:4 }}); + 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:4 }}); - 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:0x11111111, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x22222222, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x33333333, length: CopyOrMatchLength:4 }); 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 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xAAAAAAAA, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xBBBBBBBB, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: true, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x00000000, length: CopyOrMatchLength:4 }); send(tok, terminator, true); } @@ -392,21 +392,21 @@ proc DecoderMuxEmptyBlockBetweenRegularBlocksOnDifferentInputChannelsTest { 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: false, last_block: bool: false, data: BlockData:0x11111111, length: BlockPacketLength:4 }}); + 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:4 }}); 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 = 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:4 }}); + 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:4 }}); + 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:4 }}); + 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:4 }}); + + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x11111111, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x22222222, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x33333333, length: CopyOrMatchLength:4 }); 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 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xAAAAAAAA, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xBBBBBBBB, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: true, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x00000000, length: CopyOrMatchLength:4 }); send(tok, terminator, true); } @@ -435,22 +435,22 @@ proc DecoderMuxMultipleFramesTest { 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 }}); + 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:4 }}); + 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:4 }}); + 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:4 }}); + 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:4 }}); + 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:4 }}); + 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:4 }}); + 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:4 }}); + 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:4 }}); + 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:4 }}); // 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, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:0, last: bool: true, last_block: bool: false, data: BlockData:0x44444444, length: BlockPacketLength:4 }}); 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:0, last: bool: true, last_block: bool: false, data: BlockData:0x55555555, length: BlockPacketLength:4 }}); 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 }}); @@ -462,22 +462,22 @@ proc DecoderMuxMultipleFramesTest { 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 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x11111111, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x22222222, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x33333333, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xAAAAAAAA, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xBBBBBBBB, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xCCCCCCCC, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xDDDDDDDD, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xEEEEEEEE, length: CopyOrMatchLength:4 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: true, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xFFFFFFFF, length: CopyOrMatchLength:4 }); // 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:0x44444444, length: CopyOrMatchLength:4 }); 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:0x55555555, length: CopyOrMatchLength:4 }); 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 }); diff --git a/xls/modules/zstd/raw_block_dec.x b/xls/modules/zstd/raw_block_dec.x index 669b66d5b1..3617a41783 100644 --- a/xls/modules/zstd/raw_block_dec.x +++ b/xls/modules/zstd/raw_block_dec.x @@ -116,7 +116,7 @@ pub proc RawBlockDecoder { last_block: state.last_block, id: state.id, data: checked_cast(mem_resp.data), - length: checked_cast(mem_resp.length ++ u3:0), + length: checked_cast(mem_resp.length), }, }; @@ -234,7 +234,7 @@ proc RawBlockDecoderTest { last_block: false, id: u32:0, data: Data:0x1122_3344, - length: Length:64, + length: Length:8, }, }); @@ -269,7 +269,7 @@ proc RawBlockDecoderTest { last_block: true, id: u32:1, data: Data:0x1122_3344_5566_7788, - length: Length:64, + length: Length:8, }, }); @@ -281,7 +281,7 @@ proc RawBlockDecoderTest { last_block: true, id: u32:1, data: Data:0xAA_BBCC_DDEE_FF99, - length: Length:56, + length: Length:7, }, }); diff --git a/xls/modules/zstd/rle_block_dec.x b/xls/modules/zstd/rle_block_dec.x index c5529c978b..4982023fdc 100644 --- a/xls/modules/zstd/rle_block_dec.x +++ b/xls/modules/zstd/rle_block_dec.x @@ -87,7 +87,7 @@ pub proc RleBlockDecoder { last_block: req.last_block, id: req.id, data: checked_cast(data), - length: checked_cast(length << 3), + length: checked_cast(length), } }; @@ -153,7 +153,7 @@ proc RleBlockDecoderTest { last_block: true, id: u32:5, data: BlockData:0xABAB_ABAB_ABAB_ABAB, - length: BlockPacketLength:64 + length: BlockPacketLength:8 } }); @@ -165,7 +165,7 @@ proc RleBlockDecoderTest { last_block: true, id: u32:5, data: BlockData:0xABAB_ABAB_ABAB_ABAB, - length: BlockPacketLength:64 + length: BlockPacketLength:8 } }); @@ -178,7 +178,7 @@ proc RleBlockDecoderTest { last_block: true, id: u32:5, data: BlockData:0xABAB_ABAB_ABAB_ABAB, - length: BlockPacketLength:64 + length: BlockPacketLength:8 } }); @@ -191,7 +191,7 @@ proc RleBlockDecoderTest { last_block: true, id: u32:5, data: BlockData:0xABAB_ABAB_ABAB_ABAB, - length: BlockPacketLength:64 + length: BlockPacketLength:8 } }); @@ -204,7 +204,7 @@ proc RleBlockDecoderTest { last_block: true, id: u32:5, data: BlockData:0xABAB_ABAB_ABAB_ABAB, - length: BlockPacketLength:64 + length: BlockPacketLength:8 } }); From d98bfd844c41017ee1c5b88c0ef135310e101cfd Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Thu, 16 May 2024 09:27:40 +0200 Subject: [PATCH 32/85] modules/zstd: Add buffer implementing desired API Add a buffering proc that implements the desired API of the final buffering mechanism. A more efficient implementation will be provided later. Internal-tag: [#55149] Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 72 ++++++++++ xls/modules/zstd/buffer.x | 21 +++ xls/modules/zstd/shift_buffer.x | 246 ++++++++++++++++++++++++++++++++ 3 files changed, 339 insertions(+) create mode 100644 xls/modules/zstd/shift_buffer.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 355afd913d..976a8107c3 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -863,6 +863,78 @@ place_and_route( target_die_utilization_percentage = "10", ) +xls_dslx_library( + name = "shift_buffer_dslx", + srcs = ["shift_buffer.x"], + deps = [ + ":buffer_dslx", + ], +) + +xls_dslx_test( + name = "shift_buffer_dslx_test", + library = ":shift_buffer_dslx", +) + +xls_dslx_verilog( + name = "shift_buffer_verilog", + codegen_args = { + "module_name": "ShiftBuffer", + "delay_model": "asap7", + "pipeline_stages": "2", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "ShiftBufferInst", + library = ":shift_buffer_dslx", + opt_ir_args = { + "inline_procs": "true", + "top": "__shift_buffer__ShiftBufferInst__ShiftBuffer_0__128_64_7_next", + }, + verilog_file = "shift_buffer.v", +) + +xls_benchmark_ir( + name = "shift_buffer_opt_ir_benchmark", + src = ":shift_buffer_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "2", + "delay_model": "asap7", + }, +) + +verilog_library( + name = "shift_buffer_verilog_lib", + srcs = [ + ":shift_buffer.v", + ], +) + +synthesize_rtl( + name = "shift_buffer_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "ShiftBuffer", + deps = [ + ":shift_buffer_verilog_lib", + ], +) + +benchmark_synth( + name = "shift_buffer_benchmark_synth", + synth_target = ":shift_buffer_synth_asap7", +) + +place_and_route( + name = "shift_buffer_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":shift_buffer_synth_asap7", + target_die_utilization_percentage = "10", +) + zstd_dec_deps = [ ":axi_csr_accessor_dslx", ":block_header_dec_dslx", diff --git a/xls/modules/zstd/buffer.x b/xls/modules/zstd/buffer.x index d4f9acfa20..218f6f2c8d 100644 --- a/xls/modules/zstd/buffer.x +++ b/xls/modules/zstd/buffer.x @@ -119,6 +119,27 @@ fn test_buffer_append() { assert_eq(buffer, Buffer { content: u32:0xDEADBEEF, length: u32:32 }); } +pub fn buffer_append_with_length (buffer: Buffer, data: bits[CAPACITY], length: u32) -> Buffer { + if buffer.length + length > CAPACITY { + fail!("not_enough_space", buffer) + } else { + let mask = (bits[CAPACITY]:1 << length) - bits[CAPACITY]:1; + Buffer { + content: ((data & mask) << buffer.length) | buffer.content, + length: length + buffer.length + } + } +} + +#[test] +fn test_buffer_append_with_length() { + let buffer = Buffer { content: u32:0, length: u32:0 }; + let buffer = buffer_append_with_length(buffer, u32:0xBEEF, u32:8); + assert_eq(buffer, Buffer { content: u32:0xEF, length: u32:8 }); + let buffer = buffer_append_with_length(buffer, u32:0xDEAD, u32:8); + assert_eq(buffer, Buffer { content: u32:0xADEF, length: u32:16 }); +} + // 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. diff --git a/xls/modules/zstd/shift_buffer.x b/xls/modules/zstd/shift_buffer.x new file mode 100644 index 0000000000..fd520774cb --- /dev/null +++ b/xls/modules/zstd/shift_buffer.x @@ -0,0 +1,246 @@ +// 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 buffering proc responsible for +// reading the data stream and dividing it into the smallest packets of the +// requested size. + +import std; +import xls.modules.zstd.buffer; + +fn buffer_width(data_width: u32) -> u32 { data_width * u32:2 } + +pub struct ShiftBufferInput { + data: uN[DATA_W], + length: uN[LENGTH_W], + last: bool, +} + +pub struct ShiftBufferOutput { + data: uN[DATA_W], + length: uN[LENGTH_W], + last: bool, +} + +pub struct ShiftBufferCtrl { + length: uN[LENGTH_W], +} + +enum ShiftBufferStatus : u1 { + REFILL = 0, + FLUSH = 1, +} + +struct ShiftBufferState { + status: ShiftBufferStatus, + buff: buffer::Buffer, + ctrl: ShiftBufferCtrl, + ctrl_valid: bool, + last: bool, +} + +pub fn mask_data (data: bits[DATA_W], length: bits[LENGTH_W]) -> bits[DATA_W] { + type Data = bits[DATA_W]; + type Length = bits[LENGTH_W]; + + let mask = if length != Length:0 { (Data:1 << length) - Data:1 } else { Data:0 }; + data & mask +} + +pub proc ShiftBuffer< + DATA_W: u32, LENGTH_W: u32, + BUFFER_W: u32 = {buffer_width(DATA_W)} +> { + type Data = uN[DATA_W]; + type Length = uN[LENGTH_W]; + type BufferData = uN[BUFFER_W]; + type Input = ShiftBufferInput; + type Ctrl = ShiftBufferCtrl; + type Output = ShiftBufferOutput; + type State = ShiftBufferState; + type Status = ShiftBufferStatus; + + in_data_r: chan in; + in_ctrl_r: chan in; + out_data_s: chan out; + + config( + in_data_r: chan in, + in_ctrl_r: chan in, + out_data_s: chan out, + ) { (in_data_r, in_ctrl_r, out_data_s) } + + init { zero!() } + + next(state: State) { + + let tok0 = join(); + + // Receive control + let (tok1_0, ctrl, ctrl_valid) = + recv_if_non_blocking(tok0, in_ctrl_r, !state.ctrl_valid, state.ctrl); + + // Receive data + let can_fit = buffer::buffer_can_fit(state.buff, Data:0); + let do_recv_data = can_fit && state.status == Status::REFILL; + let (tok1_1, recv_data, recv_data_valid) = + recv_if_non_blocking(tok0, in_data_r, do_recv_data, zero!()); + + let tok1 = join(tok1_0, tok1_1); + + // Handle the request + // Uses buffer from the previous next evaluation (from state) to prevent + // creating long combinatorial logic that would allow receiving and + // sending back the data in the same cycle. + + let has_valid_request = ctrl_valid || state.ctrl_valid; + let has_enough_data = buffer::buffer_has_at_least(state.buff, ctrl.length as u32); + + let (data, do_send, new_buff, new_ctrl, new_ctrl_valid) = if has_valid_request && has_enough_data { + let (buff, data) = buffer::buffer_pop(state.buff, ctrl.length as u32); + let last = buff.length == u32:0 && state.last; + let output = Output { data: data as Data, length: ctrl.length, last }; + let do_send = ctrl.length != Length:0; + (output, do_send, buff, zero!(), false) + } else { + (zero!(), false, state.buff, ctrl, has_valid_request) + }; + + let tok2_0 = send_if(tok1, out_data_s, do_send, data); + + // Handle input data + let (new_buff, new_last) = if can_fit && recv_data_valid { + ( + buffer::buffer_append_with_length( + new_buff, recv_data.data as BufferData, recv_data.length as u32), + recv_data.last, + ) + } else { + (new_buff, state.last) + }; + + // Handle state change + let new_status = if state.status == Status::REFILL && recv_data_valid && recv_data.last { + Status::FLUSH + } else if state.status == Status::FLUSH && state.buff.length == ctrl.length as u32 { + Status::REFILL + } else { + state.status + }; + + State { + status: new_status, + buff: new_buff, + ctrl: new_ctrl, + ctrl_valid: new_ctrl_valid, + last: new_last + } + } +} + +const INST_DATA_W = u32:64; +const INST_LENGTH_W = std::clog2(INST_DATA_W) + u32:1; + +proc ShiftBufferInst { + type Input = ShiftBufferInput; + type Ctrl = ShiftBufferCtrl; + type Output = ShiftBufferOutput; + + config( + data_r: chan in, + ctrl_r: chan in, + out_s: chan out + ) { + spawn ShiftBuffer(data_r, ctrl_r, out_s); + } + + init { } + + next(state: ()) { } +} + +const TEST_DATA_W = u32:64; +const TEST_LENGTH_W = std::clog2(TEST_DATA_W) + u32:1; + +#[test_proc] +proc ShiftBufferTest { + type Input = ShiftBufferInput; + type Ctrl = ShiftBufferCtrl; + type Output = ShiftBufferOutput; + type Data = uN[TEST_DATA_W]; + type Length = uN[TEST_LENGTH_W]; + + terminator: chan out; + data_s: chan out; + ctrl_s: chan out; + out_r: chan in; + + config(terminator: chan out) { + let (data_s, data_r) = chan("in_data"); + let (ctrl_s, ctrl_r) = chan("in_ctrl"); + let (out_s, out_r) = chan("out_data"); + + spawn ShiftBuffer(data_r, ctrl_r, out_s); + (terminator, data_s, ctrl_s, out_r) + } + + init { } + + next(state: ()) { + + let tok = join(); + let tok = send(tok, data_s, Input { data: Data:0xDD_44, length: Length:16, last: false }); + let tok = send(tok, data_s, Input { data: Data:0xAA_11_BB_22_CC_33, length: Length:48, last: false }); + let tok = send(tok, data_s, Input { data: Data:0xEE_55_FF_66_00_77_11_88, length: Length:64, last: false }); + + let tok = send(tok, ctrl_s, Ctrl { length: Length:8 }); + let (tok, output) = recv(tok, out_r); + assert_eq(output, Output { data: Data:0x44, length: Length:8, last: false }); + + let tok = send(tok, ctrl_s, Ctrl { length: Length:4 }); + let (tok, output) = recv(tok, out_r); + assert_eq(output, Output { data: Data:0xD, length: Length:4, last: false }); + + let tok = send(tok, ctrl_s, Ctrl { length: Length:64 }); + let (tok, output) = recv(tok, out_r); + assert_eq(output, Output { data: Data:0x18_8A_A1_1B_B2_2C_C3_3D, length: Length:64, last: false }); + + let tok = send(tok, data_s, Input { data: Data:0x44_BB_55_CC, length: Length:32, last: false }); + let tok = send(tok, data_s, Input { data: Data:0x22_99_33_AA, length: Length:32, last: true }); + let tok = send(tok, data_s, Input { data: Data:0x66_DD_77_EE_88_FF_99_00, length: Length:64, last: false }); + + let tok = send(tok, ctrl_s, Ctrl { length: Length:4 }); + let (tok, output) = recv(tok, out_r); + assert_eq(output, Output { data: Data:0x1, length: Length:4, last: false }); + + let tok = send(tok, ctrl_s, Ctrl { length: Length:64 }); + let (tok, output) = recv(tok, out_r); + assert_eq(output, Output { data: Data:0x55_CC_EE_55_FF_66_00_77, length: Length:64, last: false }); + + let tok = send(tok, ctrl_s, Ctrl { length: Length:16 }); + let (tok, output) = recv(tok, out_r); + assert_eq(output, Output { data: Data:0x44_BB, length: Length:16, last: false }); + + let tok = send(tok, ctrl_s, Ctrl { length: Length:32 }); + let (tok, output) = recv(tok, out_r); + assert_eq(output, Output { data: Data:0x22_99_33_AA, length: Length:32, last: true }); + + let tok = send(tok, ctrl_s, Ctrl { length: Length:64 }); + + let (tok, output) = recv(tok, out_r); + assert_eq(output, Output { data: Data:0x66_DD_77_EE_88_FF_99_00, length: Length:64, last: false }); + + send(tok, terminator, true); + } +} From e0e461abc89b74d76bfdeaffe228f1d499c71dfc Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Thu, 16 May 2024 09:28:46 +0200 Subject: [PATCH 33/85] modules/zstd: Add FSE procs Internal-tag: [#57353] Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 99 ++++ xls/modules/zstd/common.x | 89 +++- xls/modules/zstd/fse_proba_freq_dec.x | 737 ++++++++++++++++++++++++++ 3 files changed, 919 insertions(+), 6 deletions(-) create mode 100644 xls/modules/zstd/fse_proba_freq_dec.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 976a8107c3..2144b2e0e5 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -935,6 +935,105 @@ place_and_route( target_die_utilization_percentage = "10", ) +xls_dslx_library( + name = "fse_proba_freq_dec_dslx", + srcs = ["fse_proba_freq_dec.x"], + deps = [ + ":common_dslx", + ":ram_wr_handler_dslx", + ":shift_buffer_dslx", + "//xls/examples:ram_dslx", + ], +) + +xls_dslx_test( + name = "fse_proba_freq_dec_dslx_test", + library = ":fse_proba_freq_dec_dslx", +) + +xls_dslx_verilog( + name = "fse_proba_freq_dec_verilog", + codegen_args = { + "module_name": "FseProbaFreqDec", + "generator": "pipeline", + "delay_model": "asap7", + # FIXME: update ram rewrite + #"ram_configurations": "ram:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + # latency = 5, + # rd_req = "fse_proba_freq_dec__rd_req_s", + # rd_resp = "fse_proba_freq_dec__rd_resp_r", + # wr_req = "fse_proba_freq_dec__wr_req_s", + # wr_resp = "fse_proba_freq_dec__wr_resp_r", + #), + "pipeline_stages": "6", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "FseProbaFreqDecoderInst", + library = ":fse_proba_freq_dec_dslx", + opt_ir_args = { + "inline_procs": "true", + "top": "__fse_proba_freq_dec__FseProbaFreqDecoderInst__FseProbaFreqDecoder_0__64_7_8_10_1_next", + }, + verilog_file = "fse_proba_freq_dec.v", +) + +xls_benchmark_ir( + name = "fse_proba_freq_dec_opt_ir_benchmark", + src = ":fse_proba_freq_dec_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "10", + "delay_model": "asap7", + "inline_procs": "true", + "reset": "rst", + # FIXME: update ram rewrite + #"ram_configurations": "ram:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + # latency = 5, + # rd_req = "fse_proba_freq_dec__rd_req_s", + # rd_resp = "fse_proba_freq_dec__rd_resp_r", + # wr_req = "fse_proba_freq_dec__wr_req_s", + # wr_resp = "fse_proba_freq_dec__wr_resp_r", + #), + }, +) + +xls_benchmark_verilog( + name = "fse_proba_freq_dec_verilog_benchmark", + verilog_target = "fse_proba_freq_dec_verilog", +) + +verilog_library( + name = "fse_proba_freq_dec_lib", + srcs = [ + ":fse_proba_freq_dec.v", + ], +) + +synthesize_rtl( + name = "fse_proba_freq_dec_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "FseProbaFreqDec", + deps = [ + ":fse_proba_freq_dec_lib", + ], +) + +benchmark_synth( + name = "fse_proba_freq_dec_benchmark_synth", + synth_target = ":fse_proba_freq_dec_asap7", +) + +place_and_route( + name = "fse_proba_freq_dec_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "detailed_routing", + synthesized_rtl = ":fse_proba_freq_dec_asap7", + target_die_utilization_percentage = "10", +) + zstd_dec_deps = [ ":axi_csr_accessor_dslx", ":block_header_dec_dslx", diff --git a/xls/modules/zstd/common.x b/xls/modules/zstd/common.x index 8c6b1f1c5d..98c58c60e1 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; @@ -20,8 +22,10 @@ pub const OFFSET_WIDTH = u32:22; pub const HISTORY_BUFFER_SIZE_KB = u32:64; pub const BUFFER_WIDTH = u32:128; +pub const BLOCK_PACKET_WIDTH = u32:32; + pub type BlockData = bits[DATA_WIDTH]; -pub type BlockPacketLength = u32; +pub type BlockPacketLength = bits[BLOCK_PACKET_WIDTH]; pub type BlockSize = bits[BLOCK_SIZE_WIDTH]; pub type CopyOrMatchContent = BlockData; pub type CopyOrMatchLength = u64; @@ -43,8 +47,8 @@ pub struct BlockDataPacket { } pub enum SequenceExecutorMessageType : u1 { - LITERAL = 0, - SEQUENCE = 1, + LITERAL = 0, + SEQUENCE = 1, } pub struct ExtendedBlockDataPacket { @@ -54,9 +58,9 @@ pub struct ExtendedBlockDataPacket { 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 + length: CopyOrMatchLength, // Literal length or match length + content: CopyOrMatchContent, // Literal data or match offset + last: bool, // Last packet in frame } // Defines output format of the ZSTD Decoder @@ -65,3 +69,76 @@ pub struct ZstdDecodedPacket { length: BlockPacketLength, // valid bits in data last: bool, // Last decoded packet in frame } + +pub enum CompressionMode : u2 { + PREDEFINED = 0, + RLE = 1, + COMPRESSED = 2, + REPEAT = 3, +} + +pub struct SequenceConf { + sequence_count: u17, + literals_mode: CompressionMode, + offset_mode: CompressionMode, + match_mode: CompressionMode, +} + +pub struct SequencePathCtrl { + literals_count: u20, + last_block: bool, + id: u32, + sequence_conf: SequenceConf, +} + +pub struct SequenceData { bytes: bits[64], length: u32, last: bool } + +// FSE + +pub const FSE_MAX_ACCURACY_LOG = u32:9; +pub const FSE_MAX_SYMBOLS = u32:256; + +pub const FSE_ACCURACY_LOG_WIDTH = std::clog2(FSE_MAX_ACCURACY_LOG + u32:1); +pub const FSE_SYMBOL_COUNT_WIDTH = std::clog2(FSE_MAX_SYMBOLS + u32:1); +pub const FSE_REMAINING_PROBA_WIDTH = std::clog2((u32:1 << FSE_MAX_ACCURACY_LOG) + u32:1); + +pub type FseRemainingProba = uN[FSE_REMAINING_PROBA_WIDTH]; +pub type FseAccuracyLog = uN[FSE_ACCURACY_LOG_WIDTH]; +pub type FseSymbolCount = uN[FSE_SYMBOL_COUNT_WIDTH]; + +// defined in https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1.3.2.2.1 +pub const FSE_LITERAL_LENGTH_DEFAULT_DIST = s16[36]:[ + s16:4, s16:3, s16:2, s16:2, s16:2, s16:2, s16:2, s16:2, s16:2, s16:2, s16:2, s16:2, s16:2, + s16:1, s16:1, s16:1, s16:2, s16:2, s16:2, s16:2, s16:2, s16:2, s16:2, s16:2, s16:2, s16:3, + s16:2, s16:1, s16:1, s16:1, s16:1, s16:1, s16:-1, s16:-1, s16:-1, s16:-1, +]; + +// defined in https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1.3.2.2.2 +pub const FSE_OFFSET_DEFAULT_DIST = s16[29]:[ + s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:2, s16:2, s16:2, s16:1, s16:1, s16:1, s16:1, + s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:-1, s16:-1, + s16:-1, s16:-1, s16:-1, +]; + +// defined in https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1.3.2.2.3 +pub const FSE_MATCH_LENGTH_DEFAULT_DIST = s16[53]:[ + s16:1, s16:4, s16:3, s16:2, s16:2, s16:2, s16:2, s16:2, s16:2, s16:1, s16:1, s16:1, s16:1, + s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, + s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, + s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:1, s16:-1, s16:-1, s16:-1, s16:-1, s16:-1, s16:-1, + s16:-1, +]; + +pub enum FSETableInitMode : u1 { + DEFAULT = 0, + EXTERNAL = 1, +} + +pub enum FSETableType : u2 { + LITERAL = 0, + OFFSET = 1, + MATCH = 2, +} + +pub struct FseRemainder { value: u1, valid: bool } +pub struct FseProbaFreqDecoderCtrl { remainder: FseRemainder, finished: bool } diff --git a/xls/modules/zstd/fse_proba_freq_dec.x b/xls/modules/zstd/fse_proba_freq_dec.x new file mode 100644 index 0000000000..19150851c5 --- /dev/null +++ b/xls/modules/zstd/fse_proba_freq_dec.x @@ -0,0 +1,737 @@ +// 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 a proc responsible for decoding probability frequencies +// to probability distribution, as described in: +// https://datatracker.ietf.org/doc/html/rfc8878#section-4.1.1 + +import std; +import xls.examples.ram; +import xls.modules.zstd.common; +import xls.modules.zstd.shift_buffer; +import xls.modules.zstd.ram_wr_handler as ram_wr; + +pub const FSE_MAX_SYMBOLS = u32:256; +pub const FSE_MAX_ACCURACY_LOG = u32:9; + +pub const FSE_ACCURACY_LOG_WIDTH = std::clog2(FSE_MAX_ACCURACY_LOG + u32:1); +pub const FSE_SYMBOL_COUNT_WIDTH = std::clog2(FSE_MAX_SYMBOLS + u32:1); +pub const FSE_REMAINING_PROBA_WIDTH = std::clog2((u32:1 << FSE_MAX_ACCURACY_LOG) + u32:1); + +pub type FseRemainingProba = uN[FSE_REMAINING_PROBA_WIDTH]; +pub type FseAccuracyLog = uN[FSE_ACCURACY_LOG_WIDTH]; +pub type FseSymbolCount = uN[FSE_SYMBOL_COUNT_WIDTH]; + +type AccuracyLog = common::FseAccuracyLog; +type RemainingProba = common::FseRemainingProba; +type SymbolCount = common::FseSymbolCount; +type SequenceData = common::SequenceData; + +const SYMBOL_COUNT_WIDTH = common::FSE_SYMBOL_COUNT_WIDTH; +const ACCURACY_LOG_WIDTH = common::FSE_ACCURACY_LOG_WIDTH; + +pub struct Remainder { value: u1, valid: bool } + +enum FseProbaFreqDecoderStatus: u1 { + OK = 0, + ERROR = 1, +} + +pub struct FseProbaFreqDecoderReq {} +pub struct FseProbaFreqDecoderResp { status: FseProbaFreqDecoderStatus } + +enum Fsm : u4 { + IDLE = 0, + SEND_ACCURACY_LOG_REQ = 1, + RECV_ACCURACY_LOG = 2, + SEND_SYMBOL_REQ = 3, + RECV_SYMBOL = 4, + RECV_ZERO_PROBA = 5, + WRITE_ZERO_PROBA = 6, + WAIT_FOR_COMPLETION = 7, + INVALID = 8, +} + +struct State { + fsm: Fsm, + // accuracy log used in the FSE decoding table + accuracy_log: AccuracyLog, + // remaining bit that can be a leftover from parsing small probability frequencies + remainder: Remainder, + // indicates if one more packet with zero probabilities is expected + next_recv_zero: bool, + // information about remaining probability points + remaining_proba: RemainingProba, + // number of received probability symbols + symbol_count: SymbolCount, + // number of probability symbols written to RAM + written_symbol_count: SymbolCount, + // number of processed zero probability symbols + zero_proba_count: SymbolCount, +} + +// Adapter for input data, converting the data to a shift buffer input type +pub proc FseInputBuffer { + type BufferInput = shift_buffer::ShiftBufferInput; + type BufferCtrl = shift_buffer::ShiftBufferCtrl; + type BufferOutput = shift_buffer::ShiftBufferOutput; + type Data = common::BlockData; + type Length = bits[LENGTH_WIDTH]; + + in_data_r: chan in; + buff_data_s: chan out; + + config( + data_r: chan in, + ctrl_r: chan in, + out_s: chan out, + ) { + let (buff_data_s, buff_data_r) = chan("buff_in_data"); + + spawn shift_buffer::ShiftBuffer( + buff_data_r, ctrl_r, out_s); + + (data_r, buff_data_s) + } + + init { } + + next (state: ()) { + let tok0 = join(); + let (tok1, recv_data, recv_valid) = recv_non_blocking(tok0, in_data_r, zero!()); + + let shift_buffer_data = BufferInput { + data: recv_data.bytes as Data, + length: recv_data.length as Length, + last: recv_data.last + }; + + send_if(tok1, buff_data_s, recv_valid, shift_buffer_data); + } +} + +// calculates bit_with of the next probability frequency based on the remaining probability points +fn get_bit_width(remaining_proba: RemainingProba) -> u16 { + let highest_set_bit = std::flog2(remaining_proba as u32 + u32:1); + highest_set_bit as u16 + u16:1 +} + +// calculates mask for small probability frequency values +fn get_lower_mask(bit_width: u16) -> u16 { + (u16:1 << (bit_width - u16:1)) - u16:1 +} + +// calculates threshold for a duplicated "upper" range of small probability frequencies +fn get_threshold(bit_width: u16, remaining_proba: u16) -> u16 { + (u16:1 << bit_width) - u16:1 - (remaining_proba + u16:1) +} + +// get the adjusted stream value for calculating probability points +fn get_adjusted_value(data: u16, remainder: Remainder) -> u16 { + if remainder.valid { (data << u16:1) | (remainder.value as u16) } else { data } +} + +// proc for filling probability frequencies table +pub proc FseProbaFreqDecoder< + RAM_DATA_WIDTH: u32, + RAM_SIZE: u32, + RAM_WORD_PARTITION_SIZE: u32, + RAM_ADDR_WIDTH: u32 = {std::clog2(RAM_SIZE)}, + RAM_NUM_PARTITIONS: u32 = {ram::num_partitions(RAM_WORD_PARTITION_SIZE, RAM_DATA_WIDTH)}, + DATA_WIDTH: u32 = {common::DATA_WIDTH}, + LENGTH_WIDTH: u32 = {common::BLOCK_PACKET_WIDTH} +> { + type Length = bits[LENGTH_WIDTH]; + type BufferCtrl = shift_buffer::ShiftBufferCtrl; + type BufferOutput = shift_buffer::ShiftBufferOutput; + type RamWriteReq = ram::WriteReq; + type RamWriteResp = ram::WriteResp; + type RamReadReq = ram::ReadReq; + type RamReadResp = ram::ReadResp; + type RamAddr = bits[RAM_ADDR_WIDTH]; + type RamData = bits[RAM_DATA_WIDTH]; + + type Req = FseProbaFreqDecoderReq; + type Resp = FseProbaFreqDecoderResp; + type Status = FseProbaFreqDecoderStatus; + + req_r: chan in; + resp_s: chan out; + + buff_in_ctrl_s: chan out; + buff_out_data_r: chan in; + resp_in_s: chan out; + resp_out_r: chan in; + + rd_req_s: chan out; + rd_resp_r: chan in; + wr_req_s: chan out; + + config( + // control + req_r: chan in, + resp_s: chan out, + + // incomming data + buff_in_ctrl_s: chan out, + buff_out_data_r: chan in, + + // created lookup + rd_req_s: chan out, + rd_resp_r: chan in, + wr_req_s: chan out, + wr_resp_r: chan in + ) { + let (resp_in_s, resp_in_r) = chan("resp_in"); + let (resp_out_s, resp_out_r) = chan("resp_out"); + + spawn ram_wr::RamWrRespHandler( + resp_in_r, resp_out_s, wr_resp_r + ); + + ( + req_r, resp_s, + buff_in_ctrl_s, buff_out_data_r, + resp_in_s, resp_out_r, + rd_req_s, rd_resp_r, wr_req_s, + ) + } + + init { zero!() } + + next(state: State) { + let tok0 = join(); + + type BufferCtrl = shift_buffer::ShiftBufferCtrl; + type BufferOutput = shift_buffer::ShiftBufferOutput; + + type RamWriteReq = ram::WriteReq; + type RamWriteResp = ram::WriteResp; + type RamReadReq = ram::ReadReq; + type RamReadResp = ram::ReadResp; + + let do_recv_req = (state.fsm == Fsm::IDLE); + + let (tok1_0, _) = recv_if(tok0, req_r, do_recv_req, zero!()); + + let do_buff_data_recv = match (state.fsm) { + Fsm::RECV_ACCURACY_LOG => true, + Fsm::RECV_SYMBOL => true, + Fsm::RECV_ZERO_PROBA => true, + _ => false, + }; + let (tok1_1, out_data) = recv_if(tok0, buff_out_data_r, do_buff_data_recv, zero!()); + + let (tok1_2, written_symbol_count, written_symb_count_valid) = + recv_non_blocking(tok0, resp_out_r, state.written_symbol_count); + + let tok1 = join(tok1_1, tok1_2); + + let (buffer_ctrl_option, ram_option, resp_option, new_state) = match state.fsm { + Fsm::IDLE => { + ( + (false, zero!()), + (false, zero!()), + (false, zero!()), + State { fsm: Fsm::SEND_ACCURACY_LOG_REQ, ..state }, + ) + }, + Fsm::SEND_ACCURACY_LOG_REQ => { + ( + (true, BufferCtrl { length: ACCURACY_LOG_WIDTH as Length }), + (false, zero!()), + (false, zero!()), + State { fsm: Fsm::RECV_ACCURACY_LOG, written_symbol_count, ..state }, + ) + }, + Fsm::RECV_ACCURACY_LOG => { + let accuracy_log = AccuracyLog:5 + out_data.data as AccuracyLog; + let remaining_proba = RemainingProba:1 << accuracy_log; + + ( + (false, zero!()), + (false, zero!()), + (false, zero!()), + State { + fsm: Fsm::SEND_SYMBOL_REQ, + accuracy_log, + remaining_proba, + written_symbol_count, + ..state + }, + ) + }, + Fsm::SEND_SYMBOL_REQ => { + let bit_width = get_bit_width(state.remaining_proba); + ( + (true, BufferCtrl { length: bit_width as Length }), + (false, zero!()), + (false, zero!()), + State { fsm: Fsm::RECV_SYMBOL, written_symbol_count, ..state }, + ) + }, + Fsm::RECV_SYMBOL => { + let bit_width = get_bit_width(state.remaining_proba); + let lower_mask = get_lower_mask(bit_width); + let threshold = get_threshold(bit_width, state.remaining_proba as u16); + + let mask = (u16:1 << out_data.length) - u16:1; + let data = out_data.data as u16; + assert!(data & mask == data, "data should not contain additional bits"); + + let value = get_adjusted_value(data, state.remainder); + let (remainder, value) = if (value & lower_mask) < threshold { + (Remainder { value: value[bit_width - u16:1+:u1], valid: true }, value & lower_mask) + } else if value > lower_mask { + (zero!(), value - threshold) + } else { + (zero!(), value) + }; + + let proba = value as s16 - s16:1; + let proba_points = if proba < s16:0 { RemainingProba:1 } else { proba as RemainingProba }; + assert!(proba_points <= state.remaining_proba, "corrupted_data"); + + let remaining_proba = state.remaining_proba - proba_points; + let symbol_count = state.symbol_count + SymbolCount:1; + let remainder_count = if remainder.valid { u16:1 } else { u16:0 }; + + // received all the symbols + if remaining_proba == RemainingProba:0 { + ( + (false, zero!()), + (true, RamWriteReq { + addr: state.symbol_count as RamAddr, + data: proba as RamData, + mask: std::unsigned_max_value() + }), + (false, zero!()), + State { + fsm: Fsm::WAIT_FOR_COMPLETION, + written_symbol_count, + symbol_count, + remaining_proba, + remainder, + ..state + }, + ) + // there are remaining symbols, and next symbol is normal + } else if remaining_proba > RemainingProba:0 && proba != s16:0 { + let next_bit_width = get_bit_width(remaining_proba) - remainder_count; + ( + (true, BufferCtrl { length: next_bit_width as Length }), + (true, RamWriteReq { + addr: state.symbol_count as RamAddr, + data: proba as RamData, + mask: std::unsigned_max_value() + }), + (false, zero!()), + State { + fsm: Fsm::RECV_SYMBOL, + written_symbol_count, + symbol_count, + remaining_proba, + remainder, + ..state + }, + ) + // there are remaining symbols, and next data is info about zero probability + } else if remaining_proba > RemainingProba:0 && proba == s16:0 { + let next_bit_width = u16:2 - remainder_count; + ( + (true, BufferCtrl { length: next_bit_width as Length }), + (true, RamWriteReq { + addr: state.symbol_count as RamAddr, + data: proba as RamData, + mask: std::unsigned_max_value() + }), + (false, zero!()), + State { + fsm: Fsm::RECV_ZERO_PROBA, + written_symbol_count, + symbol_count, + remaining_proba, + remainder, + ..state + } + ) + } else { + fail!( + "unhandled_case", + ( + (false, zero!()), + (false, zero!()), + (false, zero!()), + State { fsm: Fsm::INVALID, ..zero!() }, + )) + } + }, + Fsm::RECV_ZERO_PROBA => { + let zero_proba_count = out_data.data as SymbolCount; + let zero_proba_length = out_data.length as SymbolCount; + let zero_proba_count = get_adjusted_value(zero_proba_count as u16, state.remainder) as SymbolCount; + + // all zero probabilitis received + if zero_proba_count == SymbolCount:0 { + let new_fsm = if state.remaining_proba > RemainingProba:0 { + Fsm::SEND_SYMBOL_REQ + } else if state.remaining_proba == RemainingProba:0 { + Fsm::WAIT_FOR_COMPLETION + } else { + Fsm::INVALID + }; + + ( + (true, zero!()), + (false, zero!()), + (false, zero!()), + State { + fsm: new_fsm, + remainder: zero!(), + written_symbol_count, + ..state + }, + ) + // some zero probabilities left + } else { + let next_recv_zero = zero_proba_count == SymbolCount:3; + ( + (false, zero!()), + (false, zero!()), + (false, zero!()), + State { + fsm: Fsm::WRITE_ZERO_PROBA, + remainder: zero!(), + written_symbol_count, + zero_proba_count, + next_recv_zero, + ..state + }, + ) + } + }, + Fsm::WRITE_ZERO_PROBA => { + let zero_proba_count = state.zero_proba_count - SymbolCount:1; + let symbol_count = state.symbol_count + SymbolCount:1; + + let write_req = RamWriteReq { + addr: state.symbol_count as RamAddr, + data: RamData:0, + mask: std::unsigned_max_value() + }; + + if zero_proba_count == SymbolCount:0 && state.next_recv_zero == true { + ( + (true, BufferCtrl { length: Length:2 }), + (true, write_req), + (false, zero!()), + State { + fsm: Fsm::RECV_ZERO_PROBA, + next_recv_zero: false, + written_symbol_count, + zero_proba_count, + symbol_count, + ..state + }, + ) + } else if zero_proba_count == SymbolCount:0 && state.next_recv_zero == false { + ( + (false, zero!()), + (true, write_req), + (false, zero!()), + State { + fsm: Fsm::SEND_SYMBOL_REQ, + next_recv_zero: false, + zero_proba_count: SymbolCount:0, + written_symbol_count, + symbol_count, + ..state + }, + ) + } else { + ( + (false, zero!()), + (true, write_req), + (false, zero!()), + State { + fsm: Fsm::WRITE_ZERO_PROBA, + zero_proba_count, + symbol_count, + written_symbol_count, + ..state + }, + ) + } + }, + Fsm::WAIT_FOR_COMPLETION => { + if written_symbol_count == state.symbol_count { + ( + (false, zero!()), + (false, zero!()), + (true, Resp { status: Status::OK }), + zero!(), + ) + } else { + ( + (false, zero!()), + (false, zero!()), + (false, zero!()), + state, + ) + } + }, + _ => { + trace_fmt!("Invalid state"); + fail!( + "not_handled", + ( + (false, zero!()), + (false, zero!()), + (false, zero!()), + state, + )) + }, + }; + + let (do_send_ctrl, ctrl_data) = buffer_ctrl_option; + let tok2_0 = send_if(tok1, buff_in_ctrl_s, do_send_ctrl, ctrl_data); + + let (do_send_ram, ram_data) = ram_option; + let tok2_1 = send_if(tok1, wr_req_s, do_send_ram, ram_data); + let tok2_2 = send_if(tok1, resp_in_s, do_send_ram, state.symbol_count == SymbolCount:0); + + let (do_send_finish, finish_data) = resp_option; + let tok2_3 = send_if(tok1, resp_s, do_send_finish, finish_data); + + // unused channels + send_if(tok0, rd_req_s, false, zero!()); + recv_if(tok0, rd_resp_r, false, zero!()); + + new_state + } +} + +const INST_RAM_SIZE = common::FSE_MAX_SYMBOLS; +const INST_RAM_ADDR_WIDTH = std::clog2(INST_RAM_SIZE); +const INST_RAM_DATA_WIDTH = get_bit_width(RemainingProba:1 << common::FSE_MAX_ACCURACY_LOG) as u32; +const INST_RAM_WORD_PARTITION_SIZE = INST_RAM_DATA_WIDTH; +const INST_RAM_NUM_PARTITIONS = ram::num_partitions(INST_RAM_WORD_PARTITION_SIZE, INST_RAM_DATA_WIDTH); +const INST_DATA_WIDTH = common::DATA_WIDTH; +const INST_LENGTH_WIDTH = common::BLOCK_PACKET_WIDTH; + +proc FseProbaFreqDecoderInst { + rd_req_s: chan> out; + rd_resp_r: chan> in; + + config( + req_r: chan in, + resp_s: chan out, + buff_in_ctrl_s: chan> out, + buff_out_data_r: chan> in, + rd_req_s: chan> out, + rd_resp_r: chan> in, + wr_req_s: chan> out, + wr_resp_r: chan in) { + + spawn FseProbaFreqDecoder( + req_r, resp_s, + buff_in_ctrl_s, buff_out_data_r, + rd_req_s, rd_resp_r, wr_req_s, wr_resp_r + ); + + (rd_req_s, rd_resp_r) + } + + init { } + next(state: ()) { } +} + +const TEST_RAM_DATA_WIDTH = u32:16; +const TEST_RAM_SIZE = u32:100; +const TEST_RAM_ADDR_WIDTH = std::clog2(TEST_RAM_SIZE); +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_DATA_WIDTH = common::DATA_WIDTH; +const TEST_LENGTH_WIDTH = common::BLOCK_PACKET_WIDTH; + +#[test_proc] +proc FseProbaFreqDecoderTest { + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp; + type BufferCtrl = shift_buffer::ShiftBufferCtrl; + type BufferOutput = shift_buffer::ShiftBufferOutput; + type RamAddr = bits[TEST_RAM_ADDR_WIDTH]; + type RamData = uN[TEST_RAM_DATA_WIDTH]; + type RamDataSigned = sN[TEST_RAM_DATA_WIDTH]; + type Req = FseProbaFreqDecoderReq; + type Resp = FseProbaFreqDecoderResp; + + terminator: chan out; + seq_data_s: chan out; + req_s: chan out; + resp_r: chan in; + rd_req_s: chan out; + rd_resp_r: chan in; + wr_req_s: chan out; + wr_resp_r: chan in; + + config(terminator: chan out) { + // RAM channels + let (rd_req_s, rd_req_r) = chan("rd_req"); + let (rd_resp_s, rd_resp_r) = chan("rd_resp"); + let (wr_req_s, wr_req_r) = chan("wr_req"); + let (wr_resp_s, wr_resp_r) = chan("wr_resp"); + + // FseProbaFreqDecoder channels + let (seq_data_s, seq_data_r) = chan("seq_data"); + + let (req_s, req_r) = chan("req"); + let (resp_s, resp_r) = chan("resp"); + + let (buff_in_ctrl_s, buff_in_ctrl_r) = chan("buff_in_ctrl"); + let (buff_out_data_s, buff_out_data_r) = chan("buff_out_data"); + + spawn FseInputBuffer( + seq_data_r, buff_in_ctrl_r, buff_out_data_s); + + spawn FseProbaFreqDecoder( + req_r, resp_s, + buff_in_ctrl_s, buff_out_data_r, + rd_req_s, rd_resp_r, wr_req_s, wr_resp_r); + + spawn ram::RamModel( + rd_req_r, rd_resp_s, wr_req_r, wr_resp_s); + + (terminator, seq_data_s, req_s, resp_r, rd_req_s, rd_resp_r, wr_req_s, wr_resp_r) + } + + init { } + + next(state: ()) { + let tok = join(); + + // * accuracy_log = 8 + // * probability frequencies: + // | value | probability | bits (real) | symbol number | + // | ----- | ----------- | ---------- | ------------- | + // | 97 | 96 | 8(9) | 0 | + // | 117 | 116 | 8 | 1 | + // | 55 | 36 | 6 | 2 | + // | 1 | 0 | 4(3) | 3 | + // | 2* | 0 0 0 | (2) | 4 5 6 | + // | 1* | 0 | (2) | 7 | + // | 3 | 2 | 4(3) | 8 | + // | 1 | 0 | 3 | 9 | + // | 0 | -1 | 3 | 10 | + // | 6 | 5 | 3 | 11 | + + const EXPECTED_RAM_CONTENTS = RamData[12]:[ + RamData:96, + RamData:116, + RamData:36, + RamData:0, + RamData:0, RamData:0, RamData:0, + RamData:0, + RamData:2, + RamData:0, + RamDataSigned:-1 as RamData, + RamData:5 + ]; + + let tok = send(tok, seq_data_s, common::SequenceData { + bytes: u64:0b111_000_00_001_011_01_11_001_110111_01110101_01100001_0011, + length: u32:47, + last: false + }); + let tok = send(tok, req_s, zero!()); + let (tok, _) = recv(tok, resp_r); + + for ((i, exp_val), tok): ((u32, RamData), token) in enumerate(EXPECTED_RAM_CONTENTS) { + let tok = send(tok, rd_req_s, ReadReq { + addr: i as RamAddr, + mask: std::unsigned_max_value(), + }); + + let (tok, recv_data) = recv(tok, rd_resp_r); + assert_eq(recv_data.data, exp_val); + tok + }((tok)); + + // * accuracy_log = 9 + // * probability frequencies: + // | value | probability | bits (real) | symbol number | + // | ----- | ----------- | ---------- | ------------- | + // | 1022 | 511 | 10 | 0 | + // | 0 | -1 | 2(1) | 1 | + + const EXPECTED_RAM_CONTENTS = RamData[2]:[ + RamData:511, + RamDataSigned:-1 as RamData, + ]; + + let tok = send(tok, seq_data_s, common::SequenceData { + bytes: u64:0b00_1111111110_0100, + length: u32:16, + last: false + }); + let tok = send(tok, req_s, zero!()); + let (tok, _) = recv(tok, resp_r); + + for ((i, exp_val), tok): ((u32, RamData), token) in enumerate(EXPECTED_RAM_CONTENTS) { + let tok = send(tok, rd_req_s, ReadReq { + addr: i as RamAddr, + mask: std::unsigned_max_value(), + }); + + let (tok, recv_data) = recv(tok, rd_resp_r); + assert_eq(recv_data.data, exp_val); + tok + }((tok)); + + // * accuracy_log = 9 + // * probability frequencies: + // | value | probability | bits (real) | symbol number | + // | ----- | ----------- | ---------- | ------------- | + // | 1022 | 511 | 10 | 0 | + // | 2 | -1 | 2(1) | 1 | + + const EXPECTED_RAM_CONTENTS = RamData[2]:[ + RamData:511, + RamDataSigned:-1 as RamData, + ]; + + let tok = send(tok, seq_data_s, common::SequenceData { + bytes: u64:0b10_1111111110_0100, + length: u32:16, + last: false + }); + let tok = send(tok, req_s, zero!()); + let (tok, _) = recv(tok, resp_r); + + for ((i, exp_val), tok): ((u32, RamData), token) in enumerate(EXPECTED_RAM_CONTENTS) { + let tok = send(tok, rd_req_s, ReadReq { + addr: i as RamAddr, + mask: std::unsigned_max_value(), + }); + + let (tok, recv_data) = recv(tok, rd_resp_r); + assert_eq(recv_data.data, exp_val); + tok + }((tok)); + + let tok = send(tok, terminator, true); + } +} From 3a4c765cf94914bb9239c1e1be027aae5e50c703 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Thu, 16 May 2024 09:30:04 +0200 Subject: [PATCH 34/85] modules/zstd: Add a proc handling RAM write completion Internal-tag: [#57353] Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 71 ++++++++++++++ xls/modules/zstd/ram_wr_handler.x | 152 ++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+) create mode 100644 xls/modules/zstd/ram_wr_handler.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 2144b2e0e5..c46ee0376e 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -935,6 +935,77 @@ place_and_route( target_die_utilization_percentage = "10", ) +xls_dslx_library( + name = "ram_wr_handler_dslx", + srcs = ["ram_wr_handler.x"], + deps = [ + "//xls/examples:ram_dslx", + ], +) + +xls_dslx_test( + name = "ram_wr_handler_dslx_test", + library = ":ram_wr_handler_dslx", +) + +xls_dslx_verilog( + name = "ram_rw_handler_verilog", + codegen_args = { + "module_name": "RamWrRespHandler", + "delay_model": "asap7", + "pipeline_stages": "1", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "RamWrRespHandlerInst", + library = ":ram_wr_handler_dslx", + opt_ir_args = { + "top": "__ram_wr_handler__RamWrRespHandlerInst__RamWrRespHandler_0__32_next", + }, + verilog_file = "ram_rw_handler.v", +) + +xls_benchmark_ir( + name = "ram_rw_handler_opt_ir_benchmark", + src = ":ram_rw_handler_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "10", + "delay_model": "asap7", + }, +) + +verilog_library( + name = "ram_rw_handler_verilog_lib", + srcs = [ + ":ram_rw_handler.v", + ], +) + +synthesize_rtl( + name = "ram_rw_handler_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "RamWrRespHandler", + deps = [ + ":ram_rw_handler_verilog_lib", + ], +) + +benchmark_synth( + name = "ram_rw_handler_benchmark_synth", + synth_target = ":ram_rw_handler_synth_asap7", +) + +place_and_route( + name = "ram_rw_handler_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":ram_rw_handler_synth_asap7", + target_die_utilization_percentage = "10", +) + xls_dslx_library( name = "fse_proba_freq_dec_dslx", srcs = ["fse_proba_freq_dec.x"], diff --git a/xls/modules/zstd/ram_wr_handler.x b/xls/modules/zstd/ram_wr_handler.x new file mode 100644 index 0000000000..a59fd0b256 --- /dev/null +++ b/xls/modules/zstd/ram_wr_handler.x @@ -0,0 +1,152 @@ +// 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 responsible for receiving +// RAM completions and counting their number. The proc might be used to simplify +// the implementation of other procedures. + +import std; +import xls.examples.ram; + +proc RamWrRespHandler { + type Reset = bool; + type WriteCnt = bits[CNT_WIDTH]; + type WriteResp = ram::WriteResp; + + input_r: chan in; + output_s: chan out; + wr_resp_r: chan in; + + config( + input_r: chan in, + output_s: chan out, + wr_resp_r: chan in + ) { + (input_r, output_s, wr_resp_r) + } + + init { WriteCnt:0 } + + next(wr_cnt: WriteCnt) { + let tok0 = join(); + + let (tok1, reset) = recv(tok0, input_r); + recv(tok1, wr_resp_r); + + let wr_cnt = if reset { WriteCnt:1 } else { wr_cnt }; + send(tok1, output_s, wr_cnt); + + wr_cnt + WriteCnt:1 + } +} + +const INST_CNT_WIDTH = u32:32; +proc RamWrRespHandlerInst { + type Reset = bool; + type WriteCnt = bits[INST_CNT_WIDTH]; + type WriteResp = ram::WriteResp; + + config( + input_r: chan in, + output_s: chan out, + wr_resp_r: chan in + ) { + spawn RamWrRespHandler(input_r, output_s, wr_resp_r); + } + + init { } + next(state: ()) { } +} + +const TEST_CNT_WIDTH = u32:32; +const TEST_RAM_DATA_WIDTH = u32:8; +const TEST_RAM_SIZE = u32:256; +const TEST_RAM_ADDR_WIDTH = std::clog2(TEST_RAM_SIZE); +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_SYMBOLS_TO_SEND = u32:12; + +struct RamWrRespHandlerTestState { rd_cnt: u32, wr_cnt: u32 } + +#[test_proc] +proc RamWrRespHandlerTest { + type RamReadReq = ram::ReadReq; + type RamReadResp = ram::ReadResp; + type RamWriteReq = ram::WriteReq; + type RamWriteResp = ram::WriteResp; + type RamAddr = bits[TEST_RAM_ADDR_WIDTH]; + type RamData = bits[TEST_RAM_DATA_WIDTH]; + type State = RamWrRespHandlerTestState; + type CntWidth = bits[TEST_CNT_WIDTH]; + + terminator: chan out; + rd_req_s: chan out; + rd_resp_r: chan in; + wr_req_s: chan out; + wr_resp_r: chan in; + resp_in_s: chan out; + resp_out_r: chan in; + + config(terminator: chan out) { + let (rd_req_s, rd_req_r) = chan("rd_req"); + let (rd_resp_s, rd_resp_r) = chan("rd_resp"); + let (wr_req_s, wr_req_r) = chan("wr_req"); + let (wr_resp_s, wr_resp_r) = chan("wr_resp"); + + let (resp_in_s, resp_in_r) = chan("resp_in"); + let (resp_out_s, resp_out_r) = chan("resp_out"); + + spawn RamWrRespHandler(resp_in_r, resp_out_s, wr_resp_r); + + spawn ram::RamModel( + rd_req_r, rd_resp_s, wr_req_r, wr_resp_s); + + (terminator, rd_req_s, rd_resp_r, wr_req_s, wr_resp_r, resp_in_s, resp_out_r) + } + + init { + type State = RamWrRespHandlerTestState; + zero!() + } + + next(state: State) { + let tok0 = join(); + + let start = (state.rd_cnt == u32:0); + const MASK = std::unsigned_max_value(); + + let (tok1, wr_cnt, _) = recv_non_blocking(tok0, resp_out_r, state.wr_cnt); + + let do_send_ram = state.rd_cnt < TEST_SYMBOLS_TO_SEND; + let wr_req = RamWriteReq { + addr: state.rd_cnt as RamAddr, + data: state.rd_cnt as RamData, + mask: MASK + }; + + let tok2_0 = send_if(tok1, wr_req_s, do_send_ram, wr_req); + let tok2_1 = send_if(tok1, resp_in_s, do_send_ram, start); + + let do_terminate = (state.wr_cnt == (TEST_SYMBOLS_TO_SEND - u32:1)); + let tok2_2 = send_if(tok1, terminator, do_terminate, true); + + if do_terminate { + zero!() + } else { + let rd_cnt = state.rd_cnt + u32:1; + State { rd_cnt, wr_cnt } + } + } +} From ab87033902cd10fe55c52941f24165e49332a36b Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Thu, 31 Oct 2024 23:03:31 +0100 Subject: [PATCH 35/85] modules/zstd: Add Sequence and Literals Section Header decoders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Krzysztof Obłonczek Signed-off-by: Robert Winkler Signed-off-by: Krzysztof Obłonczek --- xls/modules/zstd/BUILD | 86 ++++ xls/modules/zstd/comp_block_dec.x | 244 +++++++++++ xls/modules/zstd/literals_block_header_dec.x | 436 +++++++++++++++++++ xls/modules/zstd/ram_wr_handler.x | 2 +- xls/modules/zstd/sequence_conf_dec.x | 342 +++++++++++++++ 5 files changed, 1109 insertions(+), 1 deletion(-) create mode 100644 xls/modules/zstd/comp_block_dec.x create mode 100644 xls/modules/zstd/literals_block_header_dec.x create mode 100644 xls/modules/zstd/sequence_conf_dec.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index c46ee0376e..9860a0791f 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1105,6 +1105,92 @@ place_and_route( target_die_utilization_percentage = "10", ) +xls_dslx_library( + name = "literals_block_header_dec_dslx", + srcs = ["literals_block_header_dec.x"], + deps = [ + "//xls/modules/zstd/memory:mem_reader_dslx", + ], +) + +xls_dslx_test( + name = "literals_block_header_dec_dslx_test", + dslx_test_args = {"compare": "jit"}, + library = ":literals_block_header_dec_dslx", +) + + +xls_dslx_verilog( + name = "literals_block_header_dec_verilog", + codegen_args = window_buffer_codegen_args, + dslx_top = "LiteralsHeaderDecoderInst", + library = ":literals_block_header_dec_dslx", + tags = ["manual"], + verilog_file = "literals_block_header_dec.v", +) + +xls_benchmark_ir( + name = "literals_block_header_dec_opt_ir_benchmark", + src = ":literals_block_header_dec_verilog.opt.ir", + benchmark_ir_args = { + "top": "__literals_block_header_dec__LiteralsHeaderDecoderInst__LiteralsHeaderDecoder_0__16_64_next", + "pipeline_stages": "10", + }, + tags = ["manual"], +) + + +xls_dslx_library( + name = "sequence_conf_dec_dslx", + srcs = ["sequence_conf_dec.x"], + deps = [ + "//xls/modules/zstd/memory:mem_reader_dslx", + ":common_dslx", + ], +) + +xls_dslx_test( + name = "sequence_conf_dec_dslx_test", + dslx_test_args = {"compare": "jit"}, + library = ":sequence_conf_dec_dslx", +) + +xls_dslx_verilog( + name = "sequence_conf_dec_verilog", + codegen_args = window_buffer_codegen_args, + dslx_top = "SequenceConfDecoderInst", + library = ":sequence_conf_dec_dslx", + tags = ["manual"], + verilog_file = "sequence_conf_dec.v", +) + +xls_benchmark_ir( + name = "sequence_conf_dec_opt_ir_benchmark", + src = ":sequence_conf_dec_verilog.opt.ir", + benchmark_ir_args = { + "top": "__sequence_conf_dec__SequenceConfDecoderInst__SequenceConfDecoder_0__16_64_next", + "pipeline_stages": "10", + }, + tags = ["manual"], +) + +#xls_dslx_library( +# name = "comp_block_dec_dslx", +# srcs = ["comp_block_dec.x"], +# deps = [ +# "//xls/examples:ram_dslx", +# "//xls/modules/zstd/memory:mem_reader_dslx", +# "//xls/modules/zstd/memory:axi_ram_dslx", +# ":fse_proba_freq_dec_dslx", +# ":literals_block_header_dec_dslx", +# ], +#) +# +#xls_dslx_test( +# name = "comp_block_dec_dslx_test", +# library = ":comp_block_dec_dslx", +#) + zstd_dec_deps = [ ":axi_csr_accessor_dslx", ":block_header_dec_dslx", diff --git a/xls/modules/zstd/comp_block_dec.x b/xls/modules/zstd/comp_block_dec.x new file mode 100644 index 0000000000..877998336d --- /dev/null +++ b/xls/modules/zstd/comp_block_dec.x @@ -0,0 +1,244 @@ +// 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; +import xls.modules.zstd.memory.axi; +import xls.modules.zstd.memory.mem_reader; +import xls.modules.zstd.fse_proba_freq_dec; +import xls.modules.zstd.comp_block_header_dec; + + +struct CompressBlockDecoderReq { } +struct CompressBlockDecoderResp { } + +struct CompressBlockDecoderControlState { } + +proc CompressBlockDecoderControl { + type Req = CompressBlockDecoderReq; + type Resp = CompressBlockDecoderResp; + type State = CompressBlockDecoderControlState; + + type HeaderDecoderReq = comp_block_header_dec::CompressBlockHeaderDecoderReq; + type HeaderDecoderResp = comp_block_header_dec::CompressBlockHeaderDecoderResp; + + req_r: chan in; + resp_s: chan out; + cmph_req_s: chan out; + cmph_resp_r: chan in; + + init { zero!() } + + config( + req_r: chan in, + resp_s: chan out, + cmph_req_s: chan out, + cmph_resp_r: chan in, + ) { + (req_r, resp_s, cmph_req_s, cmph_resp_r) + } + + next (state: State) { + let tok = join(); + + send_if(tok, resp_s, false, zero!()); + send_if(tok, cmph_req_s, false, zero!()); + + recv_if(tok, req_r, false, zero!()); + recv_if(tok, cmph_resp_r, false, zero!()); + + state + } +} + +proc CompressBlockDecoder< + // AXI parameters + AXI_DATA_W: u32, AXI_ADDR_W: u32, AXI_ID_W: u32, AXI_DEST_W: u32, + + // FSE proba + FSE_PROBA_DIST_W: u32 = {u32:16}, + FSE_PROBA_MAX_DISTS: u32 = {u32:256}, + + // constants + FSE_DEF_PROBA_DIST_RAM_DATA_W: u32 = {FSE_PROBA_DIST_W}, + FSE_DEF_PROBA_DIST_RAM_SIZE: u32 = {FSE_PROBA_MAX_DISTS}, + FSE_DEF_PROBA_DIST_ADDR_W: u32 = {std::clog2(FSE_DEF_PROBA_DIST_RAM_SIZE)}, + FSE_DEF_PROBA_DIST_RAM_WORD_PARTITION_SIZE: u32 = { FSE_DEF_PROBA_DIST_RAM_DATA_W }, + FSE_DEF_PROBA_DIST_RAM_NUM_PARTITIONS: u32 = { ram::num_partitions(FSE_DEF_PROBA_DIST_RAM_WORD_PARTITION_SIZE, FSE_DEF_PROBA_DIST_RAM_DATA_W) }, + AXI_DATA_W_DIV8: u32 = {AXI_DATA_W / u32:8}, +> { + type Req = CompressBlockDecoderReq; + type Resp = CompressBlockDecoderResp; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + + type MemAxiAr = axi::AxiAr; + type MemAxiR = axi::AxiR; + type MemAxiAw = axi::AxiAw; + type MemAxiW = axi::AxiW; + type MemAxiB = axi::AxiB; + + type HeaderDecoderReq = comp_block_header_dec::CompressBlockHeaderDecoderReq; + type HeaderDecoderResp = comp_block_header_dec::CompressBlockHeaderDecoderResp; + + //type ShiftBufferInput = shift_buffer::ShiftBufferInput; + //type ShiftBufferCtrl = shift_buffer::ShiftBufferCtrl; + //type ShiftBufferOutput = shift_buffer::ShiftBufferOutput; + + type DpdRamRdReq = ram::ReadReq; + type DpdRamRdResp = ram::ReadResp; + type DpdRamWrReq = ram::WriteReq; + type DpdRamWrResp = ram::WriteResp; + + init {} + next(state: ()) {} + + config( + req_r: chan in, + resp_s: chan out, + + // AXI Compressed Block Header Decoder (manager) + cmph_axi_ar_s: chan out, + cmph_axi_r_r: chan in, + + // AXI Fse Probability Frequence Decoder (manager) + //pfd_axi_ar_s: chan out, + //pfd_axi_r_r: chan in, + + ) { + + const CHANNEL_DEPTH = u32:1; + + // Compress Block Header Memory Reader + + let (cmph_mem_rd_req_s, cmph_mem_rd_req_r) = chan("cmph_mem_rd_req"); + let (cmph_mem_rd_resp_s, cmph_mem_rd_resp_r) = chan("cmph_mem_rd_resp"); + + spawn mem_reader::MemReader( + cmph_mem_rd_req_r, cmph_mem_rd_resp_s, + cmph_axi_ar_s, cmph_axi_r_r, + ); + + let (cmph_req_s, cmph_req_r) = chan("cmph_dec_req"); + let (cmph_resp_s, cmph_resp_r) = chan("cmph_dec_resp"); + + spawn comp_block_header_dec::CompressBlockHeaderDecoder( + cmph_mem_rd_req_s, cmph_mem_rd_resp_r, + cmph_req_r, cmph_resp_s + ); + + // FseProbaFreqDecoder channels + // + // This part of the decoder is used to prepare FSE lookups if they + // are provided in the encoded ZSTD bitstream (FSE_Compressed_Mode). + // This part of the design sould take the input from the input buffer + // located on the system bus, and decode the probability frequencies + // into probability distribution. The outpud data should be stored in + // a local RAM. One chalange related to this part of the code is how + // to us a single instance of the proc to prepare three different FSE tables + // (for Offsets, Matches, and Literals Lenghts). + + // spawn mem_reader::MemReader< + // AXI_DATA_W, AXI_ADDR_W, AXI_DEST_W, AXI_ID_W, CHANNEL_DEPTH + // >( + // pfd_mem_rd_req_r, pfd_mem_rd_resp_s, + // pfd_axi_ar_s, pfd_axi_r_r, + // ); + // + // spawn shift_buffer::ShiftBuffer< + // SHIFT_BUFFER_DATA_W, SHIFT_BUFFER_LENGTH_W + // >(buff_data_r, ctrl_r, out_s); + // + // + // spawn FseProbaFreqDecoder< + // FSE_DEF_PROBA_DIST_RAM_DATA_W, + // FSE_DEF_PROBA_DIST_RAM_SIZE, + // FSE_DEF_PROBA_DIST_RAM_WORD_PARTITION_SIZE + // >( + // req_r, resp_s, + // buff_in_ctrl_s, buff_out_data_r, + // rd_req_s, rd_resp_r, wr_req_s, wr_resp_r + // ); + // + // let (dpd_rd_req_r, dpd_rd_req_s) = chan("rd_req"); + // let (dpd_rd_req_r, dpd_rd_req_s) = chan("rd_resp"); + // let (dpd_wr_req_r, dpd_wr_req_s) = chan("wr_req"); + // let (dpd_wr_resp_r, dpd_wr_resp_s) = chan("wr_resp"); + // + // spawn ram::RamModel< + // FSE_PROBA_DATA_W, + // FSE_PROBA_RAM_SIZE, + // FSE_PROBA_RAM_WORD_PARTITION_SIZE + // >( + // rd_req_r, rd_resp_s, wr_req_r, wr_resp_s + // ); + + //spawn CompressBlockDecoderControl< + //>( + // req_r, resp_s + // cmph_req_s, cmph_resp_r, + //); + + //spawn fse_proba_freq_dec::FseProbaFreqDecoder(); + () + } +} + +const TEST_AXI_DATA_W = u32:64; +const TEST_AXI_ADDR_W = u32:32; +const TEST_AXI_ID_W = u32:4; +const TEST_AXI_DEST_W = u32:4; + +#[test_proc] +proc CompressBlockDecoderTest { + type Req = CompressBlockDecoderReq; + type Resp = CompressBlockDecoderResp; + + type MemAxiAr = axi::AxiAr; + type MemAxiR = axi::AxiR; + + terminator: chan out; + req_s: chan out; + resp_r: chan in; + cmph_axi_ar_r: chan in; + cmph_axi_r_s: chan out; + + init {} + config(terminator: chan out) { + let (req_s, req_r) = chan("req"); + let (resp_s, resp_r) = chan("resp"); + + let (cmph_axi_ar_s, cmph_axi_ar_r) = chan("cmph_axi_ar"); + let (cmph_axi_r_s, cmph_axi_r_r) = chan("cmph_axi_r"); + + spawn CompressBlockDecoder< + TEST_AXI_DATA_W, TEST_AXI_ADDR_W, TEST_AXI_ID_W, TEST_AXI_DEST_W + >( + req_r, resp_s, + cmph_axi_ar_s, cmph_axi_r_r); + + ( + terminator, + req_s, resp_r, + cmph_axi_ar_r, cmph_axi_r_s + ) + } + + next(state: ()) { + let tok = join(); + send(tok, terminator, true); + } +} + diff --git a/xls/modules/zstd/literals_block_header_dec.x b/xls/modules/zstd/literals_block_header_dec.x new file mode 100644 index 0000000000..86e6597c01 --- /dev/null +++ b/xls/modules/zstd/literals_block_header_dec.x @@ -0,0 +1,436 @@ +// 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 xls.modules.zstd.memory.mem_reader; + +pub enum LiteralsHeaderSize : u3 { + SINGLE_BYTE = 0, + TWO_BYTES = 1, + THREE_BYTES = 2, + COMP_THREE_BYTES = 4, + COMP_FOUR_BYTES = 5, + COMP_FIVE_BYTES = 6, +} + +pub enum LiteralsBlockType: u3 { + RAW = 0, + RLE = 1, + COMP = 2, + COMP_4 = 3, + TREELESS = 4, + TREELESS_4 = 5, +} + + +pub fn parse_literals_header_first_byte(first_byte :u8) -> (LiteralsBlockType, LiteralsHeaderSize) { + match (first_byte[:2], first_byte[2:4]) { + (u2:0, u2:1) => (LiteralsBlockType::RAW, LiteralsHeaderSize::TWO_BYTES), + (u2:0, u2:3) => (LiteralsBlockType::RAW, LiteralsHeaderSize::THREE_BYTES), + (u2:0, _) => (LiteralsBlockType::RAW, LiteralsHeaderSize::SINGLE_BYTE), + (u2:1, u2:1) => (LiteralsBlockType::RLE, LiteralsHeaderSize::TWO_BYTES), + (u2:1, u2:3) => (LiteralsBlockType::RLE, LiteralsHeaderSize::THREE_BYTES), + (u2:1, _) => (LiteralsBlockType::RLE, LiteralsHeaderSize::SINGLE_BYTE), + (u2:2, u2:0) => (LiteralsBlockType::COMP, LiteralsHeaderSize::COMP_THREE_BYTES), + (u2:2, u2:1) => (LiteralsBlockType::COMP_4, LiteralsHeaderSize::COMP_THREE_BYTES), + (u2:2, u2:2) => (LiteralsBlockType::COMP_4, LiteralsHeaderSize::COMP_FOUR_BYTES), + (u2:2, u2:3) => (LiteralsBlockType::COMP_4, LiteralsHeaderSize::COMP_FIVE_BYTES), + (u2:3, u2:0) => (LiteralsBlockType::TREELESS, LiteralsHeaderSize::COMP_THREE_BYTES), + (u2:3, u2:1) => (LiteralsBlockType::TREELESS_4, LiteralsHeaderSize::COMP_THREE_BYTES), + (u2:3, u2:2) => (LiteralsBlockType::TREELESS_4, LiteralsHeaderSize::COMP_FOUR_BYTES), + (u2:3, u2:3) => (LiteralsBlockType::TREELESS_4, LiteralsHeaderSize::COMP_FIVE_BYTES), + _ => (LiteralsBlockType::TREELESS, LiteralsHeaderSize::COMP_THREE_BYTES), + // fail!() doesn't work with quicktest, JIT failes to translate such function + //_ => fail!("Should_never_be_called", (LiteralsBlockType::RAW, LiteralsHeaderSize::SINGLE_BYTE)) + } +} + +#[quickcheck] +fn test_parse_literals_header_first_byte(x: u8) -> bool { + let (literal, length) = parse_literals_header_first_byte(x); + ((literal == LiteralsBlockType::RAW || literal == LiteralsBlockType::RLE) && + (length == LiteralsHeaderSize::SINGLE_BYTE || length == LiteralsHeaderSize::TWO_BYTES || + length == LiteralsHeaderSize::THREE_BYTES)) || + ((literal == LiteralsBlockType::COMP || literal == LiteralsBlockType::TREELESS) && + (length == LiteralsHeaderSize::COMP_THREE_BYTES)) || + ((literal == LiteralsBlockType::COMP_4 || literal == LiteralsBlockType::TREELESS_4) && + (length == LiteralsHeaderSize::COMP_THREE_BYTES || length == LiteralsHeaderSize::COMP_FOUR_BYTES || + length == LiteralsHeaderSize::COMP_FIVE_BYTES) + ) +} + +pub struct LiteralsHeader { + literal_type: LiteralsBlockType, + regenerated_size: u20, + compressed_size: u20, +} + +pub fn parse_literals_header(header_raw: u40) -> (LiteralsHeader, u3) { + let (literal_type, header_size) = parse_literals_header_first_byte(header_raw[0:8]); + let (regenerated_size, compressed_size, header_length) = match (header_size) { + LiteralsHeaderSize::SINGLE_BYTE => (header_raw[3:8] as u20, header_raw[3:8] as u20, u3:1), + LiteralsHeaderSize::TWO_BYTES => (header_raw[4:16] as u20, header_raw[4:16] as u20, u3:2), + LiteralsHeaderSize::THREE_BYTES => (header_raw[4:24] as u20, header_raw[4:24] as u20, u3:3), + LiteralsHeaderSize::COMP_THREE_BYTES => (header_raw[4:14] as u20, header_raw[14:24] as u20, u3:3), + LiteralsHeaderSize::COMP_FOUR_BYTES => (header_raw[4:18] as u20, header_raw[18:32] as u20, u3:4), + LiteralsHeaderSize::COMP_FIVE_BYTES => (header_raw[4:22] as u20, header_raw[22:40] as u20, u3:5), + // fail!() doesn't work with quicktest, JIT failes to translate such function + //_ => fail!("Unrecognized_header_sizeC" ,CompressedBlockSize { + _ => (u20:0, u20:0, u3:0), + }; + (LiteralsHeader { + literal_type: literal_type, + regenerated_size: regenerated_size, + compressed_size: match (literal_type) { + LiteralsBlockType::RLE => u20:1, + _ => compressed_size, + } + }, header_length) +} + +#[quickcheck] +fn test_parse_literals_header(x: u40) -> bool { + let (header, header_length_bytes) = parse_literals_header(x); + let (_, header_size) = parse_literals_header_first_byte(x[0:8]); + + let length_bytes_equivalence = match (header_size) { + LiteralsHeaderSize::SINGLE_BYTE => header_length_bytes == u3:1, + LiteralsHeaderSize::TWO_BYTES => header_length_bytes == u3:2, + LiteralsHeaderSize::THREE_BYTES | LiteralsHeaderSize::COMP_THREE_BYTES => header_length_bytes == u3:3, + LiteralsHeaderSize::COMP_FOUR_BYTES => header_length_bytes == u3:4, + LiteralsHeaderSize::COMP_FIVE_BYTES => header_length_bytes == u3:5, + _ => false + }; + let raw_length_equivalence = if (header.literal_type == LiteralsBlockType::RAW) { + header.regenerated_size == header.compressed_size + } else { true }; + let regen_comp_size_equivalence = if (header.literal_type == LiteralsBlockType::RAW || header.literal_type == LiteralsBlockType::RLE) { + raw_length_equivalence && match(header_size) { + LiteralsHeaderSize::SINGLE_BYTE => header.regenerated_size == x[3:8] as u20, + LiteralsHeaderSize::TWO_BYTES => header.regenerated_size == x[4:16] as u20, + LiteralsHeaderSize::THREE_BYTES => header.regenerated_size == x[4:24], + _ => false + } + } else { + match(header_size) { + LiteralsHeaderSize::COMP_THREE_BYTES => { + header.regenerated_size == x[4:14] as u20 && + header.compressed_size == x[14:24] as u20 + }, + LiteralsHeaderSize::COMP_FOUR_BYTES => { + header.regenerated_size == x[4:18] as u20 && + header.compressed_size == x[18:32] as u20 + }, + LiteralsHeaderSize::COMP_FIVE_BYTES => { + header.regenerated_size == x[4:22] as u20 && + header.compressed_size == x[22:40] as u20 + }, + _ => false + } + }; + length_bytes_equivalence && raw_length_equivalence && regen_comp_size_equivalence +} + +pub enum LiteralsHeaderDecoderStatus : u1 { + OKAY = 0, + ERROR = 1, +} + +pub struct LiteralsHeaderDecoderReq { + addr: uN[ADDR_W], +} + +pub struct LiteralsHeaderDecoderResp { + header: LiteralsHeader, + length: u3, + status: LiteralsHeaderDecoderStatus, +} + +pub proc LiteralsHeaderDecoder { + + type Req = LiteralsHeaderDecoderReq; + type Resp = LiteralsHeaderDecoderResp; + type Status = LiteralsHeaderDecoderStatus; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + type MemReaderStatus = mem_reader::MemReaderStatus; + + mem_rd_req_s: chan out; + mem_rd_resp_r: chan in; + + req_r: chan in; + resp_s: chan out; + + init {} + + config( + mem_rd_req_s: chan out, + mem_rd_resp_r: chan in, + + req_r: chan in, + resp_s: chan out, + ) { + (mem_rd_req_s, mem_rd_resp_r, req_r, resp_s) + } + + next(state: ()) { + let tok = join(); + + let (tok, decode_request) = recv(tok, req_r); + send(tok, mem_rd_req_s, MemReaderReq { + addr: decode_request.addr, + // max number of bytes that the header can have, see RFC8878 Section 3.1.1.3.1.1. + length: uN[AXI_ADDR_W]:5, + }); + // TODO: handle multiple receives on mem_rd_resp_r when AXI_DATA_W < 40 + const_assert!(AXI_DATA_W >= u32:64); + let (tok, raw) = recv(tok, mem_rd_resp_r); + let (header, length) = parse_literals_header(raw.data[:40]); + send(tok, resp_s, Resp { + header: header, + length: length, + status: match (raw.status) { + MemReaderStatus::OKAY => Status::OKAY, + MemReaderStatus::ERROR => Status::ERROR, + _ => fail!("literals_header_decoder_status_unreachable", Status::OKAY), + } + }); + } +} + +const TEST_AXI_DATA_W = u32:64; +const TEST_AXI_ADDR_W = u32:32; + +#[test_proc] +proc LiteralsHeaderDecoderTest { + type Req = LiteralsHeaderDecoderReq; + type Resp = LiteralsHeaderDecoderResp; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + type MemReaderStatus = mem_reader::MemReaderStatus; + + terminator: chan out; + + mem_rd_req_r: chan in; + mem_rd_resp_s: chan out; + req_s: chan out; + resp_r: chan in; + + init {} + + config(terminator: chan out) { + + let (mem_rd_req_s, mem_rd_req_r) = chan("mem_rd_req"); + let (mem_rd_resp_s, mem_rd_resp_r) = chan("mem_rd_resp"); + + let (req_s, req_r) = chan("req"); + let (resp_s, resp_r) = chan("resp"); + + spawn LiteralsHeaderDecoder ( + mem_rd_req_s, mem_rd_resp_r, req_r, resp_s + ); + + ( + terminator, + mem_rd_req_r, mem_rd_resp_s, + req_s, resp_r + ) + } + + next(state: ()) { + let tok = join(); + + // test data format: raw header, expected size in bytes, expected parsed header + let tests: (u40, u3, LiteralsHeader)[16] = [ + // 2 bits block type == RAW, 1 bit size_format == 0, 5 bits regenerated_size + (u40:0b10100_0_00, u3:1, LiteralsHeader { + literal_type: LiteralsBlockType::RAW, + regenerated_size: u20:0b10100, + compressed_size: u20:0b10100, + }), + // 2 bits block type == RAW, 2 bit size_format == 1, 12 bits regenerated_size + (u40:0b101010101010_01_00, u3:2, LiteralsHeader { + literal_type: LiteralsBlockType::RAW, + regenerated_size: u20:0b101010101010, + compressed_size: u20:0b101010101010, + }), + // 2 bits block type == RAW, 1 bit size_format == 2, 5 bits regenerated_size + (u40:0b10101_0_00, u3:1, LiteralsHeader { + literal_type: LiteralsBlockType::RAW, + regenerated_size: u20:0b10101, + compressed_size: u20:0b10101, + }), + // 2 bits block type == RAW, 2 bit size_format == 3, 20 bits regenerated_size + (u40:0b10101010101010101010_11_00, u3:3, LiteralsHeader { + literal_type: LiteralsBlockType::RAW, + regenerated_size: u20:0b10101010101010101010, + compressed_size: u20:0b10101010101010101010, + }), + + // 2 bits block type == RLE, 1 bit size_format == 0, 5 bits regenerated_size + (u40:0b10100_0_01, u3:1, LiteralsHeader { + literal_type: LiteralsBlockType::RLE, + regenerated_size: u20:0b10100, + compressed_size: u20:1, + }), + // 2 bits block type == RLE, 2 bits size_format == 1, 12 bits regenerated_size + (u40:0b101010101010_01_01, u3:2, LiteralsHeader { + literal_type: LiteralsBlockType::RLE, + regenerated_size: u20:0b101010101010, + compressed_size: u20:1, + }), + // 2 bits block type == RLE, 1 bit size_format == 2, 5 bits regenerated_size + (u40:0b10101_0_01, u3:1, LiteralsHeader { + literal_type: LiteralsBlockType::RLE, + regenerated_size: u20:0b10101, + compressed_size: u20:1, + }), + // 2 bits block type == RLE, 2 bits size_format == 3, 20 bits regenerated_size + (u40:0b10101010101010101010_11_01, u3:3, LiteralsHeader { + literal_type: LiteralsBlockType::RLE, + regenerated_size: u20:0b10101010101010101010, + compressed_size: u20:1, + }), + + // 2 bits block type == COMPRESSED, 2 bits size_format == 0, 10 bits regenerated_size and compressed_size + (u40:0b1010101010_0101010101_00_10, u3:3, LiteralsHeader { + literal_type: LiteralsBlockType::COMP, + regenerated_size: u20:0b0101010101, + compressed_size: u20:0b1010101010, + }), + // 2 bits block type == COMPRESSED, 2 bits size_format == 1, 10 bits regenerated_size and compressed_size + (u40:0b1010101010_0101010101_01_10, u3:3, LiteralsHeader { + literal_type: LiteralsBlockType::COMP_4, + regenerated_size: u20:0b0101010101, + compressed_size: u20:0b1010101010, + }), + // 2 bits block type == COMPRESSED, 2 bits size_format == 2, 14 bits regenerated_size and compressed_size + (u40:0b10101010101010_01010101010101_10_10, u3:4, LiteralsHeader { + literal_type: LiteralsBlockType::COMP_4, + regenerated_size: u20:0b01010101010101, + compressed_size: u20:0b10101010101010, + }), + // 2 bits block type == COMPRESSED, 2 bits size_format == 3, 18 bits regenerated_size and compressed_size + (u40:0b101010101010101010_010101010101010101_11_10, u3:5, LiteralsHeader { + literal_type: LiteralsBlockType::COMP_4, + regenerated_size: u20:0b010101010101010101, + compressed_size: u20:0b101010101010101010, + }), + + // 2 bits block type == TREELESS, 2 bits size_format == 0, 10 bits regenerated_size and compressed_size + (u40:0b1010101010_0101010101_00_11, u3:3, LiteralsHeader { + literal_type: LiteralsBlockType::TREELESS, + regenerated_size: u20:0b0101010101, + compressed_size: u20:0b1010101010, + }), + // 2 bits block type == TREELESS, 2 bits size_format == 1, 10 bits regenerated_size and compressed_size + (u40:0b1010101010_0101010101_01_11, u3:3, LiteralsHeader { + literal_type: LiteralsBlockType::TREELESS_4, + regenerated_size: u20:0b0101010101, + compressed_size: u20:0b1010101010, + }), + // 2 bits block type == TREELESS, 2 bits size_format == 2, 14 bits regenerated_size and compressed_size + (u40:0b10101010101010_01010101010101_10_11, u3:4, LiteralsHeader { + literal_type: LiteralsBlockType::TREELESS_4, + regenerated_size: u20:0b01010101010101, + compressed_size: u20:0b10101010101010, + }), + // 2 bits block type == TREELESS, 2 bits size_format == 3, 18 bits regenerated_size and compressed_size + (u40:0b101010101010101010_010101010101010101_11_11, u3:5, LiteralsHeader { + literal_type: LiteralsBlockType::TREELESS_4, + regenerated_size: u20:0b010101010101010101, + compressed_size: u20:0b101010101010101010, + }), + ]; + const ADDR = uN[TEST_AXI_ADDR_W]:0xDEAD; + + // positive cases + let tok = for ((_, (test_vec, expected_length, expected_header)), tok): ((u32, (u40, u3, LiteralsHeader)), token) in enumerate(tests) { + send(tok, req_s, Req { + addr: ADDR, + }); + let (tok, req) = recv(tok, mem_rd_req_r); + assert_eq(req, MemReaderReq { + addr: ADDR, + length: uN[TEST_AXI_ADDR_W]:5 + }); + let tok = send(tok, mem_rd_resp_s, MemReaderResp { + status: MemReaderStatus::OKAY, + data: test_vec as uN[TEST_AXI_DATA_W], + length: uN[TEST_AXI_ADDR_W]:5, + last: true, + }); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, LiteralsHeaderDecoderResp { + header: expected_header, + status: LiteralsHeaderDecoderStatus::OKAY, + length: expected_length, + }); + tok + }(tok); + + // negative case: AXI Error + send(tok, req_s, Req { + addr: ADDR, + }); + let (tok, req) = recv(tok, mem_rd_req_r); + assert_eq(req, MemReaderReq { + addr: ADDR, + length: uN[TEST_AXI_ADDR_W]:5 + }); + let tok = send(tok, mem_rd_resp_s, MemReaderResp { + status: MemReaderStatus::ERROR, + data: uN[TEST_AXI_DATA_W]:0, + length: uN[TEST_AXI_ADDR_W]:0, + last: true, + }); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp.status, LiteralsHeaderDecoderStatus::ERROR); + + send(join(), terminator, true); + } +} + +proc LiteralsHeaderDecoderInst { + type Req = LiteralsHeaderDecoderReq; + type Resp = LiteralsHeaderDecoderResp; + type ReaderReq = mem_reader::MemReaderReq; + type ReaderResp = mem_reader::MemReaderResp; + + reader_req_s: chan out; + reader_resp_r: chan in; + + decode_req_r: chan in; + decode_resp_s: chan out; + + config( + reader_req_s: chan out, + reader_resp_r: chan in, + decode_req_r: chan in, + decode_resp_s: chan out, + ) { + spawn LiteralsHeaderDecoder( + reader_req_s, + reader_resp_r, + decode_req_r, + decode_resp_s + ); + (reader_req_s, reader_resp_r, decode_req_r, decode_resp_s) + } + + init {} + + next(state: ()) {} +} diff --git a/xls/modules/zstd/ram_wr_handler.x b/xls/modules/zstd/ram_wr_handler.x index a59fd0b256..c7891e270e 100644 --- a/xls/modules/zstd/ram_wr_handler.x +++ b/xls/modules/zstd/ram_wr_handler.x @@ -19,7 +19,7 @@ import std; import xls.examples.ram; -proc RamWrRespHandler { +pub proc RamWrRespHandler { type Reset = bool; type WriteCnt = bits[CNT_WIDTH]; type WriteResp = ram::WriteResp; diff --git a/xls/modules/zstd/sequence_conf_dec.x b/xls/modules/zstd/sequence_conf_dec.x new file mode 100644 index 0000000000..166df10d8c --- /dev/null +++ b/xls/modules/zstd/sequence_conf_dec.x @@ -0,0 +1,342 @@ +// 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 xls.modules.zstd.memory.mem_reader; +import xls.modules.zstd.common; + +type CompressionMode = common::CompressionMode; +type SequenceConf = common::SequenceConf; + +enum SequenceHeaderSize : u2 { + TWO_BYTES = 0, + THREE_BYTES = 1, + FOUR_BYTES = 2, +} + +fn parse_sequence_first_byte(byte :u8) -> SequenceHeaderSize { + if byte == u8:0 { + SequenceHeaderSize::TWO_BYTES + } else if byte == u8:255 { + SequenceHeaderSize::FOUR_BYTES + } else if byte[7:] == u1:1 { + SequenceHeaderSize::THREE_BYTES + } else { + SequenceHeaderSize::TWO_BYTES + } +} + +fn extract_seq_mode(byte: u8) -> (CompressionMode, CompressionMode, CompressionMode) { + (byte[2:4] as CompressionMode, + byte[4:6] as CompressionMode, + byte[6:] as CompressionMode) +} + +pub fn parse_sequence_conf(header: u32) -> (SequenceConf, u3) { + let header_size = parse_sequence_first_byte(header[:8]); + let sum1 = (header[0:7] ++ header[8:16]) as u17; + let sum2 = sum1 + (header[16:24] ++ u8:0) as u17 ; + match(header_size) { + SequenceHeaderSize::TWO_BYTES => { + let (match_mode, offset_mode, literals_mode) = extract_seq_mode(header[8:16]); + (SequenceConf{ + sequence_count: header[:8] as u17, + match_mode: match_mode, + offset_mode: offset_mode, + literals_mode: literals_mode, + }, u3:2) + }, + SequenceHeaderSize::THREE_BYTES => { + let (match_mode, offset_mode, literals_mode) = extract_seq_mode(header[16:24]); + (SequenceConf{ + sequence_count: sum1, + match_mode: match_mode, + offset_mode: offset_mode, + literals_mode: literals_mode, + }, u3:3) + }, + SequenceHeaderSize::FOUR_BYTES => { + let (match_mode, offset_mode, literals_mode) = extract_seq_mode(header[24:32]); + (SequenceConf{ + sequence_count: sum2, + match_mode: match_mode, + offset_mode: offset_mode, + literals_mode: literals_mode, + }, u3:4) + }, + _ => (zero!(), u3:0) + // fail!() doesn't work with quicktest, JIT failes to translate such function + // _ => fail!("Incorrect_header_size", zero!()) + } +} + +#[quickcheck(test_count=50000)] +fn test_parse_sequence_conf(x: u32) -> bool { + // let length = parse_sequence_first_byte(x[0:8]); + let (seq_conf, length) = parse_sequence_conf(x); + let byte0 = x[0:8]; + let byte1 = x[8:16]; + let byte2 = x[16:24]; + + if x[0:8] < u8:128 { + length == u3:2 && seq_conf.sequence_count == byte0 as u17 + } else if x[0:8] < u8:255 { + length == u3:3 && seq_conf.sequence_count == (((byte0 - u8:128) as u17) << u8:8) as u17 + byte1 as u17 + } else { + length == u3:4 && seq_conf.sequence_count == u17:0x7f00 + byte1 as u17 + ((byte2 as u17) << u8:8) as u17 + } +} + + +pub enum SequenceConfDecoderStatus : u1 { + OKAY = 0, + ERROR = 1, +} + +pub struct SequenceConfDecoderReq { + addr: uN[ADDR_W], +} + +pub struct SequenceConfDecoderResp { + header: SequenceConf, + length: u3, + status: SequenceConfDecoderStatus, +} + +pub proc SequenceConfDecoder { + + type Req = SequenceConfDecoderReq; + type Resp = SequenceConfDecoderResp; + type Status = SequenceConfDecoderStatus; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + type MemReaderStatus = mem_reader::MemReaderStatus; + + mem_rd_req_s: chan out; + mem_rd_resp_r: chan in; + + req_r: chan in; + resp_s: chan out; + + init {} + + config( + mem_rd_req_s: chan out, + mem_rd_resp_r: chan in, + + req_r: chan in, + resp_s: chan out, + ) { + (mem_rd_req_s, mem_rd_resp_r, req_r, resp_s) + } + + next(state: ()) { + let tok = join(); + + let (tok, decode_request) = recv(tok, req_r); + send(tok, mem_rd_req_s, MemReaderReq { + addr: decode_request.addr, + // max number of bytes that the header can have, see RFC8878 Section 3.1.1.3.2.1. + length: uN[AXI_ADDR_W]:4, + }); + // TODO: handle multiple receives on mem_rd_resp_r when AXI_DATA_W < 32 + const_assert!(AXI_DATA_W >= u32:32); + let (tok, raw) = recv(tok, mem_rd_resp_r); + let (header, length) = parse_sequence_conf(raw.data[:32]); + send(tok, resp_s, Resp { + header: header, + length: length, + status: match (raw.status) { + MemReaderStatus::OKAY => Status::OKAY, + MemReaderStatus::ERROR => Status::ERROR, + _ => fail!("literals_header_decoder_status_unreachable", Status::OKAY), + } + }); + } +} + +const TEST_AXI_DATA_W = u32:64; +const TEST_AXI_ADDR_W = u32:32; + +#[test_proc] +proc SequenceConfDecoderTest { + type Req = SequenceConfDecoderReq; + type Resp = SequenceConfDecoderResp; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + type MemReaderStatus = mem_reader::MemReaderStatus; + + terminator: chan out; + + mem_rd_req_r: chan in; + mem_rd_resp_s: chan out; + req_s: chan out; + resp_r: chan in; + + init {} + + config(terminator: chan out) { + + let (mem_rd_req_s, mem_rd_req_r) = chan("mem_rd_req"); + let (mem_rd_resp_s, mem_rd_resp_r) = chan("mem_rd_resp"); + + let (req_s, req_r) = chan("req"); + let (resp_s, resp_r) = chan("resp"); + + spawn SequenceConfDecoder ( + mem_rd_req_s, mem_rd_resp_r, req_r, resp_s + ); + + ( + terminator, + mem_rd_req_r, mem_rd_resp_s, + req_s, resp_r + ) + } + + next(state: ()) { + let tok = join(); + + // test data format: raw header, expected size in bytes, expected parsed header + let tests: (u32, u3, SequenceConf)[8] = [ + (u32:0x00_00, u3:2, SequenceConf { + sequence_count: u17:0, + literals_mode: CompressionMode::PREDEFINED, + offset_mode: CompressionMode::PREDEFINED, + match_mode: CompressionMode::PREDEFINED, + }), + (u32:0x6C_00, u3:2, SequenceConf { + sequence_count: u17:0, + literals_mode: CompressionMode::RLE, + offset_mode: CompressionMode::COMPRESSED, + match_mode: CompressionMode::REPEAT, + }), + (u32:0xE4_01, u3:2, SequenceConf { + sequence_count: u17:0x01, + literals_mode: CompressionMode::REPEAT, + offset_mode: CompressionMode::COMPRESSED, + match_mode: CompressionMode::RLE, + }), + (u32:0xAC_7F, u3:2, SequenceConf { + sequence_count: u17:0x7F, + literals_mode: CompressionMode::COMPRESSED, + offset_mode: CompressionMode::COMPRESSED, + match_mode: CompressionMode::REPEAT, + }), + (u32:0x84_0080, u3:3, SequenceConf { + sequence_count: u17:0, + literals_mode: CompressionMode::COMPRESSED, + offset_mode: CompressionMode::PREDEFINED, + match_mode: CompressionMode::RLE, + }), + (u32:0x18_FFFE, u3:3, SequenceConf { + sequence_count: u17:0x7EFF, + literals_mode: CompressionMode::PREDEFINED, + offset_mode: CompressionMode::RLE, + match_mode: CompressionMode::COMPRESSED, + }), + (u32:0x70_0000FF, u3:4, SequenceConf { + sequence_count: u17:0x7F00, + literals_mode: CompressionMode::RLE, + offset_mode: CompressionMode::REPEAT, + match_mode: CompressionMode::PREDEFINED, + }), + (u32:0x68_FFFFFF, u3:4, SequenceConf { + sequence_count: u17:0x17EFF, + literals_mode: CompressionMode::RLE, + offset_mode: CompressionMode::COMPRESSED, + match_mode: CompressionMode::COMPRESSED, + }), + ]; + const ADDR = uN[TEST_AXI_ADDR_W]:0xDEAD; + + // positive cases + let tok = for ((_, (test_vec, expected_length, expected_header)), tok): ((u32, (u32, u3, SequenceConf)), token) in enumerate(tests) { + send(tok, req_s, Req { + addr: ADDR, + }); + let (tok, req) = recv(tok, mem_rd_req_r); + assert_eq(req, MemReaderReq { + addr: ADDR, + length: uN[TEST_AXI_ADDR_W]:4 + }); + let tok = send(tok, mem_rd_resp_s, MemReaderResp { + status: MemReaderStatus::OKAY, + data: test_vec as uN[TEST_AXI_DATA_W], + length: uN[TEST_AXI_ADDR_W]:4, + last: true, + }); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, SequenceConfDecoderResp { + header: expected_header, + status: SequenceConfDecoderStatus::OKAY, + length: expected_length, + }); + tok + }(tok); + + // negative case: AXI Error + send(tok, req_s, Req { + addr: ADDR, + }); + let (tok, req) = recv(tok, mem_rd_req_r); + assert_eq(req, MemReaderReq { + addr: ADDR, + length: uN[TEST_AXI_ADDR_W]:4 + }); + let tok = send(tok, mem_rd_resp_s, MemReaderResp { + status: MemReaderStatus::ERROR, + data: uN[TEST_AXI_DATA_W]:0, + length: uN[TEST_AXI_ADDR_W]:0, + last: true, + }); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp.status, SequenceConfDecoderStatus::ERROR); + + send(join(), terminator, true); + } +} + +proc SequenceConfDecoderInst { + type Req = SequenceConfDecoderReq; + type Resp = SequenceConfDecoderResp; + type ReaderReq = mem_reader::MemReaderReq; + type ReaderResp = mem_reader::MemReaderResp; + + reader_req_s: chan out; + reader_resp_r: chan in; + + decode_req_r: chan in; + decode_resp_s: chan out; + + config( + reader_req_s: chan out, + reader_resp_r: chan in, + decode_req_r: chan in, + decode_resp_s: chan out, + ) { + spawn SequenceConfDecoder( + reader_req_s, + reader_resp_r, + decode_req_r, + decode_resp_s + ); + (reader_req_s, reader_resp_r, decode_req_r, decode_resp_s) + } + + init {} + + next(state: ()) {} +} From 95eec628397479ede58c91f515cf6e878132be1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Ob=C5=82onczek?= Date: Tue, 16 Apr 2024 18:58:28 +0200 Subject: [PATCH 36/85] modules: Add ShiftBuffer and RefillingShiftBuffer implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert Winkler Co-authored-by: Pawel Czarnecki Signed-off-by: Robert Winkler Signed-off-by: Pawel Czarnecki Signed-off-by: Krzysztof Obłonczek --- xls/modules/shift_buffer/BUILD | 285 ++++++ xls/modules/shift_buffer/fixme.x | 52 ++ xls/modules/shift_buffer/math.x | 97 ++ xls/modules/shift_buffer/shift_buffer.x | 641 +++++++++++++ xls/modules/shift_buffer/shift_buffer_inst.x | 71 ++ xls/modules/zstd/BUILD | 167 ++-- xls/modules/zstd/refilling_shift_buffer.x | 889 +++++++++++++++++++ xls/modules/zstd/shift_buffer.x | 246 ----- 8 files changed, 2117 insertions(+), 331 deletions(-) create mode 100644 xls/modules/shift_buffer/BUILD create mode 100644 xls/modules/shift_buffer/fixme.x create mode 100644 xls/modules/shift_buffer/math.x create mode 100644 xls/modules/shift_buffer/shift_buffer.x create mode 100644 xls/modules/shift_buffer/shift_buffer_inst.x create mode 100644 xls/modules/zstd/refilling_shift_buffer.x delete mode 100644 xls/modules/zstd/shift_buffer.x diff --git a/xls/modules/shift_buffer/BUILD b/xls/modules/shift_buffer/BUILD new file mode 100644 index 0000000000..201bbf6fcc --- /dev/null +++ b/xls/modules/shift_buffer/BUILD @@ -0,0 +1,285 @@ +# 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 is a pipeline-aware data buffer. The implementation aligns incoming data +# before it enters the buffer and mutates its state. + +load("@rules_hdl//place_and_route:build_defs.bzl", "place_and_route") +load("@rules_hdl//synthesis:build_defs.bzl", "benchmark_synth", "synthesize_rtl") +load("@rules_hdl//verilog:providers.bzl", "verilog_library") +load( + "//xls/build_rules:xls_build_defs.bzl", + "xls_benchmark_ir", + "xls_benchmark_verilog", + "xls_dslx_ir", + "xls_dslx_library", + "xls_dslx_opt_ir", + "xls_dslx_test", + "xls_dslx_verilog", + "xls_ir_opt_ir", + "xls_ir_verilog", +) + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//xls:xls_users"], + licenses = ["notice"], +) + +xls_dslx_library( + name = "fixme_dslx", + srcs = ["fixme.x"], +) + +xls_dslx_test( + name = "fixme_dslx_test", + library = ":fixme_dslx", +) + +xls_dslx_library( + name = "math_dslx", + srcs = ["math.x"], + deps = [":fixme_dslx"] +) + +xls_dslx_test( + name = "math_dslx_test", + library = ":math_dslx", +) + +## ShiftBuffer + +xls_dslx_library( + name = "shift_buffer_dslx", + srcs = ["shift_buffer.x"], + deps = [ + ":math_dslx", + ":fixme_dslx", + ], +) + +xls_dslx_test( + name = "shift_buffer_dslx_test", + library = ":shift_buffer_dslx", +) + +xls_dslx_library( + name = "shift_buffer_inst_dslx", + srcs = ["shift_buffer_inst.x"], + deps = [ + ":shift_buffer_dslx", + ], +) + +# ShiftBufferAligner + +xls_dslx_verilog( + name = "shift_buffer_aligner_verilog", + codegen_args = { + "module_name": "ShiftBufferAligner", + "generator": "pipeline", + "delay_model": "asap7", + "pipeline_stages": "2", + "reset": "rst", + "worst_case_throughput": "1", + "use_system_verilog": "false", + }, + dslx_top = "ShiftBufferAlignerInst", + library = ":shift_buffer_inst_dslx", + opt_ir_args = { + "top": "__xls_modules_shift_buffer_shift_buffer__ShiftBufferAlignerInst__ShiftBufferAligner_0__64_128_7_next", + }, + verilog_file = "shift_buffer_aligner.v", +) + +xls_benchmark_ir( + name = "shift_buffer_aligner_opt_ir_benchmark", + src = ":shift_buffer_aligner_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "2", + "delay_model": "asap7", + }, +) + +verilog_library( + name = "shift_buffer_aligner_verilog_lib", + srcs = [ + ":shift_buffer_aligner.v", + ], +) + +synthesize_rtl( + name = "shift_buffer_aligner_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "ShiftBufferAligner", + deps = [ + ":shift_buffer_aligner_verilog_lib", + ], +) + +benchmark_synth( + name = "shift_buffer_aligner_benchmark_synth", + synth_target = ":shift_buffer_aligner_synth_asap7", +) + +place_and_route( + name = "shift_buffer_aligner_place_and_route", + clock_period = "650", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":shift_buffer_aligner_synth_asap7", + target_die_utilization_percentage = "5", +) + +xls_benchmark_verilog( + name = "shift_buffer_aligner_verilog_benchmark", + verilog_target = "shift_buffer_aligner_verilog", +) + +## ShiftBufferStorage + +xls_dslx_verilog( + name = "shift_buffer_storage_verilog", + codegen_args = { + "module_name": "ShiftBufferStorage", + "generator": "pipeline", + "delay_model": "asap7", + "pipeline_stages": "2", + "reset": "rst", + "worst_case_throughput": "1", + "use_system_verilog": "false", + }, + dslx_top = "ShiftBufferStorageInst", + library = ":shift_buffer_inst_dslx", + opt_ir_args = { + "top": "__xls_modules_shift_buffer_shift_buffer__ShiftBufferStorageInst__ShiftBufferStorage_0__64_7_next", + }, + verilog_file = "shift_buffer_storage.v", +) + +xls_benchmark_ir( + name = "shift_buffer_storage_opt_ir_benchmark", + src = ":shift_buffer_storage_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "2", + "delay_model": "asap7", + }, +) + +verilog_library( + name = "shift_buffer_storage_verilog_lib", + srcs = [ + ":shift_buffer_storage.v", + ], +) + +synthesize_rtl( + name = "shift_buffer_storage_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "ShiftBufferStorage", + deps = [ + ":shift_buffer_storage_verilog_lib", + ], +) + +benchmark_synth( + name = "shift_buffer_storage_benchmark_synth", + synth_target = ":shift_buffer_storage_synth_asap7", +) + +place_and_route( + name = "shift_buffer_storage_place_and_route", + clock_period = "650", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":shift_buffer_storage_synth_asap7", + target_die_utilization_percentage = "5", +) + +xls_benchmark_verilog( + name = "shift_buffer_storage_verilog_benchmark", + verilog_target = "shift_buffer_storage_verilog", +) + +## ShiftBuffer + +xls_dslx_verilog( + name = "shift_buffer_verilog", + codegen_args = { + "module_name": "ShiftBuffer", + "generator": "pipeline", + "delay_model": "asap7", + "pipeline_stages": "2", + "reset": "rst", + "worst_case_throughput": "1", + "use_system_verilog": "false", + }, + dslx_top = "ShiftBufferInst", + library = ":shift_buffer_inst_dslx", + opt_ir_args = { + "inline_procs": "true", + "top": "__xls_modules_shift_buffer_shift_buffer__ShiftBufferInst__ShiftBuffer_0__ShiftBufferStorage_0__64_7_next", + }, + verilog_file = "shift_buffer.v", +) + +xls_benchmark_ir( + name = "shift_buffer_opt_ir_benchmark", + src = ":shift_buffer_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "2", + "delay_model": "asap7", + }, +) + +verilog_library( + name = "shift_buffer_verilog_lib", + srcs = [ + ":shift_buffer.v", + ], +) + +synthesize_rtl( + name = "shift_buffer_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "ShiftBuffer", + deps = [ + ":shift_buffer_verilog_lib", + ], +) + +benchmark_synth( + name = "shift_buffer_benchmark_synth", + synth_target = ":shift_buffer_synth_asap7", +) + +place_and_route( + name = "shift_buffer_place_and_route", + clock_period = "650", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":shift_buffer_synth_asap7", + target_die_utilization_percentage = "5", +) + +xls_benchmark_verilog( + name = "shift_buffer_verilog_benchmark", + verilog_target = "shift_buffer_verilog", +) diff --git a/xls/modules/shift_buffer/fixme.x b/xls/modules/shift_buffer/fixme.x new file mode 100644 index 0000000000..9fa79247c1 --- /dev/null +++ b/xls/modules/shift_buffer/fixme.x @@ -0,0 +1,52 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub fn fast_if(cond: bool, arg1: uN[N], arg2: uN[N]) -> uN[N] { + let mask = if cond { !bits[N]:0 } else { bits[N]:0 }; + (arg1 & mask) | (arg2 & !mask) +} + +pub fn fast_if_tuple_2 + (cond: bool, arg1: (uN[N], uN[M]), arg2: (uN[N], uN[M])) -> (uN[N], uN[M]) { + (fast_if(cond, arg1.0, arg2.0), fast_if(cond, arg1.1, arg2.1)) +} + +pub fn fast_if_tuple_3 + (cond: bool, arg1: (uN[N], uN[M], uN[O]), arg2: (uN[N], uN[M], uN[O])) + -> (uN[N], uN[M], uN[O]) { + (fast_if(cond, arg1.0, arg2.0), fast_if(cond, arg1.1, arg2.1), fast_if(cond, arg1.2, arg2.2)) +} + +#[test] +fn fast_if_test() { + assert_eq(if true { u32:1 } else { u32:5 }, fast_if(true, u32:1, u32:5)); + assert_eq(if false { u32:1 } else { u32:5 }, fast_if(false, u32:1, u32:5)); + + assert_eq( + if true { + trace_fmt!("if: true"); + u16:74 + } else { + trace_fmt!("if: false"); + u16:32 + }, + fast_if(true, { + trace_fmt!("fast_if: true"); + u16:74 + }, { + trace_fmt!("fast_if: false"); + u16:32 + }) + ); +} diff --git a/xls/modules/shift_buffer/math.x b/xls/modules/shift_buffer/math.x new file mode 100644 index 0000000000..057a843556 --- /dev/null +++ b/xls/modules/shift_buffer/math.x @@ -0,0 +1,97 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import std; +import xls.modules.shift_buffer.fixme; + +// Log-depth shift bits left +pub fn logshiftl(n: bits[N], r: bits[R]) -> bits[N] { + for (i, y) in u32:0..R { + fixme::fast_if(r[i+:u1], { y << (bits[R]:1 << i) }, { y }) + }(n as bits[N]) +} + +#[test] +fn logshiftl_test() { + // Test varying base + assert_eq(logshiftl(bits[64]:0, bits[6]:3), bits[64]:0 << u32:3); + assert_eq(logshiftl(bits[64]:1, bits[6]:3), bits[64]:1 << u32:3); + assert_eq(logshiftl(bits[64]:2, bits[6]:3), bits[64]:2 << u32:3); + assert_eq(logshiftl(bits[64]:3, bits[6]:3), bits[64]:3 << u32:3); + assert_eq(logshiftl(bits[64]:4, bits[6]:3), bits[64]:4 << u32:3); + + // Test varying exponent + assert_eq(logshiftl(bits[64]:50, bits[6]:0), bits[64]:50 << u32:0); + assert_eq(logshiftl(bits[64]:50, bits[6]:1), bits[64]:50 << u32:1); + assert_eq(logshiftl(bits[64]:50, bits[6]:2), bits[64]:50 << u32:2); + assert_eq(logshiftl(bits[64]:50, bits[6]:3), bits[64]:50 << u32:3); + assert_eq(logshiftl(bits[64]:50, bits[6]:4), bits[64]:50 << u32:4); + + // Test overflow + let max = std::unsigned_max_value(); + assert_eq(logshiftl(max, u4:4), max << u4:4); + assert_eq(logshiftl(max, u4:5), max << u4:5); + assert_eq(logshiftl(max, u4:15), max << u4:15); + assert_eq(logshiftl(bits[24]:0xc0ffee, u8:12), bits[24]:0xfee000); +} + + +// Log-depth shift bits right +pub fn logshiftr(n: bits[N], r: bits[R]) -> bits[N] { + for (i, y) in u32:0..R { + fixme::fast_if(r[i+:u1], { y >> (bits[R]:1 << i) }, { y }) + }(n as bits[N]) +} + +#[test] +fn logshiftr_test() { + // Test varying base + assert_eq(logshiftr(bits[64]:0x0fac4e782, bits[6]:3), bits[64]:0x0fac4e782 >> u32:3); + assert_eq(logshiftr(bits[64]:0x1fac4e782, bits[6]:3), bits[64]:0x1fac4e782 >> u32:3); + assert_eq(logshiftr(bits[64]:0x2fac4e782, bits[6]:3), bits[64]:0x2fac4e782 >> u32:3); + assert_eq(logshiftr(bits[64]:0x3fac4e782, bits[6]:3), bits[64]:0x3fac4e782 >> u32:3); + assert_eq(logshiftr(bits[64]:0x4fac4e782, bits[6]:3), bits[64]:0x4fac4e782 >> u32:3); + + // Test varying exponent + assert_eq(logshiftr(bits[64]:0x50fac4e782, bits[6]:0), bits[64]:0x50fac4e782 >> u32:0); + assert_eq(logshiftr(bits[64]:0x50fac4e782, bits[6]:1), bits[64]:0x50fac4e782 >> u32:1); + assert_eq(logshiftr(bits[64]:0x50fac4e782, bits[6]:2), bits[64]:0x50fac4e782 >> u32:2); + assert_eq(logshiftr(bits[64]:0x50fac4e782, bits[6]:3), bits[64]:0x50fac4e782 >> u32:3); + assert_eq(logshiftr(bits[64]:0x50fac4e782, bits[6]:4), bits[64]:0x50fac4e782 >> u32:4); + + // Test overflow + let max = std::unsigned_max_value(); + assert_eq(logshiftr(max, u4:4), max >> u4:4); + assert_eq(logshiftr(max, u4:5), max >> u4:5); + assert_eq(logshiftr(max, u4:15), max >> u4:15); + assert_eq(logshiftr(bits[24]:0xc0ffee, u8:12), bits[24]:0x000c0f); +} + +// Return given value with m first bits masked +pub fn mask(n: bits[N], m: bits[M]) -> bits[N] { + n & (std::mask_bits() >> (N as bits[M] - m)) +} + +#[test] +fn mask_test() { + assert_eq(mask(u8:0b11111111, u4:0), u8:0b00000000); + assert_eq(mask(u8:0b11111111, u4:1), u8:0b00000001); + assert_eq(mask(u8:0b11111111, u4:2), u8:0b00000011); + assert_eq(mask(u8:0b11111111, u4:4), u8:0b00001111); + assert_eq(mask(u8:0b11111111, u4:8), u8:0b11111111); + assert_eq(mask(u8:0b11111111, u4:9), u8:0b00000000); // FIXME: sketchy result, I would expect + // 0b11111111 +} + + diff --git a/xls/modules/shift_buffer/shift_buffer.x b/xls/modules/shift_buffer/shift_buffer.x new file mode 100644 index 0000000000..add66ffe3e --- /dev/null +++ b/xls/modules/shift_buffer/shift_buffer.x @@ -0,0 +1,641 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import std; +import xls.modules.shift_buffer.fixme; +import xls.modules.shift_buffer.math; + +pub enum ShiftBufferStatus : u1 { + OK = 0, + ERROR = 1, +} + +pub fn length_width(data_width: u32) -> u32 { + std::clog2(data_width + u32:1) +} + +// Common definition for buffer input and output payload +pub struct ShiftBufferPacket { + data: uN[DATA_WIDTH], + length: uN[LENGTH_WIDTH], +} + +// Output structure - packet with embedded status of the buffer operation +pub type ShiftBufferOutput = ShiftBufferPacket; + +// Buffer pop command +pub struct ShiftBufferCtrl { + length: uN[LENGTH_WIDTH] +} + +struct ShiftBufferAlignerState { + ptr: uN[LENGTH_WIDTH] +} + +pub proc ShiftBufferAligner< + DATA_WIDTH: u32, + LENGTH_WIDTH: u32 = {length_width(DATA_WIDTH)}, + DATA_WIDTH_X2: u32 = {DATA_WIDTH * u32:2}, +> { + type Length = uN[LENGTH_WIDTH]; + type Data = uN[DATA_WIDTH]; + type DataX2 = uN[DATA_WIDTH_X2]; + + type State = ShiftBufferAlignerState; + type Input = ShiftBufferPacket; + type Inter = ShiftBufferPacket; + + input_r: chan in; + inter_s: chan out; + + config( + input_r: chan in, + inter_s: chan out, + ) { + (input_r, inter_s) + } + + init {zero!()} + + next(state: State) { + // FIXME: Remove when https://github.com/google/xls/issues/1368 is resolved + type Inter = ShiftBufferPacket; + + let tok = join(); + + let (tok0, data) = recv(tok, input_r); + let tok0 = send(tok0, inter_s, Inter { + length: data.length, + data: math::logshiftl(data.data as DataX2, state.ptr), + }); + + State {ptr: (state.ptr + data.length) % (DATA_WIDTH as Length) } + } +} + +const ALIGNER_TEST_DATA_WIDTH = u32:64; +const ALIGNER_TEST_LENGTH_WIDTH = length_width(ALIGNER_TEST_DATA_WIDTH); +const ALIGNER_TEST_DATA_WIDTH_X2 = ALIGNER_TEST_DATA_WIDTH * u32:2; + +#[test_proc] +proc ShiftBufferAlignerTest { + terminator: chan out; + type Input = ShiftBufferPacket; + type Inter = ShiftBufferPacket; + + type Data = uN[ALIGNER_TEST_DATA_WIDTH]; + type Length = uN[ALIGNER_TEST_LENGTH_WIDTH]; + type DataX2 = uN[ALIGNER_TEST_DATA_WIDTH_X2]; + + input_s: chan out; + inter_r: chan in; + + config(terminator: chan out) { + let (input_s, input_r) = chan("input"); + let (inter_s, inter_r) = chan("inter"); + + spawn ShiftBufferAligner(input_r, inter_s); + + (terminator, input_s, inter_r) + } + + init { } + + next(state: ()) { + let tok = send(join(), input_s, Input { data: Data:0xAABB_CCDD, length: Length:32}); + let tok = send(tok, input_s, Input { data: Data:0x1122, length: Length:16}); + let tok = send(tok, input_s, Input { data: Data:0x33, length: Length:8}); + let tok = send(tok, input_s, Input { data: Data:0x44, length: Length:8}); + let tok = send(tok, input_s, Input { data: Data:0xFFFF, length: Length:4}); + let tok = send(tok, input_s, Input { data: Data:0x0, length: Length:0}); + let tok = send(tok, input_s, Input { data: Data:0x0, length: Length:4}); + let tok = send(tok, input_s, Input { data: Data:0x1, length: Length:1}); + let tok = send(tok, input_s, Input { data: Data:0xF, length: Length:3}); + let tok = send(tok, input_s, Input { data: Data:0xF, length: Length:4}); + + let (tok, data) = recv(tok, inter_r); + assert_eq(data, Inter { data: DataX2: 0xAABB_CCDD, length: Length: 32}); + let (tok, data) = recv(tok, inter_r); + assert_eq(data, Inter { data: DataX2: 0x1122_0000_0000, length: Length: 16}); + let (tok, data) = recv(tok, inter_r); + assert_eq(data, Inter { data: DataX2: 0x33_0000_0000_0000, length: Length: 8}); + let (tok, data) = recv(tok, inter_r); + assert_eq(data, Inter { data: DataX2: 0x4400_0000_0000_0000, length: Length: 8}); + let (tok, data) = recv(tok, inter_r); + assert_eq(data, Inter { data: DataX2:0xFFFF, length: Length:4}); + let (tok, data) = recv(tok, inter_r); + assert_eq(data, Inter { data: DataX2:0x0, length: Length:0}); + let (tok, data) = recv(tok, inter_r); + assert_eq(data, Inter { data: DataX2:0x00, length: Length:4}); + let (tok, data) = recv(tok, inter_r); + assert_eq(data, Inter { data: DataX2:0x100, length: Length:1}); + let (tok, data) = recv(tok, inter_r); + assert_eq(data, Inter { data: DataX2:0x1E00, length: Length:3}); + let (tok, data) = recv(tok, inter_r); + assert_eq(data, Inter { data: DataX2:0xF000, length: Length:4}); + + send(tok, terminator, true); + } +} + +struct ShiftBufferStorageState { + buffer: bits[BUFFER_WIDTH], // The storage element. + buffer_cnt: bits[LENGTH_WIDTH + u32:2], // Number of valid bits in the buffer. + read_ptr: bits[LENGTH_WIDTH + u32:2], // First occupied bit in the buffer when buffer_cnt > 0. + write_ptr: bits[LENGTH_WIDTH + u32:2], // First free bit in the buffer. + cmd: ShiftBufferCtrl, // Received command of ShiftBufferCtrl type. + cmd_valid: bool, // Field cmd is valid. +} + +pub proc ShiftBufferStorage { + type Buffer = bits[DATA_WIDTH * u32:3]; + type BufferLength = bits[LENGTH_WIDTH + u32:2]; // TODO: where does this "+ u32:2" come from? shouldn't it be number_of_bits_required_to_represent(DATA_WIDTH * u32:3)? + type Data = bits[DATA_WIDTH]; + type DataLength = bits[LENGTH_WIDTH]; + type State = ShiftBufferStorageState; + type Ctrl = ShiftBufferCtrl; + type Inter = ShiftBufferPacket; + type Output = ShiftBufferOutput; + ctrl: chan> in; + inter: chan> in; + output: chan> out; + + config( + ctrl: chan> in, + inter: chan> in, + output: chan> out, + ) { + (ctrl, inter, output) + } + + init { + type State = ShiftBufferStorageState<{DATA_WIDTH * u32:3}, LENGTH_WIDTH>; + zero!() + } + + next(state: State<{DATA_WIDTH * u32:3}, LENGTH_WIDTH>) { + type State = ShiftBufferStorageState<{DATA_WIDTH * u32:3}, LENGTH_WIDTH>; + type Ctrl = ShiftBufferCtrl; + type Inter = ShiftBufferPacket<{DATA_WIDTH * u32:2}, LENGTH_WIDTH>; + type Output = ShiftBufferOutput; + type OutputPayload = ShiftBufferPacket; + type OutputStatus = ShiftBufferStatus; + type DataLength = bits[LENGTH_WIDTH]; + // trace_fmt!("state: {:#x}", state); + + const MAX_BUFFER_CNT = (DATA_WIDTH * u32:3) as BufferLength; + + let shift_buffer_right = state.read_ptr >= (DATA_WIDTH as BufferLength); + // trace_fmt!("shift_buffer_right: {:#x}", shift_buffer_right); + let shift_data_left = + state.write_ptr >= (DATA_WIDTH as BufferLength) && !shift_buffer_right; + // trace_fmt!("shift_data_left: {:#x}", shift_data_left); + let recv_new_input = state.write_ptr < (DATA_WIDTH * u32:2) as BufferLength; + // trace_fmt!("recv_new_input: {:#x}", recv_new_input); + let has_enough_data = (state.cmd.length as BufferLength <= state.buffer_cnt); + let send_response = state.cmd_valid && has_enough_data; + // trace_fmt!("send_response: {:#x}", send_response); + let recv_new_cmd = !state.cmd_valid || send_response; + // trace_fmt!("recv_new_cmd: {:#x}", recv_new_cmd); + + let tok = join(); + + // Shift buffer if required + let (new_buffer, new_read_ptr, new_write_ptr) = if shift_buffer_right { + (state.buffer >> DATA_WIDTH, + state.read_ptr - DATA_WIDTH as BufferLength, + state.write_ptr - DATA_WIDTH as BufferLength) + } else { + (state.buffer, + state.read_ptr, + state.write_ptr) + }; + + // if (shift_buffer_right) { + // trace_fmt!("Shifted data"); + // trace_fmt!("new_buffer: {:#x}", new_buffer); + // trace_fmt!("new_read_ptr: {}", new_read_ptr); + // trace_fmt!("new_write_ptr: {}", new_write_ptr); + // } else { () }; + + // Handle incoming writes + let (tok_input, wdata, wdata_valid) = recv_if_non_blocking(tok, inter, recv_new_input, zero!()); + + let (new_buffer, new_write_ptr) = if wdata_valid { + // Shift data if required + let new_data = if shift_data_left { + wdata.data as Buffer << DATA_WIDTH + } else { + wdata.data as Buffer + }; + let new_buffer = new_buffer | new_data; + let new_write_ptr = new_write_ptr + wdata.length as BufferLength; + + (new_buffer, new_write_ptr) + } else { + (new_buffer, new_write_ptr) + }; + + // if (wdata_valid) { + // trace_fmt!("Received aligned data {:#x}", wdata); + // trace_fmt!("new_buffer: {:#x}", new_buffer); + // trace_fmt!("new_write_ptr: {}", new_write_ptr); + // } else { () }; + + // Handle incoming reads + let (tok_ctrl, new_cmd, new_cmd_valid) = + recv_if_non_blocking(tok, ctrl, recv_new_cmd, state.cmd); + + // if (new_cmd_valid) { + // trace_fmt!("Received new cmd: {}", new_cmd); + // } else {()}; + let new_cmd_valid = if recv_new_cmd { new_cmd_valid } else { state.cmd_valid }; + // Handle current read + + let (rdata, new_read_ptr) = if send_response { + let new_read_ptr = new_read_ptr + state.cmd.length as BufferLength; + let rdata = Output { + length: state.cmd.length, + data: math::mask(math::logshiftr(state.buffer, state.read_ptr) as Data, state.cmd.length), + }; + + // trace_fmt!("rdata: {:#x}", rdata); + // trace_fmt!("new_read_ptr: {}", new_read_ptr); + + (rdata, new_read_ptr) + } else { + (zero!(), new_read_ptr) + }; + + let tok = join(tok_input, tok_ctrl); + send_if(tok, output, send_response, rdata); + // if (send_response) { + // trace_fmt!("Sent out rdata: {:#x}", rdata); + // } else {()}; + + let new_buffer_cnt = new_write_ptr - new_read_ptr; + + let new_state = State { + buffer: new_buffer, + buffer_cnt: new_buffer_cnt, + read_ptr: new_read_ptr, + write_ptr: new_write_ptr, + cmd: new_cmd, + cmd_valid: new_cmd_valid, + }; + + new_state + } +} + +const STORAGE_TEST_DATA_WIDTH = u32:64; +const STORAGE_TEST_LENGTH_WIDTH = length_width(STORAGE_TEST_DATA_WIDTH); +const STORAGE_TEST_DATA_WIDTH_X2 = STORAGE_TEST_DATA_WIDTH * u32:2; + +#[test_proc] +proc ShiftBufferStorageTest { + terminator: chan out; + type Ctrl = ShiftBufferCtrl; + type Inter = ShiftBufferPacket; + type Output = ShiftBufferOutput; + type OutputPayload = ShiftBufferPacket; + type OutputStatus = ShiftBufferStatus; + + type Length = uN[STORAGE_TEST_LENGTH_WIDTH]; + type Data = uN[STORAGE_TEST_DATA_WIDTH]; + type DataX2 = uN[STORAGE_TEST_DATA_WIDTH_X2]; + + ctrl_s: chan out; + inter_s: chan out; + output_r: chan in; + + config(terminator: chan out) { + let (ctrl_s, ctrl_r) = chan("ctrl"); + let (inter_s, inter_r) = chan("inter"); + let (output_s, output_r) = chan("output"); + + spawn ShiftBufferStorage(ctrl_r, inter_r, output_s); + + (terminator, ctrl_s, inter_s, output_r) + } + + init { } + + next(state: ()) { + // Single input, single output packet 32bit buffering + let tok = send(join(), inter_s, Inter { data: DataX2: 0xAABB_CCDD, length: Length: 32}); + + // Multiple input packets, single output 32bit buffering + let tok = send(tok, inter_s, Inter { data: DataX2: 0x3344_0000_0000, length: Length: 16}); + let tok = send(tok, inter_s, Inter { data: DataX2: 0x22_0000_0000_0000, length: Length: 8}); + let tok = send(tok, inter_s, Inter { data: DataX2: 0x1100_0000_0000_0000, length: Length: 8}); + + // Small consecutive single input, single output 8bit buffering + let tok = send(tok, inter_s, Inter { data: DataX2: 0x55, length: Length: 8}); + let tok = send(tok, inter_s, Inter { data: DataX2: 0x6600, length: Length: 8}); + + // Multiple input packets, single output 64bit buffering + let tok = send(tok, inter_s, Inter { data: DataX2: 0xDDEE_0000, length: Length: 16}); + let tok = send(tok, inter_s, Inter { data: DataX2: 0xBBCC_0000_0000, length: Length: 16}); + let tok = send(tok, inter_s, Inter { data: DataX2: 0x99AA_0000_0000_0000, length: Length: 16}); + let tok = send(tok, inter_s, Inter { data: DataX2: 0x7788, length: Length: 16}); + + // Single input packet, single output 64bit buffering + let tok = send(tok, inter_s, Inter { data: DataX2: 0x1122_3344_5566_7788_0000, length: Length: 64}); + + // Single 64bit input packet, multiple output packets of different sizes + let tok = send(tok, inter_s, Inter { data: DataX2: 0xEEFF_0011_CCDD_BBAA_0000, length: Length: 64}); + + // Account for leftover 0xEEFF from the previous packet + let tok = send(tok, inter_s, Inter { data: DataX2: 0x1122_0000, length: Length: 16}); + // Should operate on flushed buffer + let tok = send(tok, inter_s, Inter { data: DataX2: 0x3344_0000_0000, length: Length: 16}); + + // Input packets additionally span across 2 shift buffer aligner shift domains + let tok = send(tok, inter_s, Inter { data: DataX2: 0x7788_0000_0000_0000, length: Length: 16}); + let tok = send(tok, inter_s, Inter { data: DataX2: 0x5566, length: Length: 16}); + + // Single input, single output packet 32bit buffering + let tok = send(tok, ctrl_s, Ctrl { length: Length:32}); + let (tok, data) = recv(tok, output_r); + assert_eq(data, Output { data: Data: 0xAABB_CCDD, length: Length: 32}); + + // Multiple input packets, single output 32bit buffering + let tok = send(tok, ctrl_s, Ctrl { length: Length:32}); + let (tok, data) = recv(tok, output_r); + assert_eq(data, Output { data: Data: 0x1122_3344, length: Length: 32}); + + // Small consecutive single input, single output 8bit buffering + let tok = send(tok, ctrl_s, Ctrl { length: Length:8}); + let (tok, data) = recv(tok, output_r); + assert_eq(data, Output { data: Data: 0x55, length: Length: 8}); + let tok = send(tok, ctrl_s, Ctrl { length: Length:8}); + let (tok, data) = recv(tok, output_r); + assert_eq(data, Output { data: Data: 0x66, length: Length: 8}); + + // Multiple input packets, single output 64bit buffering + let tok = send(tok, ctrl_s, Ctrl { length: Length:64}); + let (tok, data) = recv(tok, output_r); + assert_eq(data, Output { data: Data: 0x7788_99AA_BBCC_DDEE, length: Length: 64}); + + // Single input packet, single output 64bit buffering + let tok = send(tok, ctrl_s, Ctrl { length: Length:64}); + let (tok, data) = recv(tok, output_r); + assert_eq(data, Output { data: Data: 0x1122_3344_5566_7788, length: Length: 64}); + + // Single 64bit input packet, multiple output packets of different sizes + let tok = send(tok, ctrl_s, Ctrl { length: Length:8}); + let (tok, data) = recv(tok, output_r); + assert_eq(data, Output { data: Data: 0xAA, length: Length: 8}); + let tok = send(tok, ctrl_s, Ctrl { length: Length:8}); + let (tok, data) = recv(tok, output_r); + assert_eq(data, Output { data: Data: 0xBB, length: Length: 8}); + let tok = send(tok, ctrl_s, Ctrl { length: Length:16}); + let (tok, data) = recv(tok, output_r); + assert_eq(data, Output { data: Data: 0xCCDD, length: Length: 16}); + let tok = send(tok, ctrl_s, Ctrl { length: Length:32}); + let (tok, data) = recv(tok, output_r); + assert_eq(data, Output { data: Data: 0xEEFF_0011, length: Length: 32}); + + let tok = send(tok, ctrl_s, Ctrl { length: Length:16}); + let (tok, data) = recv(tok, output_r); + assert_eq(data, Output { data: Data: 0x1122, length: Length: 16}); + let tok = send(tok, ctrl_s, Ctrl { length: Length:16}); + let (tok, data) = recv(tok, output_r); + assert_eq(data, Output { data: Data: 0x3344, length: Length: 16}); + + let tok = send(tok, ctrl_s, Ctrl { length: Length:32}); + let (tok, data) = recv(tok, output_r); + assert_eq(data, Output { data: Data: 0x5566_7788, length: Length: 32}); + + // Test attempting to read more data than available in the buffer + // This should wait indefinitely, we test this by checking that we can't + // receive data over the next consecutive 100 iterations + let tok = send(tok, ctrl_s, Ctrl { length: Length:64}); + let tok = for (_, tok): (u32, token) in u32:1..u32:100 { + let (tok, _, data_valid) = recv_non_blocking(tok, output_r, zero!()); + assert_eq(data_valid, false); + tok + }(tok); + + // Refill the buffer with more data - not enough to reply to the earlier request for 64b + let tok = send(tok, inter_s, Inter { data: DataX2: 0xDEAD_BEEF_0000, length: Length: 32}); + // Check that we can't receive still + let tok = for (_, tok): (u32, token) in u32:1..u32:100 { + let (tok, _, data_valid) = recv_non_blocking(tok, output_r, zero!()); + assert_eq(data_valid, false); + tok + }(tok); + + // Refill buffer with enough data + let tok = send(tok, inter_s, Inter { data: DataX2: 0xF00B_A4BA_0000_0000_0000, length: Length: 32}); + // Now we should be able to receive a response for 64b request + let (tok, data) = recv(tok, output_r); + assert_eq(data, Output { data: Data: 0xF00BA4BA_DEADBEEF, length: Length: 64}); + + send(tok, terminator, true); + } +} + +pub proc ShiftBuffer { + type Input = ShiftBufferPacket; + type Ctrl = ShiftBufferCtrl; + type Inter = ShiftBufferPacket; + type Output = ShiftBufferOutput; + + config(ctrl: chan> in, input: chan> in, + output: chan> out) { + let (inter_out, inter_in) = + chan, u32:1>("inter"); + spawn ShiftBufferAligner(input, inter_out); + spawn ShiftBufferStorage(ctrl, inter_in, output); + } + + init { } + + next(state: ()) { } +} + +const TEST_DATA_WIDTH = u32:64; +const TEST_LENGTH_WIDTH = std::clog2(TEST_DATA_WIDTH) + u32:1; // TODO: other places in the code use length_width(TEST_DATA_WIDTH) which is clog2(TEST_DATA_WIDTH + 1) instead, why clog2(TEST_DATA_WIDTH) + 1 here? + +#[test_proc] +proc ShiftBufferTest { + type Input = ShiftBufferPacket; + type Ctrl = ShiftBufferCtrl; + type Output = ShiftBufferOutput; + + terminator: chan out; + input_s: chan> out; + ctrl_s: chan> out; + data_r: chan> in; + + config(terminator: chan out) { + let (input_s, input_r) = chan, u32:1>("input"); + let (ctrl_s, ctrl_r) = chan, u32:1>("ctrl"); + let (data_s, data_r) = chan, u32:1>("data"); + + spawn ShiftBuffer(ctrl_r, input_r, data_s); + + (terminator, input_s, ctrl_s, data_r) + } + + init { } + + next(state: ()) { + type Data = bits[TEST_DATA_WIDTH]; + type Length = bits[TEST_LENGTH_WIDTH]; + type Input = ShiftBufferPacket; + type Output = ShiftBufferOutput; + type OutputPayload = ShiftBufferPacket; + type OutputStatus = ShiftBufferStatus; + type Ctrl = ShiftBufferCtrl; + + let tok = send(join(), input_s, Input { data: Data:0xDD_44, length: Length:16 }); + let tok = send(tok, input_s, Input { data: Data:0xAA_11_BB_22_CC_33, length: Length:48 }); + let tok = send(tok, input_s, Input { data: Data:0xEE_55_FF_66_00_77_11_88, length: Length:64 }); + + // Single input, single output packet 32bit buffering + let tok = send(join(), input_s, Input { data: Data: 0xAABB_CCDD, length: Length: 32}); + + // Multiple input packets, single output 32bit buffering + let tok = send(tok, input_s, Input { data: Data: 0x3344, length: Length: 16}); + let tok = send(tok, input_s, Input { data: Data: 0x22, length: Length: 8}); + let tok = send(tok, input_s, Input { data: Data: 0x11, length: Length: 8}); + + // Small consecutive single input, single output 8bit buffering + let tok = send(tok, input_s, Input { data: Data: 0x55, length: Length: 8}); + let tok = send(tok, input_s, Input { data: Data: 0x66, length: Length: 8}); + + // Multiple input packets, single output 64bit buffering + let tok = send(tok, input_s, Input { data: Data: 0xDDEE, length: Length: 16}); + let tok = send(tok, input_s, Input { data: Data: 0xBBCC, length: Length: 16}); + let tok = send(tok, input_s, Input { data: Data: 0x99AA, length: Length: 16}); + let tok = send(tok, input_s, Input { data: Data: 0x7788, length: Length: 16}); + + // Single input packet, single output 64bit buffering + let tok = send(tok, input_s, Input { data: Data: 0x1122_3344_5566_7788, length: Length: 64}); + + // Single 64bit input packet, multiple output packets of different sizes + let tok = send(tok, input_s, Input { data: Data: 0xEEFF_0011_CCDD_BBAA, length: Length: 64}); + + // Account for leftover 0xEEFF from the previous packet + let tok = send(tok, input_s, Input { data: Data: 0x1122, length: Length: 16}); + // Should operate on flushed buffer + let tok = send(tok, input_s, Input { data: Data: 0x3344, length: Length: 16}); + + // Input packets additionally span across 2 shift buffer aligner shift domains + let tok = send(tok, input_s, Input { data: Data: 0x7788, length: Length: 16}); + let tok = send(tok, input_s, Input { data: Data: 0x5566, length: Length: 16}); + + let tok = send(tok, ctrl_s, Ctrl { length: Length:8 }); + let (tok, output) = recv(tok, data_r); + assert_eq(output, Output { data: Data:0x44, length: Length:8 }); + + let tok = send(tok, ctrl_s, Ctrl { length: Length:4 }); + let (tok, output) = recv(tok, data_r); + assert_eq(output, Output { data: Data:0xD, length: Length:4 }); + let tok = send(tok, ctrl_s, Ctrl { length: Length:4 }); + let (tok, output) = recv(tok, data_r); + assert_eq(output, Output { data: Data:0xD, length: Length:4 }); + + let tok = send(tok, ctrl_s, Ctrl { length: Length:48 }); + let (tok, output) = recv(tok, data_r); + assert_eq(output, Output { data: Data:0xAA_11_BB_22_CC_33, length: Length:48 }); + + let tok = send(tok, ctrl_s, Ctrl { length: Length:64 }); + let (tok, output) = recv(tok, data_r); + assert_eq(output, Output { data: Data:0xEE_55_FF_66_00_77_11_88, length: Length:64 }); + + // Single input, single output packet 32bit buffering + let tok = send(tok, ctrl_s, Ctrl { length: Length:32}); + let (tok, data) = recv(tok, data_r); + assert_eq(data, Output { data: Data: 0xAABB_CCDD, length: Length: 32}); + + // Multiple input packets, single output 32bit buffering + let tok = send(tok, ctrl_s, Ctrl { length: Length:32}); + let (tok, data) = recv(tok, data_r); + assert_eq(data, Output { data: Data: 0x1122_3344, length: Length: 32}); + + // Small consecutive single input, single output 8bit buffering + let tok = send(tok, ctrl_s, Ctrl { length: Length:8}); + let (tok, data) = recv(tok, data_r); + assert_eq(data, Output { data: Data: 0x55, length: Length: 8}); + let tok = send(tok, ctrl_s, Ctrl { length: Length:8}); + let (tok, data) = recv(tok, data_r); + assert_eq(data, Output { data: Data: 0x66, length: Length: 8}); + + // Multiple input packets, single output 64bit buffering + let tok = send(tok, ctrl_s, Ctrl { length: Length:64}); + let (tok, data) = recv(tok, data_r); + assert_eq(data, Output { data: Data: 0x7788_99AA_BBCC_DDEE, length: Length: 64}); + + // Single input packet, single output 64bit buffering + let tok = send(tok, ctrl_s, Ctrl { length: Length:64}); + let (tok, data) = recv(tok, data_r); + assert_eq(data, Output { data: Data: 0x1122_3344_5566_7788, length: Length: 64}); + + // Single 64bit input packet, multiple output packets of different sizes + let tok = send(tok, ctrl_s, Ctrl { length: Length:8}); + let (tok, data) = recv(tok, data_r); + assert_eq(data, Output { data: Data: 0xAA, length: Length: 8}); + let tok = send(tok, ctrl_s, Ctrl { length: Length:8}); + let (tok, data) = recv(tok, data_r); + assert_eq(data, Output { data: Data: 0xBB, length: Length: 8}); + let tok = send(tok, ctrl_s, Ctrl { length: Length:16}); + let (tok, data) = recv(tok, data_r); + assert_eq(data, Output { data: Data: 0xCCDD, length: Length: 16}); + let tok = send(tok, ctrl_s, Ctrl { length: Length:32}); + let (tok, data) = recv(tok, data_r); + assert_eq(data, Output { data: Data: 0xEEFF_0011, length: Length: 32}); + + let tok = send(tok, ctrl_s, Ctrl { length: Length:16}); + let (tok, data) = recv(tok, data_r); + assert_eq(data, Output { data: Data: 0x1122, length: Length: 16}); + let tok = send(tok, ctrl_s, Ctrl { length: Length:16}); + let (tok, data) = recv(tok, data_r); + assert_eq(data, Output { data: Data: 0x3344, length: Length: 16}); + + let tok = send(tok, ctrl_s, Ctrl { length: Length:32}); + let (tok, data) = recv(tok, data_r); + assert_eq(data, Output { data: Data: 0x5566_7788, length: Length: 32}); + + // Test attempting to read more data than available in the buffer + // This should wait indefinitely, we test this by checking that we can't + // receive data over the next consecutive 100 iterations + let tok = send(tok, ctrl_s, Ctrl { length: Length:64}); + let tok = for (_, tok): (u32, token) in u32:1..u32:100 { + let (tok, _, data_valid) = recv_non_blocking(tok, data_r, zero!()); + assert_eq(data_valid, false); + tok + }(tok); + + // Refill the buffer with more data - not enough to reply to the earlier request for 64b + let tok = send(tok, input_s, Input { data: Data: 0xDEAD_BEEF, length: Length: 32}); + // Check that we can't receive still + let tok = for (_, tok): (u32, token) in u32:1..u32:100 { + let (tok, _, data_valid) = recv_non_blocking(tok, data_r, zero!()); + assert_eq(data_valid, false); + tok + }(tok); + + // Refill buffer with enough data + let tok = send(tok, input_s, Input { data: Data: 0xF00B_A4BA, length: Length: 32}); + // Now we should be able to receive a response for 64b request + let (tok, data) = recv(tok, data_r); + assert_eq(data, Output { data: Data: 0xF00BA4BA_DEADBEEF, length: Length: 64}); + + send(tok, terminator, true); + } +} diff --git a/xls/modules/shift_buffer/shift_buffer_inst.x b/xls/modules/shift_buffer/shift_buffer_inst.x new file mode 100644 index 0000000000..44c2df8be9 --- /dev/null +++ b/xls/modules/shift_buffer/shift_buffer_inst.x @@ -0,0 +1,71 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import std; +import xls.modules.shift_buffer.shift_buffer; + +const TEST_DATA_WIDTH = u32:64; +const TEST_DATA_WIDTH_X2 = u32:128; +const TEST_LENGTH_WIDTH = std::clog2(TEST_DATA_WIDTH) + u32:1; + +proc ShiftBufferInst { + type Input = shift_buffer::ShiftBufferPacket; + type Ctrl = shift_buffer::ShiftBufferCtrl; + type Output = shift_buffer::ShiftBufferOutput; + input_r: chan> in; + ctrl_r: chan> in; + output_s: chan> out; + + config(input_r: chan> in, + ctrl_r: chan> in, + output_s: chan> out) { + + spawn shift_buffer::ShiftBuffer(ctrl_r, input_r, output_s); + + (input_r, ctrl_r, output_s) + } + + init { } + + next(state: ()) {} +} + +proc ShiftBufferAlignerInst { + type Input = shift_buffer::ShiftBufferPacket; + type Inter = shift_buffer::ShiftBufferPacket; + + config(input: chan in, inter: chan out) { + spawn shift_buffer::ShiftBufferAligner(input, inter); + } + + init { } + + next(state: ()) { } +} + +proc ShiftBufferStorageInst { + type Ctrl = shift_buffer::ShiftBufferCtrl; + type Inter = shift_buffer::ShiftBufferPacket; + type Output = shift_buffer::ShiftBufferOutput; + + config(ctrl: chan in, inter: chan in, output: chan out) { + spawn shift_buffer::ShiftBufferStorage(ctrl, inter, output); + } + + init { } + + next(state: ()) { } +} + + diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 9860a0791f..32e9e05ebf 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -45,19 +45,6 @@ common_codegen_args = { "multi_proc": "true", } -xls_dslx_library( - name = "math_dslx", - srcs = [ - "math.x", - ], -) - -xls_dslx_test( - name = "math_dslx_test", - library = ":math_dslx", - tags = ["manual"], -) - xls_dslx_library( name = "buffer_dslx", srcs = [ @@ -93,6 +80,7 @@ window_buffer_codegen_args = common_codegen_args | { "module_name": "WindowBuffer64", "clock_period_ps": "0", "pipeline_stages": "1", + "worst_case_throughput": "2", } xls_dslx_verilog( @@ -863,78 +851,6 @@ place_and_route( target_die_utilization_percentage = "10", ) -xls_dslx_library( - name = "shift_buffer_dslx", - srcs = ["shift_buffer.x"], - deps = [ - ":buffer_dslx", - ], -) - -xls_dslx_test( - name = "shift_buffer_dslx_test", - library = ":shift_buffer_dslx", -) - -xls_dslx_verilog( - name = "shift_buffer_verilog", - codegen_args = { - "module_name": "ShiftBuffer", - "delay_model": "asap7", - "pipeline_stages": "2", - "reset": "rst", - "use_system_verilog": "false", - }, - dslx_top = "ShiftBufferInst", - library = ":shift_buffer_dslx", - opt_ir_args = { - "inline_procs": "true", - "top": "__shift_buffer__ShiftBufferInst__ShiftBuffer_0__128_64_7_next", - }, - verilog_file = "shift_buffer.v", -) - -xls_benchmark_ir( - name = "shift_buffer_opt_ir_benchmark", - src = ":shift_buffer_verilog.opt.ir", - benchmark_ir_args = { - "pipeline_stages": "2", - "delay_model": "asap7", - }, -) - -verilog_library( - name = "shift_buffer_verilog_lib", - srcs = [ - ":shift_buffer.v", - ], -) - -synthesize_rtl( - name = "shift_buffer_synth_asap7", - standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", - top_module = "ShiftBuffer", - deps = [ - ":shift_buffer_verilog_lib", - ], -) - -benchmark_synth( - name = "shift_buffer_benchmark_synth", - synth_target = ":shift_buffer_synth_asap7", -) - -place_and_route( - name = "shift_buffer_place_and_route", - clock_period = CLOCK_PERIOD_PS, - core_padding_microns = 2, - min_pin_distance = "0.5", - placement_density = "0.30", - stop_after_step = "global_routing", - synthesized_rtl = ":shift_buffer_synth_asap7", - target_die_utilization_percentage = "10", -) - xls_dslx_library( name = "ram_wr_handler_dslx", srcs = ["ram_wr_handler.x"], @@ -1191,6 +1107,87 @@ xls_benchmark_ir( # library = ":comp_block_dec_dslx", #) +xls_dslx_library( + name = "refilling_shift_buffer_dslx", + srcs = ["refilling_shift_buffer.x"], + deps = [ + "//xls/modules/shift_buffer:shift_buffer_dslx", + "//xls/modules/zstd/memory:mem_reader_dslx", + ], +) + +xls_dslx_test( + name = "refilling_shift_buffer_dslx_test", + dslx_test_args = {"compare": "jit"}, + library = ":refilling_shift_buffer_dslx", +) + +xls_dslx_verilog( + name = "refilling_shift_buffer_internal_verilog", + codegen_args = common_codegen_args | { + "module_name": "RefillingShiftBufferInternalInst" + }, + dslx_top = "RefillingShiftBufferInternalInst", + library = ":refilling_shift_buffer_dslx", + tags = ["manual"], + verilog_file = "refilling_shift_buffer_internal.v", +) + +xls_benchmark_ir( + name = "refilling_shift_buffer_internal_opt_ir_benchmark", + src = ":refilling_shift_buffer_internal_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "10", + "inline_procs": "false", + }, + tags = ["manual"], +) + +verilog_library( + name = "refilling_shift_buffer_internal_verilog_lib", + srcs = [ + ":refilling_shift_buffer_internal.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "refilling_shift_buffer_internal_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "RefillingShiftBufferInternalInst", + deps = [ + ":refilling_shift_buffer_internal_verilog_lib", + ], +) + +benchmark_synth( + name = "refilling_shift_buffer_internal_benchmark_synth", + synth_target = ":refilling_shift_buffer_internal_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "refilling_shift_buffer_internal_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.4", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":refilling_shift_buffer_internal_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + +#xls_dslx_verilog( +# name = "refilling_shift_buffer_verilog", +# codegen_args = common_codegen_args, +# dslx_top = "RefillingShiftBufferInst", +# library = ":refilling_shift_buffer_dslx", +# tags = ["manual"], +# verilog_file = "refilling_shift_buffer.v", +#) + zstd_dec_deps = [ ":axi_csr_accessor_dslx", ":block_header_dec_dslx", diff --git a/xls/modules/zstd/refilling_shift_buffer.x b/xls/modules/zstd/refilling_shift_buffer.x new file mode 100644 index 0000000000..3be573f8ef --- /dev/null +++ b/xls/modules/zstd/refilling_shift_buffer.x @@ -0,0 +1,889 @@ +// 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 proc responsible for automatically refilling ShiftBuffer +// with data from memory from some starting address, consecutive data from +// increasing addresses + + +import std; +import xls.modules.shift_buffer.shift_buffer; +import xls.modules.zstd.memory.mem_reader; + + +pub type RefillingShiftBufferInput = shift_buffer::ShiftBufferPacket; +pub type RefillingShiftBufferCtrl = shift_buffer::ShiftBufferCtrl; + +pub struct RefillStart { + start_addr: uN[ADDR_W] +} + +pub struct RefillingShiftBufferOutput { + data: uN[DATA_WIDTH], + length: uN[LENGTH_WIDTH], + error: bool, +} + +enum RefillerFsm: u2 { + IDLE = 0, + REFILLING = 1, + FLUSHING = 2, +} + +struct RefillerState { + curr_addr: uN[ADDR_W], // next memory address to request data from + fsm: RefillerFsm, // FSM state + future_buf_occupancy: uN[BUFFER_W_CLOG2], // amount of bits that are currently in the ShiftBuffer + + // amount of bits that will enter ShiftBuffer once all + // pending memory requests are served + axi_error: bool, // whether or not at least one memory read resulted in AXI error - + // - this bit is sticky and can be cleared only by flushing + bits_to_axi_error: uN[BUFFER_W_CLOG2], // amount of bits that we need to consume from the + // ShiftBuffer to trigger an AXI error + bits_to_flush: uN[BUFFER_W_CLOG2], // amount of bits left to flush during flushing state +} + +pub fn length_width(data_width: u32) -> u32 { + shift_buffer::length_width(data_width) +} + +// works only on values with bit length divisible by 8 +fn reverse_byte_order(data: uN[N_BITS]) -> uN[N_BITS] { + const_assert!(std::ceil_div(N_BITS, u32:8) == N_BITS / u32:8); + unroll_for! (i, acc): (u32, uN[N_BITS]) in range(u32:0, N_BYTES) { + let offset = i * u32:8; + let offset_rev = (N_BYTES - i - u32:1) * u32:8; + acc | (rev(data[offset +: u8]) as uN[N_BITS] << offset_rev) + }(uN[N_BITS]:0) +} + +#[test] +fn test_reverse_byte_order() { + assert_eq(reverse_byte_order(u64:0b00000001_00100011_01000101_01100111_10001001_10101011_11001101_11101111), u64:0b11110111_10110011_11010101_10010001_11100110_10100010_11000100_10000000); + assert_eq(reverse_byte_order(u32:0b10001001_10101011_11001101_11101111), u32:0b11110111_10110011_11010101_10010001); + assert_eq(reverse_byte_order(u16:0b11001101_11101111), u16:0b11110111_10110011); +} + +proc RefillingShiftBufferInternal< + DATA_W: u32, ADDR_W: u32, BACKWARDS: bool = {false}, INSTANCE: u32 = {u32:0}, + LENGTH_W: u32 = {length_width(DATA_W)}, + DATA_W_DIV8: u32 = {DATA_W / u32:8}, + BUFFER_W: u32 = {DATA_W * u32:2}, // TODO: fix implementation detail of ShiftBuffer leaking here + BUFFER_W_CLOG2: u32 = {std::clog2(BUFFER_W) + u32:1}, +>{ + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + type MemReaderStatus = mem_reader::MemReaderStatus; + type StartReq = RefillStart; + type RSBInput = RefillingShiftBufferInput; + type RSBOutput = RefillingShiftBufferOutput; + type RSBCtrl = RefillingShiftBufferCtrl; + type SBOutput = shift_buffer::ShiftBufferOutput; + type State = RefillerState; + type Fsm = RefillerFsm; + type BufferSize = uN[BUFFER_W_CLOG2]; + + reader_req_s: chan out; + reader_resp_r: chan in; + start_req_r: chan in; + stop_flush_req_r: chan<()> in; + buffer_data_in_s: chan out; + buffer_data_out_s: chan out; + buffer_ctrl_r: chan in; + snoop_data_out_r: chan in; + snoop_ctrl_s: chan out; + flushing_done_s: chan<()> out; + + config( + reader_req_s: chan out, + reader_resp_r: chan in, + start_req_r: chan in, + stop_flush_req_r: chan<()> in, + buffer_ctrl_r: chan in, + buffer_data_out_s: chan out, + snoop_ctrl_s: chan out, + buffer_data_in_s: chan out, + snoop_data_out_r: chan in, + flushing_done_s: chan<()> out, + ) { + (reader_req_s, reader_resp_r, start_req_r, stop_flush_req_r, + buffer_data_in_s, buffer_data_out_s, buffer_ctrl_r, snoop_data_out_r, + snoop_ctrl_s, flushing_done_s) + } + + init { + zero!() + } + + next(state: State) { + let tok = join(); + + // trace_fmt!("Current refiller state: {:#x}", state); + + // receive start and stop&flush requests + let (_, start_req, start_valid) = recv_if_non_blocking(tok, start_req_r, state.fsm == Fsm::IDLE, zero!()); + let (_, (), stop_flush_valid) = recv_if_non_blocking(tok, stop_flush_req_r, state.fsm == Fsm::REFILLING, ()); + + // flush logic + let flushing_end = state.future_buf_occupancy == BufferSize:0; + let flushing = state.fsm == Fsm::FLUSHING; + // flush at most DATA_W bits in a given next() evaluation + let flush_amount_bits = std::min(DATA_W as BufferSize, state.bits_to_flush); + // send "flushing done" notification once we complete it + send_if(tok, flushing_done_s, flushing && flushing_end, ()); + // if (flushing && flushing_end) { + // trace_fmt!("Sent done on the flushing done channel"); + // } else {}; + + // snooping logic for the ShiftBuffer control channel + // recv and immediately send out control packets heading for ShiftBuffer, + // unless we're flushing, if so we block receiving any new control packets + let (_, snoop_ctrl, snoop_ctrl_valid) = recv_if_non_blocking(tok, buffer_ctrl_r, !flushing, zero!()); + // If we're flushing send our packets for taking out data from the shiftbuffer + // (that data will then be discarded) + let ctrl_packet = if (flushing) { + RSBCtrl {length: flush_amount_bits as uN[LENGTH_W]} + } else if (snoop_ctrl_valid) { + snoop_ctrl + } else { + zero!() + }; + let do_send_ctrl = (flushing && flush_amount_bits > BufferSize:0) || snoop_ctrl_valid; + send_if(tok, snoop_ctrl_s, do_send_ctrl, ctrl_packet); + // if do_send_ctrl { + // trace_fmt!("Sent snooped/injected control packet: {:#x}", ctrl_packet); + // } else {}; + + // snoop data output packet (for keeping track how many bits in ShiftBuffer are occupied) + let (_, snoop_data, snoop_data_valid) = recv_non_blocking(tok, snoop_data_out_r, zero!()); + + // refilling logic + const REFILL_SIZE = DATA_W_DIV8 as uN[ADDR_W]; + // we eagerly request data based on the *future* capacity of the buffer, + // this might stall us (and in turn MemReader and potentially the whole bus) + // on send to buffer_data_in_s if the proc sending control requests isn't + // receiving the data on the output channel fast enough, but this is true + // of any proc that uses MemReader and we don't consider this an issue + let buf_will_have_enough_space = state.future_buf_occupancy <= DATA_W as BufferSize; // TODO: fix implementation detail of ShiftBuffer leaking here + let do_refill_cycle = state.fsm == Fsm::REFILLING && buf_will_have_enough_space; + // send request to memory for more data under the assumption + // that there's enough space in the ShiftBuffer to fit it + let mem_req = MemReaderReq { + addr: state.curr_addr, + length: REFILL_SIZE, + }; + send_if(tok, reader_req_s, do_refill_cycle, mem_req); + // if (do_refill_cycle) { + // trace_fmt!("[{:#x}] Sent request for data to memory: {:#x}", INSTANCE, mem_req); + // } else {}; + // receive data from memory + let (_, reader_resp, reader_resp_valid) = recv_non_blocking(tok, reader_resp_r, zero!()); + // if reader_resp_valid { + // trace_fmt!("[{:#x}] Received data from memory: {:#x}", INSTANCE, reader_resp); + // } else {}; + // always send some data regardless of the reader_resp.status to allow for all requests + // to complete (possibly with invalid data) since the response channel queue must be empty for + // flushing to work correctly + let do_buffer_refill = reader_resp_valid; + let reader_resp_len_bits = DATA_W as uN[LENGTH_W]; + let data_packet = RSBInput { + data: if BACKWARDS { reverse_byte_order(reader_resp.data) } else { reader_resp.data }, + length: reader_resp_len_bits, + }; + // this send might stall only if proc that receives responses isn't reading from the + // ShiftBuffer fast enough, apart from that since part of the condition `do_buffer_refill` + // is `buf_will_have_enough_space` it should not block + send_if(tok, buffer_data_in_s, do_buffer_refill, data_packet); + // if (do_buffer_refill) { + // trace_fmt!("Sent data to the ShiftBuffer: {:#x}", data_packet); + // } else {}; + + // length of additional data that will be inserted into the ShiftBuffer *in the future* + // once all pending memory requests are served + let future_input_bits = if (do_refill_cycle) { + DATA_W as BufferSize + } else { + BufferSize:0 + }; + // actual amount of bits inserted into the ShiftBuffer in this next() evaluation + let input_bits = if (do_buffer_refill) { + DATA_W as BufferSize + } else { + BufferSize:0 + }; + // length of data that was snooped on the ShiftBuffer output + // note: default value of snoop_ctrl.length from its recv_if_non_blocking is 0 + let output_bits = snoop_data.length as BufferSize; + // calculate the difference in the amount of bits inserted/taken out + // this will never underflow as it's always true that output_bits <= state.future_buf_occupancy + // (because output_bits is based on the number of outgoing bits from the buffer which cannot be + // larger than its current occupancy) + let next_future_buf_occupancy = state.future_buf_occupancy + future_input_bits - output_bits; + + // keep track of the amount of remaining bits to flush + let next_bits_to_flush = if (flushing) { + state.bits_to_flush - flush_amount_bits + } else { + next_future_buf_occupancy + }; + + // error handling + // we've encountered an error, either previously or in this next() evaluation + let axi_error = state.axi_error || (reader_resp_valid && reader_resp.status == MemReaderStatus::ERROR); + let next_bits_to_axi_error = if (axi_error) { + if (state.bits_to_axi_error < snoop_data.length as BufferSize) { + // prevent underflow + BufferSize:0 + } else { + // keep track of amount of bits to reach offending data (from ERROR memory response) + state.bits_to_axi_error - (snoop_data.length as BufferSize) + } + } else if (flushing_end) { + // reset the counter after a flush since its state will be invalid after that + BufferSize:0 + } else { + // keep track of current amount of bits in the buffer + state.bits_to_axi_error + input_bits - output_bits + }; + // check if we will consume at least one bit from the data that returned AXI error + let reads_error_bits = snoop_data_valid && state.bits_to_axi_error < snoop_data.length as BufferSize; + + // data snoop forwarding logic + // forward data heading for the ShiftBuffer output, attaching an error bit + // if we've encountered an AXI error, unless we're flushing - in that case discard snoop_data + let forward_snooped_data = snoop_data_valid && !flushing; + send_if(tok, buffer_data_out_s, forward_snooped_data, RSBOutput { + data: if BACKWARDS { + rev(snoop_data.data) >> (u32:64 - snoop_data.length as u32) + } else { + snoop_data.data + }, + length: snoop_data.length, + error: axi_error && reads_error_bits, + }); + // if forward_snooped_data { + // trace_fmt!("[{:#x}] Forwarded snooped data output packet: {:#x}", INSTANCE, snoop_data); + // } else {}; + + // FSM + let next_state = match (state.fsm) { + Fsm::IDLE => { + if (start_valid) { + State { + fsm: Fsm::REFILLING, + curr_addr: if BACKWARDS { + start_req.start_addr - DATA_W_DIV8 as uN[ADDR_W] + } else { + start_req.start_addr + }, + ..state + } + } else { + state + } + }, + Fsm::REFILLING => { + // stop and AXI error might happen on the same cycle, + // in that case stop&flush takes precedence over error + if (stop_flush_valid) { + State { + fsm: Fsm::FLUSHING, + ..state + } + } else if (do_refill_cycle) { + State { + curr_addr: if BACKWARDS { + state.curr_addr - REFILL_SIZE + } else { + state.curr_addr + REFILL_SIZE + }, + ..state + } + } else { + state + } + }, + Fsm::FLUSHING => { + if (flushing_end) { + State { + fsm: Fsm::IDLE, + ..state + } + } else { + state + } + }, + _ => fail!("refilling_shift_buffer_fsm_unreachable", zero!()) + }; + + let next_axi_error = axi_error && next_state.fsm == Fsm::REFILLING; + + // combine next FSM state with buffer occupancy data + let next_state = State { + future_buf_occupancy: next_future_buf_occupancy, + bits_to_axi_error: next_bits_to_axi_error, + bits_to_flush: next_bits_to_flush, + axi_error: next_axi_error, + ..next_state + }; + + // check some invariants + // asserts are equivalent to implications in a preceding comment + // state.fsm == Fsm::IDLE -> next_future_buf_occupancy == 0 + assert!(!(state.fsm == Fsm::IDLE) || state.future_buf_occupancy == BufferSize:0, "future_buf_occupancy was not 0 in IDLE state"); + // state.fsm == Fsm::IDLE -> state.bits_to_axi_error == BufferSize:0 + assert!(!(state.fsm == Fsm::IDLE) || state.bits_to_axi_error == BufferSize:0, "bits_to_axi_error was not 0 in IDLE state"); + // state.fsm == Fsm::IDLE -> state.bits_to_flush == BufferSize:0 + assert!(!(state.fsm == Fsm::IDLE) || state.bits_to_flush == BufferSize:0, "bits_to_flush was not 0 in IDLE state"); + + // state.fsm == Fsm::REFILLING -> state.future_buf_occupancy >= state.bits_to_axi_error + assert!(!(state.fsm == Fsm::REFILLING) || state.future_buf_occupancy >= state.bits_to_axi_error, "future_buf_occupancy >= bits_to_axi_error in REFILLING state"); + // state.fsm == Fsm::REFILLING -> state.future_buf_occupancy >= state.bits_to_flush + assert!(!(state.fsm == Fsm::REFILLING) || state.future_buf_occupancy >= state.bits_to_flush, "future_buf_occupancy >= bits_to_flush in REFILLING state"); + // state.fsm == Fsm::REFILLING -> state.bits_to_flush >= state.bits_to_axi_error + assert!(!(state.fsm == Fsm::REFILLING) || state.bits_to_flush >= state.bits_to_axi_error, "bits_to_flush >= bits_to_axi_error in REFILLING state"); + + // state.fsm != Fsm::REFILLING -> state.axi_error == false + assert!(!(state.fsm != Fsm::REFILLING) || state.axi_error == false, "axi_error was true in a state other than REFILLING"); + // axi_error -> state.bits_to_axi_error >= next_bits_to_axi_error + assert!(!axi_error || state.bits_to_axi_error >= next_bits_to_axi_error, "state.bits_to_axi_error increased during axi_error"); + // flushing -> state.bits_to_flush >= next_bits_to_flush + assert!(!flushing || state.bits_to_flush >= next_bits_to_flush, "state.bits_to_flush increased during flushing"); + + next_state + } +} + +// Main proc for RefillingShiftBuffer +// +// Typical usage pattern is as follows: +// 1. Send start request with starting address where the refilling is supposed +// to start from on start_req channel +// 2. Send requests for up to DATA_W bits on buffer_ctrl channel +// 3. Receive responses on buffer_data_out channel +// 4. Once you're done, send a request on stop_flush_req channel +// and wait for confirmation on flushing_done channel +// +// In case of an AXI error on the bus an error bit is set in response +// on buffer_data_out channel. You may still send requests on buffer_ctrl +// and receive responses on buffer_data_out but the data is not guaranteed +// to be correct and said error bit will always be set from that point +// onwards until you trigger a flush +// +// To send a request on stop_flush_req channel, you must first receive all +// responses from the buffer_data_out channel that you sent requests for on +// buffer_ctrl channel + +pub proc RefillingShiftBuffer< + DATA_W: u32, + ADDR_W: u32, + BACKWARDS: bool = {false}, + INSTANCE: u32 = {u32:0}, + LENGTH_W: u32 = {length_width(DATA_W)}, +> { + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + type StartReq = RefillStart; + type RSBInput = RefillingShiftBufferInput; + type RSBOutput = RefillingShiftBufferOutput; + type RSBCtrl = RefillingShiftBufferCtrl; + type SBOutput = shift_buffer::ShiftBufferOutput; + + config( + reader_req_s: chan out, + reader_resp_r: chan in, + start_req_r: chan in, + stop_flush_req_r: chan<()> in, + buffer_ctrl_r: chan in, + buffer_data_out_s: chan out, + flushing_done_s: chan<()> out, + ) { + const CHANNEL_DEPTH = u32:1; + + let (buffer_data_in_s, buffer_data_in_r) = chan("buffer_data_in"); + let (snoop_data_out_s, snoop_data_out_r) = chan("snoop_data_out_s"); + let (snoop_ctrl_s, snoop_ctrl_r) = chan("snoop_ctrl"); + + spawn shift_buffer::ShiftBuffer( + snoop_ctrl_r, buffer_data_in_r, snoop_data_out_s + ); + spawn RefillingShiftBufferInternal( + reader_req_s, + reader_resp_r, + start_req_r, + stop_flush_req_r, + buffer_ctrl_r, + buffer_data_out_s, + snoop_ctrl_s, + buffer_data_in_s, + snoop_data_out_r, + flushing_done_s, + ); + } + + init {} + + next(_: ()) {} +} + + +const TEST_DATA_W = u32:64; +const TEST_ADDR_W = u32:32; +const TEST_LENGTH_W = length_width(TEST_DATA_W); +const TEST_DATA_W_DIV8 = TEST_DATA_W / u32:8; +const TEST_BUFFER_W = TEST_DATA_W * u32:2; // TODO: fix implementation detail of ShiftBuffer leaking here +const TEST_BUFFER_W_CLOG2 = std::clog2(TEST_BUFFER_W); + +proc RefillingShiftBufferTest { + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + type MemReaderStatus = mem_reader::MemReaderStatus; + type StartReq = RefillStart; + type RSBInput = RefillingShiftBufferInput; + type RSBOutput = RefillingShiftBufferOutput; + type RSBCtrl = RefillingShiftBufferCtrl; + type State = RefillerState; + + terminator: chan out; + reader_req_r: chan in; + reader_resp_s: chan out; + start_req_s: chan out; + stop_flush_req_s: chan<()> out; + buffer_ctrl_s: chan out; + buffer_data_out_r: chan in; + flushing_done_r: chan<()> in; + + config(terminator: chan out) { + let (reader_req_s, reader_req_r) = chan("reader_req"); + let (reader_resp_s, reader_resp_r) = chan("reader_resp"); + let (start_req_s, start_req_r) = chan("start_req"); + let (stop_flush_req_s, stop_flush_req_r) = chan<()>("stop_flush_req"); + let (buffer_ctrl_s, buffer_ctrl_r) = chan("buffer_ctrl"); + let (buffer_data_out_s, buffer_data_out_r) = chan("buffer_data_out"); + let (flushing_done_s, flushing_done_r) = chan<()>("flushing_done"); + + spawn RefillingShiftBuffer( + reader_req_s, reader_resp_r, start_req_r, stop_flush_req_r, + buffer_ctrl_r, buffer_data_out_s, flushing_done_s, + ); + + ( + terminator, reader_req_r, reader_resp_s, start_req_s, + stop_flush_req_s, buffer_ctrl_s, buffer_data_out_r, + flushing_done_r, + ) + } + + init { } + + next(state: ()) { + type Addr = uN[TEST_ADDR_W]; + type Data = uN[TEST_DATA_W]; + type Length = uN[TEST_LENGTH_W]; + + let tok = join(); + + const REFILL_SIZE = TEST_DATA_W_DIV8 as Addr; + let tok = send(tok, start_req_s, StartReq { start_addr: Addr:0xDEAD_0008 }); + + // proc should ask for data 2 times (2/3 of the size of the internal ShiftBuffer) + let (tok, req) = recv(tok, reader_req_r); + assert_eq(req, MemReaderReq { + addr: if BACKWARDS { Addr: 0xDEAD_0000 } else { Addr:0xDEAD_0008 }, + length: REFILL_SIZE, + }); + let tok = send(tok, reader_resp_s, MemReaderResp { + status: MemReaderStatus::OKAY, + data: Data:0x01234567_89ABCDEF, + length: REFILL_SIZE, + last: true, + }); + let (tok, req) = recv(tok, reader_req_r); + assert_eq(req, MemReaderReq { + addr: if BACKWARDS { Addr: 0xDEAC_FFF8 } else { Addr:0xDEAD_0010 }, + length: REFILL_SIZE, + }); + let tok = send(tok, reader_resp_s, MemReaderResp { + status: MemReaderStatus::OKAY, + data: Data:0xFEDCBA98_76543210, + length: REFILL_SIZE, + last: true, + }); + + // read single byte + let tok = send(tok, buffer_ctrl_s, RSBCtrl { + length: Length:8 + }); + let (tok, resp) = recv(tok, buffer_data_out_r); + assert_eq(resp, RSBOutput { + data: if BACKWARDS { Data:0x01 } else { Data:0xEF }, + length: Length:8, + error: false, + }); + + // proc shouldn't be asking for any more data at this point + let tok = for (_, tok): (u32, token) in u32:1..u32:100 { + let (tok, _, data_valid) = recv_non_blocking(tok, reader_req_r, zero!()); + assert_eq(data_valid, false); + tok + }(tok); + + // read enough data from the buffer to trigger a refill + let tok = send(tok, buffer_ctrl_s, RSBCtrl { + length: Length:56 + }); + let (tok, resp) = recv(tok, buffer_data_out_r); + assert_eq(resp, RSBOutput { + data: if BACKWARDS { Data:0x23456789ABCDEF } else { Data:0x01234567_89ABCD }, + length: Length:56, + error: false, + }); + let (tok, req) = recv(tok, reader_req_r); + assert_eq(req, MemReaderReq { + addr: if BACKWARDS { Addr: 0xDEAC_FFF0 } else { Addr:0xDEAD_0018 } , + length: REFILL_SIZE, + }); + // don't respond to the request yet + + // we have 64 bits in the buffer at this point - almost empty it manually + let tok = send(tok, buffer_ctrl_s, RSBCtrl { + length: Length:60 + }); + let (tok, resp) = recv(tok, buffer_data_out_r); + assert_eq(resp, RSBOutput { + data: if BACKWARDS { Data:0x0FEDCBA9_87654321 } else { Data:0xEDCBA98_76543210 }, + length: Length:60, + error: false, + }); + + // ask for more data from the buffer (but not enough data is available) + let tok = send(tok, buffer_ctrl_s, RSBCtrl { + length: Length:12 + }); + // make sure that reading from output is stuck + let tok = for (_, tok): (u32, token) in u32:1..u32:100 { + let (tok, _, data_valid) = recv_non_blocking(tok, buffer_data_out_r, zero!()); + assert_eq(data_valid, false); + tok + }(tok); + + // serve earlier memory request + let tok = send(tok, reader_resp_s, MemReaderResp { + status: MemReaderStatus::OKAY, + data: Data:0x02481357_8ACE9BD0, + length: REFILL_SIZE, + last: true, + }); + // should be able to receive from the buffer now + let (tok, resp) = recv(tok, buffer_data_out_r); + assert_eq(resp, RSBOutput { + data: if BACKWARDS { Data:0x02 } else { Data:0xD0F }, + length: Length:12, + error: false, + }); + + // buffer now contains 56 bits - proc should have sent 1 more + // memory requests by this point - serve it + let (tok, req) = recv(tok, reader_req_r); + assert_eq(req, MemReaderReq { + addr: if BACKWARDS { Addr:0xDEAC_FFE8 } else { Addr:0xDEAD_0020 }, + length: REFILL_SIZE, + }); + let tok = send(tok, reader_resp_s, MemReaderResp { + status: MemReaderStatus::OKAY, + data: Data:0x86868686_42424242, + length: REFILL_SIZE, + last: true, + }); + + // make sure proc is not requesting more data that we can insert into the buffer + let tok = for (_, tok): (u32, token) in u32:1..u32:100 { + let (tok, _, req_valid) = recv_non_blocking(tok, reader_req_r, zero!()); + assert_eq(req_valid, false); + tok + }(tok); + + // try flushing + let tok = send(tok, stop_flush_req_s, ()); + let (tok, ()) = recv(tok, flushing_done_r); + + // start from a new address and refill buffer with more data + let tok = send(tok, start_req_s, StartReq { start_addr: u32: 0x1000_11F0 }); + let (tok, req) = recv(tok, reader_req_r); + assert_eq(req, MemReaderReq { + addr: if BACKWARDS { Addr: 0x1000_11E8 } else { Addr: 0x1000_11F0 } , + length: REFILL_SIZE, + }); + let tok = send(tok, reader_resp_s, MemReaderResp { + status: MemReaderStatus::OKAY, + data: Data:0xFEFDFCFB_FAF9F8F7, + length: REFILL_SIZE, + last: true, + }); + + // try reading data from the buffer after the flush + let tok = send(tok, buffer_ctrl_s, RSBCtrl { + length: Length:4 + }); + let (tok, resp) = recv(tok, buffer_data_out_r); + assert_eq(resp, RSBOutput { + data: if BACKWARDS { Data: 0xF } else { Data:0x7 }, + length: Length:4, + error: false, + }); + + // refill with even more data + let (tok, req) = recv(tok, reader_req_r); + assert_eq(req, MemReaderReq { + addr: if BACKWARDS { Addr: 0x1000_11E0 } else { Addr:0x1000_11F8 }, + length: REFILL_SIZE, + }); + let tok = send(tok, reader_resp_s, MemReaderResp { + status: MemReaderStatus::OKAY, + data: Data:0xABBA_BAAB_AABB_BBAA, + length: REFILL_SIZE, + last: true, + }); + + // test receiving more than DATA_W bits + let tok = send(tok, buffer_ctrl_s, RSBCtrl { + length: Length:64 + }); + let tok = send(tok, buffer_ctrl_s, RSBCtrl { + length: Length:60 + }); + + // receive all of the new data and verify that no old data + // remained in the buffer + let (tok, resp) = recv(tok, buffer_data_out_r); + assert_eq(resp, RSBOutput { + data: if BACKWARDS { Data:0xEFDFCFBFAF9F8F7A } else { Data:0xAFEFDFCF_BFAF9F8F }, + length: Length:64, + error: false, + }); + let (tok, resp) = recv(tok, buffer_data_out_r); + assert_eq(resp, RSBOutput { + data: if BACKWARDS { Data:0xBBABAABAABBBBAA } else { Data:0xABBA_BAAB_AABB_BBA }, + length: Length:60, + error: false, + }); + + // proc should've requested more data by now + // respond with AXI error from MemReader + let (tok, req) = recv(tok, reader_req_r); + assert_eq(req, MemReaderReq { + addr: if BACKWARDS { Addr:0x1000_11D8 } else { Addr:0x1000_1200 }, + length: REFILL_SIZE, + }); + let tok = send(tok, reader_resp_s, MemReaderResp { + status: MemReaderStatus::ERROR, + data: Data:0x0, + length: Addr:0x0, + last: true, + }); + + // try reading from the buffer that's tainted by + // AXI error - should induce a packet on the error channel + let tok = send(tok, buffer_ctrl_s, RSBCtrl { + length: Length:1 + }); + + // to comply with the usage protocol of refiller we need to recv response + let (tok, resp) = recv(tok, buffer_data_out_r); + // don't assume anything about the response except that the lenght must be 1 and error true + assert_eq(resp.length, Length:1); + assert_eq(resp.error, true); + + // send some more data, can be OK status this time + let (tok, req) = recv(tok, reader_req_r); + assert_eq(req, MemReaderReq { + addr: if BACKWARDS { Addr:0x1000_11D0 } else { Addr:0x1000_1208 }, + length: REFILL_SIZE, + }); + let tok = send(tok, reader_resp_s, MemReaderResp { + status: MemReaderStatus::OKAY, + data: Data:0xDEADBEEF_FEEBDAED, + length: Addr:0x40, + last: true, + }); + + // check that we get another error after trying to read from the buffer once more + let tok = send(tok, buffer_ctrl_s, RSBCtrl { + length: Length:64 + }); + let (tok, resp) = recv(tok, buffer_data_out_r); + // again don't assume anything about the response other data length and error + assert_eq(resp.length, Length:64); + assert_eq(resp.error, true); + + // to comply with the usage protocol of refiller we must flush it after + // receiving the error to permit further operation in non-error state + let tok = send(tok, stop_flush_req_s, ()); + + // test that flushing works even if response from memory arrives after + // flushing is requested + let (tok, req) = recv(tok, reader_req_r); + assert_eq(req, MemReaderReq { + addr: if BACKWARDS { Addr:0x1000_11C8 } else { Addr:0x1000_1210 }, + length: REFILL_SIZE, + }); + let tok = send(tok, reader_resp_s, MemReaderResp { + status: MemReaderStatus::OKAY, + data: Data:0xFFFF_EEEE_DDDD_CCCC, + length: Addr:0x40, + last: true, + }); + + let (tok, ()) = recv(tok, flushing_done_r); + + // test that we can restart refilling after flushing from an error state + let tok = send(tok, start_req_s, StartReq { + start_addr: Addr:0xABCD_0000 + }); + + // respond to memory request + let (tok, req) = recv(tok, reader_req_r); + assert_eq(req, MemReaderReq { + addr: if BACKWARDS { Addr:0xABCC_FFF8 } else { Addr:0xABCD_0000 }, + length: REFILL_SIZE, + }); + let tok = send(tok, reader_resp_s, MemReaderResp { + status: MemReaderStatus::OKAY, + data: Data:0x0123_4567_89AB_CDEF, + length: Addr:0x40, + last: true, + }); + + // ask for some data + let tok = send(tok, buffer_ctrl_s, RSBCtrl { + length: Length:8 + }); + let (tok, resp) = recv(tok, buffer_data_out_r); + assert_eq(resp, RSBOutput { + data: if BACKWARDS { Data:0x01 } else { Data:0xEF }, + length: Length:8, + error: false, + }); + + // respond to second memory request + let (tok, req) = recv(tok, reader_req_r); + assert_eq(req, MemReaderReq { + addr: if BACKWARDS { Addr: 0xABCC_FFF0 } else { Addr:0xABCD_0008 }, + length: REFILL_SIZE, + }); + // taint this response + let tok = send(tok, reader_resp_s, MemReaderResp { + status: MemReaderStatus::ERROR, + data: Data:0x8888_7777_6666_5555, + length: Addr:0x40, + last: true, + }); + + // ask for data that won't trigger an error + let tok = send(tok, buffer_ctrl_s, RSBCtrl { + length: Length:48 + }); + let (tok, resp) = recv(tok, buffer_data_out_r); + assert_eq(resp, RSBOutput { + data: Data:0x23456789ABCD, + length: Length:48, + error: false, + }); + + // now ask for data that *will* trigger an error + // we have 72 bits in the buffer, 8 untainted and 64 tainted + let tok = send(tok, buffer_ctrl_s, RSBCtrl { + length: Length: 9 + }); + let (tok, resp) = recv(tok, buffer_data_out_r); + assert_eq(resp.length, Length:9); + assert_eq(resp.error, true); + + send(tok, terminator, true); + } +} + +#[test_proc] +proc RefillingShiftBufferTestForward { + terminator_r: chan in; + terminator: chan out; + + config(terminator: chan out) { + // we need to instantiate an intermediate channel since terminator channel + // cannot be passed directly to the proc + let (terminator_s, terminator_r) = chan("terminator"); + spawn RefillingShiftBufferTest(terminator_s); + (terminator_r, terminator) + } + init {} + next(_: ()) { + let tok = join(); + let (tok, value) = recv(tok, terminator_r); + send(tok, terminator, value); + } +} + +#[test_proc] +proc RefillingShiftBufferTestBackward { + terminator_r: chan in; + terminator: chan out; + + config(terminator: chan out) { + let (terminator_s, terminator_r) = chan("terminator"); + spawn RefillingShiftBufferTest(terminator_s); + (terminator_r, terminator) + } + init {} + next(_: ()) { + let tok = join(); + let (tok, value) = recv(tok, terminator_r); + send(tok, terminator, value); + } +} + +proc RefillingShiftBufferInternalInst { + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + type MemReaderStatus = mem_reader::MemReaderStatus; + type StartReq = RefillStart; + type RSBInput = RefillingShiftBufferInput; + type RSBOutput = RefillingShiftBufferOutput; + type RSBCtrl = RefillingShiftBufferCtrl; + type SBOutput = shift_buffer::ShiftBufferOutput; + type State = RefillerState; + + config( + reader_req_s: chan out, + reader_resp_r: chan in, + start_req_r: chan in, + stop_flush_req_r: chan<()> in, + buffer_ctrl_r: chan in, + buffer_data_out_s: chan out, + snoop_ctrl_s: chan out, + buffer_data_in_s: chan out, + snoop_data_out_r: chan in, + flushing_done_s: chan<()> out, + ) { + // instantiate with BACKWARDS = true to test worst-case results + spawn RefillingShiftBufferInternal( + reader_req_s, reader_resp_r, start_req_r, stop_flush_req_r, + buffer_ctrl_r, buffer_data_out_s, snoop_ctrl_s, + buffer_data_in_s, snoop_data_out_r, flushing_done_s, + ); + } + + init { } + + next(state: ()) { } +} diff --git a/xls/modules/zstd/shift_buffer.x b/xls/modules/zstd/shift_buffer.x deleted file mode 100644 index fd520774cb..0000000000 --- a/xls/modules/zstd/shift_buffer.x +++ /dev/null @@ -1,246 +0,0 @@ -// 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 buffering proc responsible for -// reading the data stream and dividing it into the smallest packets of the -// requested size. - -import std; -import xls.modules.zstd.buffer; - -fn buffer_width(data_width: u32) -> u32 { data_width * u32:2 } - -pub struct ShiftBufferInput { - data: uN[DATA_W], - length: uN[LENGTH_W], - last: bool, -} - -pub struct ShiftBufferOutput { - data: uN[DATA_W], - length: uN[LENGTH_W], - last: bool, -} - -pub struct ShiftBufferCtrl { - length: uN[LENGTH_W], -} - -enum ShiftBufferStatus : u1 { - REFILL = 0, - FLUSH = 1, -} - -struct ShiftBufferState { - status: ShiftBufferStatus, - buff: buffer::Buffer, - ctrl: ShiftBufferCtrl, - ctrl_valid: bool, - last: bool, -} - -pub fn mask_data (data: bits[DATA_W], length: bits[LENGTH_W]) -> bits[DATA_W] { - type Data = bits[DATA_W]; - type Length = bits[LENGTH_W]; - - let mask = if length != Length:0 { (Data:1 << length) - Data:1 } else { Data:0 }; - data & mask -} - -pub proc ShiftBuffer< - DATA_W: u32, LENGTH_W: u32, - BUFFER_W: u32 = {buffer_width(DATA_W)} -> { - type Data = uN[DATA_W]; - type Length = uN[LENGTH_W]; - type BufferData = uN[BUFFER_W]; - type Input = ShiftBufferInput; - type Ctrl = ShiftBufferCtrl; - type Output = ShiftBufferOutput; - type State = ShiftBufferState; - type Status = ShiftBufferStatus; - - in_data_r: chan in; - in_ctrl_r: chan in; - out_data_s: chan out; - - config( - in_data_r: chan in, - in_ctrl_r: chan in, - out_data_s: chan out, - ) { (in_data_r, in_ctrl_r, out_data_s) } - - init { zero!() } - - next(state: State) { - - let tok0 = join(); - - // Receive control - let (tok1_0, ctrl, ctrl_valid) = - recv_if_non_blocking(tok0, in_ctrl_r, !state.ctrl_valid, state.ctrl); - - // Receive data - let can_fit = buffer::buffer_can_fit(state.buff, Data:0); - let do_recv_data = can_fit && state.status == Status::REFILL; - let (tok1_1, recv_data, recv_data_valid) = - recv_if_non_blocking(tok0, in_data_r, do_recv_data, zero!()); - - let tok1 = join(tok1_0, tok1_1); - - // Handle the request - // Uses buffer from the previous next evaluation (from state) to prevent - // creating long combinatorial logic that would allow receiving and - // sending back the data in the same cycle. - - let has_valid_request = ctrl_valid || state.ctrl_valid; - let has_enough_data = buffer::buffer_has_at_least(state.buff, ctrl.length as u32); - - let (data, do_send, new_buff, new_ctrl, new_ctrl_valid) = if has_valid_request && has_enough_data { - let (buff, data) = buffer::buffer_pop(state.buff, ctrl.length as u32); - let last = buff.length == u32:0 && state.last; - let output = Output { data: data as Data, length: ctrl.length, last }; - let do_send = ctrl.length != Length:0; - (output, do_send, buff, zero!(), false) - } else { - (zero!(), false, state.buff, ctrl, has_valid_request) - }; - - let tok2_0 = send_if(tok1, out_data_s, do_send, data); - - // Handle input data - let (new_buff, new_last) = if can_fit && recv_data_valid { - ( - buffer::buffer_append_with_length( - new_buff, recv_data.data as BufferData, recv_data.length as u32), - recv_data.last, - ) - } else { - (new_buff, state.last) - }; - - // Handle state change - let new_status = if state.status == Status::REFILL && recv_data_valid && recv_data.last { - Status::FLUSH - } else if state.status == Status::FLUSH && state.buff.length == ctrl.length as u32 { - Status::REFILL - } else { - state.status - }; - - State { - status: new_status, - buff: new_buff, - ctrl: new_ctrl, - ctrl_valid: new_ctrl_valid, - last: new_last - } - } -} - -const INST_DATA_W = u32:64; -const INST_LENGTH_W = std::clog2(INST_DATA_W) + u32:1; - -proc ShiftBufferInst { - type Input = ShiftBufferInput; - type Ctrl = ShiftBufferCtrl; - type Output = ShiftBufferOutput; - - config( - data_r: chan in, - ctrl_r: chan in, - out_s: chan out - ) { - spawn ShiftBuffer(data_r, ctrl_r, out_s); - } - - init { } - - next(state: ()) { } -} - -const TEST_DATA_W = u32:64; -const TEST_LENGTH_W = std::clog2(TEST_DATA_W) + u32:1; - -#[test_proc] -proc ShiftBufferTest { - type Input = ShiftBufferInput; - type Ctrl = ShiftBufferCtrl; - type Output = ShiftBufferOutput; - type Data = uN[TEST_DATA_W]; - type Length = uN[TEST_LENGTH_W]; - - terminator: chan out; - data_s: chan out; - ctrl_s: chan out; - out_r: chan in; - - config(terminator: chan out) { - let (data_s, data_r) = chan("in_data"); - let (ctrl_s, ctrl_r) = chan("in_ctrl"); - let (out_s, out_r) = chan("out_data"); - - spawn ShiftBuffer(data_r, ctrl_r, out_s); - (terminator, data_s, ctrl_s, out_r) - } - - init { } - - next(state: ()) { - - let tok = join(); - let tok = send(tok, data_s, Input { data: Data:0xDD_44, length: Length:16, last: false }); - let tok = send(tok, data_s, Input { data: Data:0xAA_11_BB_22_CC_33, length: Length:48, last: false }); - let tok = send(tok, data_s, Input { data: Data:0xEE_55_FF_66_00_77_11_88, length: Length:64, last: false }); - - let tok = send(tok, ctrl_s, Ctrl { length: Length:8 }); - let (tok, output) = recv(tok, out_r); - assert_eq(output, Output { data: Data:0x44, length: Length:8, last: false }); - - let tok = send(tok, ctrl_s, Ctrl { length: Length:4 }); - let (tok, output) = recv(tok, out_r); - assert_eq(output, Output { data: Data:0xD, length: Length:4, last: false }); - - let tok = send(tok, ctrl_s, Ctrl { length: Length:64 }); - let (tok, output) = recv(tok, out_r); - assert_eq(output, Output { data: Data:0x18_8A_A1_1B_B2_2C_C3_3D, length: Length:64, last: false }); - - let tok = send(tok, data_s, Input { data: Data:0x44_BB_55_CC, length: Length:32, last: false }); - let tok = send(tok, data_s, Input { data: Data:0x22_99_33_AA, length: Length:32, last: true }); - let tok = send(tok, data_s, Input { data: Data:0x66_DD_77_EE_88_FF_99_00, length: Length:64, last: false }); - - let tok = send(tok, ctrl_s, Ctrl { length: Length:4 }); - let (tok, output) = recv(tok, out_r); - assert_eq(output, Output { data: Data:0x1, length: Length:4, last: false }); - - let tok = send(tok, ctrl_s, Ctrl { length: Length:64 }); - let (tok, output) = recv(tok, out_r); - assert_eq(output, Output { data: Data:0x55_CC_EE_55_FF_66_00_77, length: Length:64, last: false }); - - let tok = send(tok, ctrl_s, Ctrl { length: Length:16 }); - let (tok, output) = recv(tok, out_r); - assert_eq(output, Output { data: Data:0x44_BB, length: Length:16, last: false }); - - let tok = send(tok, ctrl_s, Ctrl { length: Length:32 }); - let (tok, output) = recv(tok, out_r); - assert_eq(output, Output { data: Data:0x22_99_33_AA, length: Length:32, last: true }); - - let tok = send(tok, ctrl_s, Ctrl { length: Length:64 }); - - let (tok, output) = recv(tok, out_r); - assert_eq(output, Output { data: Data:0x66_DD_77_EE_88_FF_99_00, length: Length:64, last: false }); - - send(tok, terminator, true); - } -} From f63f0b29d021032b8315fdf2504d179e2ae6158c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Ob=C5=82onczek?= Date: Wed, 13 Nov 2024 22:32:07 +0100 Subject: [PATCH 37/85] modules/zstd: Add FSE tables and lookup decoder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Internal-tag: [#68052] Signed-off-by: Krzysztof Obłonczek --- xls/modules/zstd/BUILD | 280 +++++- xls/modules/zstd/common.x | 102 +- xls/modules/zstd/fse_dec.x | 1320 +++++++++++++++++++++++++ xls/modules/zstd/fse_lookup_dec.x | 1049 ++++++++++++++++++++ xls/modules/zstd/fse_proba_freq_dec.x | 212 ++-- xls/modules/zstd/fse_table_creator.x | 652 ++++++++++++ xls/modules/zstd/fse_table_iterator.x | 124 +++ 7 files changed, 3669 insertions(+), 70 deletions(-) create mode 100644 xls/modules/zstd/fse_dec.x create mode 100644 xls/modules/zstd/fse_lookup_dec.x create mode 100644 xls/modules/zstd/fse_table_creator.x create mode 100644 xls/modules/zstd/fse_table_iterator.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 32e9e05ebf..3360a55e03 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -45,6 +45,19 @@ common_codegen_args = { "multi_proc": "true", } +xls_dslx_library( + name = "math_dslx", + srcs = [ + "math.x", + ], +) + +xls_dslx_test( + name = "math_dslx_test", + library = ":math_dslx", + tags = ["manual"], +) + xls_dslx_library( name = "buffer_dslx", srcs = [ @@ -163,7 +176,10 @@ xls_dslx_library( srcs = [ "common.x", ], - deps = [], + deps = [ + "//xls/modules/shift_buffer:shift_buffer_dslx", + "//xls/examples:ram_dslx", + ], ) xls_dslx_library( @@ -928,7 +944,8 @@ xls_dslx_library( deps = [ ":common_dslx", ":ram_wr_handler_dslx", - ":shift_buffer_dslx", + ":refilling_shift_buffer_dslx", + "//xls/modules/shift_buffer:shift_buffer_dslx", "//xls/examples:ram_dslx", ], ) @@ -1283,3 +1300,262 @@ place_and_route( tags = ["manual"], target_die_utilization_percentage = "10", ) + +xls_dslx_library( + name = "fse_lookup_dec_dslx", + srcs = [ + "fse_lookup_dec.x", + ], + deps = [ + "//xls/examples:ram_dslx", + "//xls/modules/zstd/memory:axi_dslx", + "//xls/modules/zstd/memory:axi_ram_dslx", + "//xls/modules/zstd:common_dslx", + "//xls/modules/zstd:fse_table_creator_dslx", + "//xls/modules/zstd:refilling_shift_buffer_dslx", + "//xls/modules/zstd:fse_proba_freq_dec_dslx", + "//xls/modules/shift_buffer:shift_buffer_dslx", + ] +) + +xls_dslx_test( + name = "fse_lookup_dec_dslx_test", + library = ":fse_lookup_dec_dslx", +) + +xls_dslx_library( + name = "fse_table_iterator_dslx", + srcs = ["fse_table_iterator.x"], + deps = [ + ":common_dslx", + ], +) + +xls_dslx_test( + name = "fse_table_iterator_dslx_test", + library = ":fse_table_iterator_dslx", +) + +xls_dslx_verilog( + name = "fse_table_iterator_verilog", + codegen_args = { + "module_name": "FseTableIterator", + "delay_model": "asap7", + "pipeline_stages": "1", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "FseTableIterator", + library = ":fse_table_iterator_dslx", + verilog_file = "fse_table_iterator.v", +) + +xls_benchmark_ir( + name = "fse_table_iterator_opt_ir_benchmark", + src = ":fse_table_iterator_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "10", + "delay_model": "asap7", + }, +) + +verilog_library( + name = "fse_table_iterator_verilog_lib", + srcs = [ + ":fse_table_iterator.v", + ], +) + +synthesize_rtl( + name = "fse_table_iterator_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "FseTableIterator", + deps = [ + ":fse_table_iterator_verilog_lib", + ], +) + +benchmark_synth( + name = "fse_table_iterator_benchmark_synth", + synth_target = ":fse_table_iterator_synth_asap7", +) + +place_and_route( + name = "fse_table_iterator_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 = ":fse_table_iterator_synth_asap7", + target_die_utilization_percentage = "10", +) + +xls_dslx_library( + name = "fse_table_creator_dslx", + srcs = ["fse_table_creator.x"], + deps = [ + ":common_dslx", + ":fse_table_iterator_dslx", + ":ram_wr_handler_dslx", + "//xls/examples:ram_dslx", + ], +) + +xls_dslx_test( + name = "fse_table_creator_dslx_test", + library = ":fse_table_creator_dslx", +) + +xls_dslx_verilog( + name = "fse_table_creator_verilog", + codegen_args = { + "module_name": "FseTableCreator", + "delay_model": "asap7", + "pipeline_stages": "4", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "FseTableCreatorInst", + library = ":fse_table_creator_dslx", + opt_ir_args = { + "inline_procs": "true", + "top": "__fse_table_creator__FseTableCreatorInst__FseTableCreator_0__8_16_1_8_32_4_8_16_1_next", + }, + verilog_file = "fse_table_creator.v", + tags = ["manual"], +) + +xls_benchmark_ir( + name = "fse_table_creator_opt_ir_benchmark", + src = ":fse_table_creator_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "10", + "delay_model": "asap7", + }, +) + +xls_benchmark_verilog( + name = "fse_table_creator_verilog_benchmark", + verilog_target = "fse_table_creator_verilog", +) + +verilog_library( + name = "fse_table_creator_lib", + srcs = [ + ":fse_table_creator.v", + ], +) + +synthesize_rtl( + name = "fse_table_creator_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "FseTableCreator", + deps = [ + ":fse_table_creator_lib", + ], +) + +benchmark_synth( + name = "fse_table_creator_benchmark_synth", + synth_target = ":fse_table_creator_asap7", +) + +place_and_route( + name = "fse_table_creator_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 = ":fse_table_creator_asap7", + target_die_utilization_percentage = "10", +) + +xls_dslx_library( + name = "fse_dec_dslx", + srcs = [ + "fse_dec.x", + ], + deps = [ + "//xls/examples:ram_dslx", + ":common_dslx", + ":math_dslx", + ":fse_table_creator_dslx", + "//xls/modules/zstd:refilling_shift_buffer_dslx", + "//xls/examples:ram_dslx", + ], +) + +xls_dslx_test( + name = "fse_dec_dslx_test", + library = ":fse_dec_dslx", + tags = ["manual"], +) + +xls_dslx_verilog( + name = "fse_dec_verilog", + codegen_args = { + "module_name": "FseDecoder", + "delay_model": "asap7", + "pipeline_stages": "8", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "FseDecoderInst", + opt_ir_args = { + "top": "__fse_dec__FseDecoderInst__FseDecoder_0__64_8_32_4_64_7_next" + }, + library = ":fse_dec_dslx", + tags = ["manual"], + verilog_file = "fse_dec.v", +) + +xls_benchmark_ir( + name = "fse_dec_opt_ir_benchmark", + src = ":fse_dec_verilog.opt.ir", + benchmark_ir_args = { + "delay_model": "asap7", + "pipeline_stages": "3", + "inline_procs": "false" + }, + tags = ["manual"], +) + +verilog_library( + name = "fse_dec_verilog_lib", + srcs = [ + ":fse_dec.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "fse_dec_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "FseDecoder", + deps = [ + ":fse_dec_verilog_lib", + ], +) + +benchmark_synth( + name = "fse_dec_benchmark_synth", + synth_target = ":fse_dec_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "fse_dec_place_and_route", + clock_period = "750", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + die_height_microns = 100, + die_width_microns = 100, + stop_after_step = "global_routing", + synthesized_rtl = ":fse_dec_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) diff --git a/xls/modules/zstd/common.x b/xls/modules/zstd/common.x index 98c58c60e1..13773aad47 100644 --- a/xls/modules/zstd/common.x +++ b/xls/modules/zstd/common.x @@ -13,6 +13,8 @@ // limitations under the License. import std; +import xls.examples.ram; +import xls.modules.shift_buffer.shift_buffer; pub const DATA_WIDTH = u32:64; pub const MAX_ID = u32::MAX; @@ -21,6 +23,7 @@ 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 const MAX_BLOCK_SIZE_KB = u32:64; pub const BLOCK_PACKET_WIDTH = u32:32; @@ -58,9 +61,19 @@ pub struct ExtendedBlockDataPacket { 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 + length: CopyOrMatchLength, // Literal length or match length + content: CopyOrMatchContent, // Literal data or match offset + last: bool, // Last packet in frame +} + +pub struct BlockSyncData { + id: u32, + last_block: bool, +} + +pub struct CommandConstructorData { + sync: BlockSyncData, + data: SequenceExecutorPacket, } // Defines output format of the ZSTD Decoder @@ -101,10 +114,17 @@ pub const FSE_MAX_SYMBOLS = u32:256; pub const FSE_ACCURACY_LOG_WIDTH = std::clog2(FSE_MAX_ACCURACY_LOG + u32:1); pub const FSE_SYMBOL_COUNT_WIDTH = std::clog2(FSE_MAX_SYMBOLS + u32:1); pub const FSE_REMAINING_PROBA_WIDTH = std::clog2((u32:1 << FSE_MAX_ACCURACY_LOG) + u32:1); +pub const FSE_TABLE_INDEX_WIDTH = std::clog2(u32:1 << FSE_MAX_ACCURACY_LOG); + +pub const FSE_PROB_DIST_WIDTH = u32:16; +pub const FSE_MAX_PROB_DIST = u32:256; +pub const FSE_SYMBOL_WIDTH = u32:16; pub type FseRemainingProba = uN[FSE_REMAINING_PROBA_WIDTH]; pub type FseAccuracyLog = uN[FSE_ACCURACY_LOG_WIDTH]; pub type FseSymbolCount = uN[FSE_SYMBOL_COUNT_WIDTH]; +pub type FseTableIndex = uN[FSE_TABLE_INDEX_WIDTH]; + // defined in https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1.3.2.2.1 pub const FSE_LITERAL_LENGTH_DEFAULT_DIST = s16[36]:[ @@ -140,5 +160,81 @@ pub enum FSETableType : u2 { MATCH = 2, } +pub struct FseTableRecord { + symbol: u8, + num_of_bits: u8, + base: u16 +} + pub struct FseRemainder { value: u1, valid: bool } pub struct FseProbaFreqDecoderCtrl { remainder: FseRemainder, finished: bool } + +pub struct FseTableCreatorCtrl { + accuracy_log: FseAccuracyLog, + negative_proba_count: FseSymbolCount +} + +pub fn highest_set_bit(num: uN[N]) -> u16 { std::flog2(num) } + +// SequenceDecoder + +pub const SEQDEC_DPD_RAM_DATA_WIDTH = FSE_PROB_DIST_WIDTH; +pub const SEQDEC_DPD_RAM_SIZE = FSE_MAX_PROB_DIST; +pub const SEQDEC_DPD_RAM_WORD_PARTITION_SIZE = SEQDEC_DPD_RAM_DATA_WIDTH; +pub const SEQDEC_DPD_RAM_ADDR_WIDTH = std::clog2(SEQDEC_DPD_RAM_SIZE); +pub const SEQDEC_DPD_RAM_NUM_PARTITIONS = ram::num_partitions(SEQDEC_DPD_RAM_WORD_PARTITION_SIZE, SEQDEC_DPD_RAM_DATA_WIDTH); + +pub const SEQDEC_TMP_RAM_DATA_WIDTH = FSE_PROB_DIST_WIDTH; +pub const SEQDEC_TMP_RAM_SIZE = FSE_MAX_PROB_DIST; +pub const SEQDEC_TMP_RAM_WORD_PARTITION_SIZE = SEQDEC_TMP_RAM_DATA_WIDTH; +pub const SEQDEC_TMP_RAM_ADDR_WIDTH = std::clog2(SEQDEC_TMP_RAM_SIZE); +pub const SEQDEC_TMP_RAM_NUM_PARTITIONS = ram::num_partitions(SEQDEC_TMP_RAM_WORD_PARTITION_SIZE, SEQDEC_TMP_RAM_DATA_WIDTH); + +pub const SEQDEC_FSE_RAM_DATA_WIDTH = u32:32; +pub const SEQDEC_FSE_RAM_SIZE = FSE_MAX_SYMBOLS; +pub const SEQDEC_FSE_RAM_WORD_PARTITION_SIZE = SEQDEC_FSE_RAM_DATA_WIDTH / u32:3; +pub const SEQDEC_FSE_RAM_ADDR_WIDTH: u32 = std::clog2(SEQDEC_FSE_RAM_SIZE); +pub const SEQDEC_FSE_RAM_NUM_PARTITIONS: u32 = ram::num_partitions(SEQDEC_FSE_RAM_WORD_PARTITION_SIZE, SEQDEC_FSE_RAM_DATA_WIDTH); + +pub const SEQDEC_BLOCK_RAM_DATA_WIDTH = DATA_WIDTH; +pub const SEQDEC_BLOCK_RAM_SIZE = (MAX_BLOCK_SIZE_KB * u32:1024 * u32:8) / SEQDEC_BLOCK_RAM_DATA_WIDTH; +pub const SEQDEC_BLOCK_RAM_WORD_PARTITION_SIZE = SEQDEC_BLOCK_RAM_DATA_WIDTH; +pub const SEQDEC_BLOCK_RAM_ADDR_WIDTH = std::clog2(SEQDEC_BLOCK_RAM_SIZE); +pub const SEQDEC_BLOCK_RAM_NUM_PARTITIONS: u32 = ram::num_partitions(SEQDEC_BLOCK_RAM_WORD_PARTITION_SIZE, SEQDEC_BLOCK_RAM_DATA_WIDTH); + +pub const SEQDEC_SHIFT_BUFFER_DATA_WIDTH = DATA_WIDTH; +pub const SEQDEC_SHIFT_BUFFER_LENGTH_WIDTH = std::clog2(SEQDEC_SHIFT_BUFFER_DATA_WIDTH + u32:1); + +pub type SeqDecDpdRamReadReq = ram::ReadReq; +pub type SeqDecDpdRamReadResp = ram::ReadResp; +pub type SeqDecDpdRamWriteReq = ram::WriteReq; +pub type SeqDecDpdRamWriteResp = ram::WriteResp; +pub type SeqDecDpdRamAddr = bits[SEQDEC_DPD_RAM_ADDR_WIDTH]; +pub type SeqDecDpdRamData = bits[SEQDEC_DPD_RAM_DATA_WIDTH]; + +pub type SeqDecTmpRamReadReq = ram::ReadReq; +pub type SeqDecTmpRamReadResp = ram::ReadResp; +pub type SeqDecTmpRamWriteReq = ram::WriteReq; +pub type SeqDecTmpRamWriteResp = ram::WriteResp; +pub type SeqDecTmpRamAddr = bits[SEQDEC_TMP_RAM_ADDR_WIDTH]; +pub type SeqDecTmpRamData = bits[SEQDEC_TMP_RAM_DATA_WIDTH]; + +pub type SeqDecFseRamReadReq = ram::ReadReq; +pub type SeqDecFseRamReadResp = ram::ReadResp; +pub type SeqDecFseRamWriteReq = ram::WriteReq; +pub type SeqDecFseRamWriteResp = ram::WriteResp; +pub type SeqDecFseRamAddr = bits[SEQDEC_FSE_RAM_ADDR_WIDTH]; +pub type SeqDecFseRamData = bits[SEQDEC_FSE_RAM_DATA_WIDTH]; + +pub type SeqDecBlockRamReadReq = ram::ReadReq; +pub type SeqDecBlockRamReadResp = ram::ReadResp; +pub type SeqDecBlockRamWriteReq = ram::WriteReq; +pub type SeqDecBlockRamWriteResp = ram::WriteResp; +pub type SeqDecBlockRamAddr = bits[SEQDEC_BLOCK_RAM_ADDR_WIDTH]; +pub type SeqDecBlockRamData = bits[SEQDEC_BLOCK_RAM_DATA_WIDTH]; + +pub type SeqDecShiftBufferCtrl = shift_buffer::ShiftBufferCtrl; +pub type SeqDecShiftBufferInput = shift_buffer::ShiftBufferPacket; +pub type SeqDecShiftBufferOutput = shift_buffer::ShiftBufferOutput; +pub type SeqDecShiftBufferPacket = shift_buffer::ShiftBufferPacket; +pub type SeqDecShiftBufferStatus = shift_buffer::ShiftBufferStatus; diff --git a/xls/modules/zstd/fse_dec.x b/xls/modules/zstd/fse_dec.x new file mode 100644 index 0000000000..01a1416862 --- /dev/null +++ b/xls/modules/zstd/fse_dec.x @@ -0,0 +1,1320 @@ +// 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; +import xls.modules.zstd.common; +import xls.modules.zstd.math; +import xls.modules.zstd.refilling_shift_buffer; +import xls.modules.zstd.fse_table_creator; + +type FseTableRecord = common::FseTableRecord; + +// +// type Base = u16; +// type Symbol = u16; +// type NumOfBits = u16; +// +// struct FseTableRecord { +// symbol: Symbol, +// num_of_bits: NumOfBits, +// base: Base +// } +// +// pub fn bits_to_fse_record(bit: u48) -> FseTableRecord { +// FseTableRecord { +// symbol: bit[0:16], +// num_of_bits: bit[16:32], +// base: bit[32:48] +// } +// } +// +// fn fse_record_to_bits(record: FseTableRecord) -> u48 { +// record.base ++ record.num_of_bits ++ record.symbol +// } + + + +type BlockSyncData = common::BlockSyncData; +type SequenceExecutorMessageType = common::SequenceExecutorMessageType; +type SequenceExecutorPacket = common::SequenceExecutorPacket; +type CommandConstructorData = common::CommandConstructorData; + +type CopyOrMatchLength = common::CopyOrMatchLength; +type CopyOrMatchContent = common::CopyOrMatchContent; + +type RefillingSBCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; +type RefillingSBOutput = refilling_shift_buffer::RefillingShiftBufferOutput; + +pub enum FseDecoderStatus: u1 { + OK = 0, + ERROR = 1, +} + +pub struct FseDecoderCtrl { + sync: BlockSyncData, + sequences_count: u24, + literals_count: u20, + of_acc_log: u7, + ll_acc_log: u7, + ml_acc_log: u7, +} + +pub struct FseDecoderFinish { + status: FseDecoderStatus +} + +// 3.1.1.3.2.1.1. Sequence Codes for Lengths and Offsets +const SEQ_MAX_CODES_LL = u8:35; +const SEQ_MAX_CODES_ML = u8:51; + +const SEQ_LITERAL_LENGTH_BASELINES = u32[36]:[ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 18, 20, 22, 24, 28, 32, 40, + 48, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536 +]; +const SEQ_LITERAL_LENGTH_EXTRA_BITS = u8[36]:[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, + 1, 1, 2, 2, 3, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 +]; + +const SEQ_MATCH_LENGTH_BASELINES = u32[53]:[ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 37, 39, 41, 43, 47, 51, 59, 67, 83, + 99, 131, 259, 515, 1027, 2051, 4099, 8195, 16387, 32771, 65539 +]; +const SEQ_MATCH_LENGTH_EXTRA_BITS = u8[53]:[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 2, 2, 3, 3, 4, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 +]; + +enum SEQ_PART : u2 { + LiteralLength = 0, + Offset = 1, + MatchLength = 2, +} + +enum FseDecoderFSM : u5 { + RECV_CTRL = 0, + PADDING = 1, + INIT_OF_STATE = 2, + INIT_ML_STATE = 3, + INIT_LL_STATE = 4, + SEND_RAM_RD_REQ = 5, + RECV_RAM_RD_RESP = 6, + READ_OF_BITS = 7, + READ_ML_BITS = 8, + READ_LL_BITS = 9, + UPDATE_OF_STATE = 10, + UPDATE_ML_STATE = 11, + UPDATE_LL_STATE = 12, + SEND_COMMAND_LITERAL = 13, + SEND_COMMAND_SEQUENCE = 14, + SEND_LEFTOVER_LITERALS_REQ = 15, + SEND_FINISH = 16, +} + +struct FseDecoderState { + fsm: FseDecoderFSM, + ctrl: FseDecoderCtrl, + sequences_count: u24, + literals_count: u20, + of: u64, + ll: u64, + ml: u64, + of_fse_table_record: FseTableRecord, + ll_fse_table_record: FseTableRecord, + ml_fse_table_record: FseTableRecord, + of_fse_table_record_valid: bool, + ll_fse_table_record_valid: bool, + ml_fse_table_record_valid: bool, + of_state: u16, + ll_state: u16, + ml_state: u16, + read_bits: u16, + read_bits_length: u7, + read_bits_needed: u7, + sent_buf_ctrl: bool, + shift_buffer_error: bool, + padding: u4, +} + +pub proc FseDecoder< + RAM_DATA_W: u32, RAM_ADDR_W: u32, RAM_NUM_PARTITIONS:u32, + AXI_DATA_W: u32, + REFILLING_SB_DATA_W: u32 = {AXI_DATA_W}, + REFILLING_SB_LENGTH_W: u32 = {refilling_shift_buffer::length_width(REFILLING_SB_DATA_W)}, +> { + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; + + type RefillingSBCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + type RefillingSBOutput = refilling_shift_buffer::RefillingShiftBufferOutput; + + // control + ctrl_r: chan in; + finish_s: chan out; + + // shift buffer + rsb_ctrl_s: chan out; + rsb_data_r: chan in; + + // output command + command_s: chan out; + + // RAMs + ll_fse_rd_req_s: chan out; + ll_fse_rd_resp_r: chan in; + + ml_fse_rd_req_s: chan out; + ml_fse_rd_resp_r: chan in; + + of_fse_rd_req_s: chan out; + of_fse_rd_resp_r: chan in; + + config ( + ctrl_r: chan in, + finish_s: chan out, + rsb_ctrl_s: chan out, + rsb_data_r: chan in, + command_s: chan out, + ll_fse_rd_req_s: chan out, + ll_fse_rd_resp_r: chan in, + ml_fse_rd_req_s: chan out, + ml_fse_rd_resp_r: chan in, + of_fse_rd_req_s: chan out, + of_fse_rd_resp_r: chan in, + ) { + ( + ctrl_r, finish_s, + rsb_ctrl_s, rsb_data_r, + command_s, + ll_fse_rd_req_s, ll_fse_rd_resp_r, + ml_fse_rd_req_s, ml_fse_rd_resp_r, + of_fse_rd_req_s, of_fse_rd_resp_r, + ) + } + + init { zero!() } + + next (state: FseDecoderState) { + type RamAddr = uN[RAM_ADDR_W]; + const RAM_MASK_ALL = std::unsigned_max_value(); + + let tok0 = join(); + + // receive ctrl + let (_, ctrl, ctrl_valid) = recv_if_non_blocking(tok0, ctrl_r, state.fsm == FseDecoderFSM::RECV_CTRL, zero!()); + if ctrl_valid { + trace_fmt!("ctrl: {:#x}", ctrl); + } else {}; + let state = if ctrl_valid { + FseDecoderState { + ctrl: ctrl, + sequences_count: ctrl.sequences_count, + ..state + } + } else { state }; + + // receive ram read response + let (_, ll_rd_resp, ll_rd_resp_valid) = recv_if_non_blocking(tok0, ll_fse_rd_resp_r, state.fsm == FseDecoderFSM::RECV_RAM_RD_RESP, zero!()); + let (_, ml_rd_resp, ml_rd_resp_valid) = recv_if_non_blocking(tok0, ml_fse_rd_resp_r, state.fsm == FseDecoderFSM::RECV_RAM_RD_RESP, zero!()); + let (_, of_rd_resp, of_rd_resp_valid) = recv_if_non_blocking(tok0, of_fse_rd_resp_r, state.fsm == FseDecoderFSM::RECV_RAM_RD_RESP, zero!()); + + let ll_fse_table_record = fse_table_creator::bits_to_fse_record(ll_rd_resp.data); + let ml_fse_table_record = fse_table_creator::bits_to_fse_record(ml_rd_resp.data); + let of_fse_table_record = fse_table_creator::bits_to_fse_record(of_rd_resp.data); + + // if ll_rd_resp_valid { + // trace_fmt!("ll_fse_table_record: {:#x}", ll_fse_table_record); + // } else {}; + // if ml_rd_resp_valid { + // trace_fmt!("ml_fse_table_record: {:#x}", ml_fse_table_record); + // } else {}; + // if of_rd_resp_valid { + // trace_fmt!("of_fse_table_record: {:#x}", of_fse_table_record); + // } else {}; + // validate LL and ML symbols + assert!(!(ll_rd_resp_valid && ll_fse_table_record.symbol > SEQ_MAX_CODES_LL), "invalid_literal_length_symbol"); + assert!(!(ml_rd_resp_valid && ml_fse_table_record.symbol > SEQ_MAX_CODES_ML), "invalid_match_length_symbol"); + + // request records + let do_send_ram_rd_req = state.fsm == FseDecoderFSM::SEND_RAM_RD_REQ; + + let ll_req = FseRamRdReq { addr: state.ll_state as RamAddr, mask: RAM_MASK_ALL}; + let ml_req = FseRamRdReq { addr: state.ml_state as RamAddr, mask: RAM_MASK_ALL}; + let of_req = FseRamRdReq { addr: state.of_state as RamAddr, mask: RAM_MASK_ALL}; + + send_if(tok0, ll_fse_rd_req_s, do_send_ram_rd_req, ll_req); + send_if(tok0, ml_fse_rd_req_s, do_send_ram_rd_req, ml_req); + send_if(tok0, of_fse_rd_req_s, do_send_ram_rd_req, of_req); + + if do_send_ram_rd_req { + trace_fmt!("ll_req: {:#x}", ll_req); + trace_fmt!("ml_req: {:#x}", ml_req); + trace_fmt!("of_req: {:#x}", of_req); + } else {}; + + // read bits + let do_read_bits = ( + state.fsm == FseDecoderFSM::PADDING || + state.fsm == FseDecoderFSM::INIT_OF_STATE || + state.fsm == FseDecoderFSM::INIT_ML_STATE || + state.fsm == FseDecoderFSM::INIT_LL_STATE || + state.fsm == FseDecoderFSM::READ_OF_BITS || + state.fsm == FseDecoderFSM::READ_ML_BITS || + state.fsm == FseDecoderFSM::READ_LL_BITS || + state.fsm == FseDecoderFSM::UPDATE_OF_STATE || + state.fsm == FseDecoderFSM::UPDATE_ML_STATE || + state.fsm == FseDecoderFSM::UPDATE_LL_STATE + ); + let do_send_buf_ctrl = do_read_bits && !state.sent_buf_ctrl; + + let buf_ctrl_length = if ((state.read_bits_needed - state.read_bits_length) > REFILLING_SB_DATA_W as u7) { + REFILLING_SB_DATA_W as u7 + } else { + state.read_bits_needed - state.read_bits_length + }; + + if do_send_buf_ctrl { + trace_fmt!("Asking for {:#x} data", buf_ctrl_length); + } else {}; + + send_if(tok0, rsb_ctrl_s, do_send_buf_ctrl, RefillingSBCtrl { + length: buf_ctrl_length, + }); + + let state = if do_send_buf_ctrl { + FseDecoderState { sent_buf_ctrl: do_send_buf_ctrl, ..state } + } else { state }; + + let recv_sb_output = (do_read_bits && state.sent_buf_ctrl); + let (_, buf_data, buf_data_valid) = recv_if_non_blocking(tok0, rsb_data_r, recv_sb_output, zero!()); + if buf_data_valid { + trace_fmt!("[FseDecoder] Received data {:#x} in state {}", buf_data, state.fsm); + } else { }; + + let state = if do_read_bits & buf_data_valid { + FseDecoderState { + sent_buf_ctrl: false, + read_bits: math::logshiftl(buf_data.data as u16, state.read_bits_length) | state.read_bits, + read_bits_length: state.read_bits_length + buf_data.length, + shift_buffer_error: state.shift_buffer_error | buf_data.error, + ..state + } + } else { state }; + + // send command + let literals_count = state.literals_count + state.ll as u20; + let command_data = if state.fsm == FseDecoderFSM::SEND_COMMAND_LITERAL { + trace_fmt!("(ll: {:#x}, ml: {:#x}, of: {:#x}", state.ll, state.ml, state.of); + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: state.ll, + content: CopyOrMatchContent:0, + last: false, + } + } else if state.fsm == FseDecoderFSM::SEND_COMMAND_SEQUENCE { + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: state.ml, + content: state.of, + last: (state.sequences_count == u24:1) && (state.literals_count == state.ctrl.literals_count), + } + } else { + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: (state.ctrl.literals_count - state.literals_count) as CopyOrMatchLength, + content: CopyOrMatchContent:0, + last: true, + } + }; + let do_send_command = ( + (command_data.length != CopyOrMatchLength:0) && + ( + state.fsm == FseDecoderFSM::SEND_COMMAND_LITERAL || + state.fsm == FseDecoderFSM::SEND_COMMAND_SEQUENCE || + state.fsm == FseDecoderFSM::SEND_LEFTOVER_LITERALS_REQ + ) + ); + let command = CommandConstructorData { + sync: state.ctrl.sync, + data: command_data, + }; + + if do_send_command { + trace_fmt!("[FseDecoder] Sending command: {:#x}", command); + } else {}; + send_if(tok0, command_s, do_send_command, command); + + // send finish + send_if(tok0, finish_s, state.fsm == FseDecoderFSM::SEND_FINISH, FseDecoderFinish { + status: if state.shift_buffer_error { FseDecoderStatus::ERROR } else { FseDecoderStatus::OK } + }); + + // update state + match (state.fsm) { + FseDecoderFSM::RECV_CTRL => { + if (ctrl_valid) { + trace_fmt!("[FseDecoder]: Moving to PADDING"); + if ctrl.sequences_count == u24:0 { + FseDecoderState { + fsm: FseDecoderFSM::SEND_LEFTOVER_LITERALS_REQ, + ctrl: ctrl, + ..state + } + } else { + FseDecoderState { + fsm: FseDecoderFSM::PADDING, + ctrl: ctrl, + read_bits: u16:0, + read_bits_length: u7:0, + read_bits_needed: u7:1, + ..state + } + } + } else { state } + }, + FseDecoderFSM::PADDING => { + if (state.read_bits_needed == state.read_bits_length && !state.sent_buf_ctrl) { + trace_fmt!("[FseDecoder]: Moving to INIT_LL_STATE"); + + let padding = state.padding + u4:1; + assert!(padding <= u4:8, "invalid_padding"); + + let padding_available = (state.read_bits as u1 == u1:0); + if padding_available { + FseDecoderState { + fsm: FseDecoderFSM::PADDING, + read_bits: u16:0, + read_bits_length: u7:0, + read_bits_needed: u7:1, + padding, ..state + } + } else { + trace_fmt!("padding is: {:#x}", padding); + FseDecoderState { + fsm: FseDecoderFSM::INIT_LL_STATE, + read_bits: u16:0, + read_bits_length: u7:0, + read_bits_needed: state.ctrl.ll_acc_log, + ..state + } + } + } else { state } + }, + + FseDecoderFSM::INIT_LL_STATE => { + if (state.read_bits_needed == state.read_bits_length && !state.sent_buf_ctrl) { + trace_fmt!("[FseDecoder]: Moving to INIT_OF_STATE"); + FseDecoderState { + fsm: FseDecoderFSM::INIT_OF_STATE, + ll_state: state.read_bits, + read_bits: u16:0, + read_bits_length: u7:0, + read_bits_needed: state.ctrl.of_acc_log, + ..state + } + } else { state } + }, + FseDecoderFSM::INIT_OF_STATE => { + if (state.read_bits_needed == state.read_bits_length && !state.sent_buf_ctrl) { + trace_fmt!("[FseDecoder]: Moving to INIT_ML_STATE"); + FseDecoderState { + fsm: FseDecoderFSM::INIT_ML_STATE, + of_state: state.read_bits, + read_bits: u16:0, + read_bits_length: u7:0, + read_bits_needed: state.ctrl.ml_acc_log, + ..state + } + } else { state } + }, + FseDecoderFSM::INIT_ML_STATE => { + if (state.read_bits_needed == state.read_bits_length && !state.sent_buf_ctrl) { + trace_fmt!("[FseDecoder]: Moving to RAM_RD_REQ"); + FseDecoderState { + fsm: FseDecoderFSM::SEND_RAM_RD_REQ, + ml_state: state.read_bits, + read_bits: u16:0, + read_bits_length: u7:0, + read_bits_needed: u7:0, + ..state + } + } else { state } + }, + FseDecoderFSM::SEND_RAM_RD_REQ => { + // trace_fmt!("State LL: {} ML: {} OF: {}", state.ll_state, state.ml_state, state.of_state); + trace_fmt!("State LL: {:#x} ML: {:#x} OF: {:#x}", state.ll_state, state.ml_state, state.of_state); + FseDecoderState { + fsm: FseDecoderFSM::RECV_RAM_RD_RESP, + ll_fse_table_record_valid: false, + ml_fse_table_record_valid: false, + of_fse_table_record_valid: false, + ..state + } + }, + FseDecoderFSM::RECV_RAM_RD_RESP => { + trace_fmt!("RECV_RAM_RD_RESP"); + // save fse records in state + let state = if ll_rd_resp_valid { + FseDecoderState { ll_fse_table_record: ll_fse_table_record, ll_fse_table_record_valid: true, ..state } + } else { state }; + let state = if ml_rd_resp_valid { + FseDecoderState { ml_fse_table_record: ml_fse_table_record, ml_fse_table_record_valid: true, ..state } + } else { state }; + let state = if of_rd_resp_valid { + FseDecoderState { of_fse_table_record: of_fse_table_record, of_fse_table_record_valid: true, ..state } + } else { state }; + + if (state.ll_fse_table_record_valid && + state.ml_fse_table_record_valid && + state.of_fse_table_record_valid + ) { + trace_fmt!("all states received: {:#x}", state); + FseDecoderState { + fsm: FseDecoderFSM::READ_OF_BITS, + read_bits: u16:0, + read_bits_length: u7:0, + read_bits_needed: state.of_fse_table_record.symbol as u7, + ..state + } + } else { state } + }, + FseDecoderFSM::READ_OF_BITS => { + if ((state.read_bits_needed == state.read_bits_length) && !state.sent_buf_ctrl) { + trace_fmt!("of_code: {:#x}", state.of_fse_table_record.symbol); + FseDecoderState { + fsm: FseDecoderFSM::READ_ML_BITS, + of: (math::logshiftl(u32:1, state.of_fse_table_record.symbol) + state.read_bits as u32) as u64, + read_bits: u16:0, + read_bits_length: u7:0, + read_bits_needed: SEQ_MATCH_LENGTH_EXTRA_BITS[state.ml_fse_table_record.symbol] as u7, + ..state + } + } else { state } + }, + FseDecoderFSM::READ_ML_BITS => { + if ((state.read_bits_needed == state.read_bits_length) && !state.sent_buf_ctrl) { + FseDecoderState { + fsm: FseDecoderFSM::READ_LL_BITS, + ml: (SEQ_MATCH_LENGTH_BASELINES[state.ml_fse_table_record.symbol] + state.read_bits as u32) as u64, + read_bits: u16:0, + read_bits_length: u7:0, + read_bits_needed: SEQ_LITERAL_LENGTH_EXTRA_BITS[state.ll_fse_table_record.symbol] as u7, + ..state + } + } else { state } + + }, + FseDecoderFSM::READ_LL_BITS => { + if ((state.read_bits_needed == state.read_bits_length) && !state.sent_buf_ctrl) { + if state.sequences_count == u24:1 { + // skip state update for last sequence + FseDecoderState { + fsm: FseDecoderFSM::SEND_COMMAND_LITERAL, + of_state: state.of_fse_table_record.base + state.read_bits, + ll: (SEQ_LITERAL_LENGTH_BASELINES[state.ll_fse_table_record.symbol] + state.read_bits as u32) as u64, + read_bits: u16:0, + read_bits_length: u7:0, + read_bits_needed: u7:0, + ..state + } + } else { + FseDecoderState { + fsm: FseDecoderFSM::UPDATE_LL_STATE, + ll: (SEQ_LITERAL_LENGTH_BASELINES[state.ll_fse_table_record.symbol] + state.read_bits as u32) as u64, + read_bits: u16:0, + read_bits_length: u7:0, + read_bits_needed: state.ll_fse_table_record.num_of_bits as u7, + ..state + } + } + } else { state } + }, + FseDecoderFSM::UPDATE_LL_STATE => { + trace_fmt!("Values LL: {:#x} ML: {:#x} OF: {:#x}", state.ll, state.ml, state.of); + if ((state.read_bits_needed == state.read_bits_length) && !state.sent_buf_ctrl) { + FseDecoderState { + fsm: FseDecoderFSM::UPDATE_ML_STATE, + ll_state: state.ll_fse_table_record.base + state.read_bits, + read_bits: u16:0, + read_bits_length: u7:0, + read_bits_needed: state.ml_fse_table_record.num_of_bits as u7, + ..state + } + } else { state } + }, + FseDecoderFSM::UPDATE_ML_STATE => { + if ((state.read_bits_needed == state.read_bits_length) && !state.sent_buf_ctrl) { + FseDecoderState { + fsm: FseDecoderFSM::UPDATE_OF_STATE, + ml_state: state.ml_fse_table_record.base + state.read_bits, + read_bits: u16:0, + read_bits_length: u7:0, + read_bits_needed: state.of_fse_table_record.num_of_bits as u7, + ..state + } + } else { state } + }, + FseDecoderFSM::UPDATE_OF_STATE => { + if ((state.read_bits_needed == state.read_bits_length) && !state.sent_buf_ctrl) { + FseDecoderState { + fsm: FseDecoderFSM::SEND_COMMAND_LITERAL, + of_state: state.of_fse_table_record.base + state.read_bits, + read_bits: u16:0, + read_bits_length: u7:0, + read_bits_needed: u7:0, + ..state + } + } else { state } + }, + FseDecoderFSM::SEND_COMMAND_LITERAL => { + trace_fmt!("LITERALS_COUNT: {:#x}/{:#x}", literals_count, state.ctrl.literals_count); + FseDecoderState { + fsm: FseDecoderFSM::SEND_COMMAND_SEQUENCE, + literals_count, ..state + } + }, + FseDecoderFSM::SEND_COMMAND_SEQUENCE => { + if (state.sequences_count == u24:1) { + if state.literals_count < state.ctrl.literals_count { + trace_fmt!("Going to LEFTOVER"); + FseDecoderState { + fsm: FseDecoderFSM::SEND_LEFTOVER_LITERALS_REQ, + sequences_count: u24:0, + ..state + } + } else if state.literals_count == state.ctrl.literals_count { + trace_fmt!("Going to FINISH"); + FseDecoderState { + fsm: FseDecoderFSM::SEND_FINISH, + sequences_count: u24:0, + ..state + } + } else { + fail!("too_many_literals", zero!()) + } + } else { + FseDecoderState { + fsm: FseDecoderFSM::SEND_RAM_RD_REQ, + sequences_count: state.sequences_count - u24:1, + ..state + } + } + }, + FseDecoderFSM::SEND_LEFTOVER_LITERALS_REQ => { + FseDecoderState { + fsm:FseDecoderFSM::SEND_FINISH, + ..zero!() + } + }, + + FseDecoderFSM::SEND_FINISH => { + FseDecoderState { + fsm:FseDecoderFSM::RECV_CTRL, + ..zero!() + } + }, + _ => { + fail!("impossible_case", state) + }, + } + } +} + +const INST_RAM_SIZE = common::FSE_MAX_SYMBOLS; +const INST_RAM_ADDR_W = std::clog2(INST_RAM_SIZE); +const INST_RAM_DATA_W = u32:32; +const INST_RAM_WORD_PARTITION_SIZE = INST_RAM_DATA_W / u32:3; +const INST_RAM_NUM_PARTITIONS = ram::num_partitions( + INST_RAM_WORD_PARTITION_SIZE, INST_RAM_DATA_W); +const INST_AXI_DATA_W = u32:64; +const INST_REFILLING_SB_DATA_W = INST_AXI_DATA_W; +const INST_REFILLING_SB_LENGTH_W = refilling_shift_buffer::length_width(INST_REFILLING_SB_DATA_W); + +pub proc FseDecoderInst { + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; + + type RefillingSBCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + type RefillingSBOutput = refilling_shift_buffer::RefillingShiftBufferOutput; + + config ( + ctrl_r: chan in, + finish_s: chan out, + rsb_ctrl_s: chan out, + rsb_data_r: chan in, + command_s: chan out, + ll_fse_rd_req_s: chan out, + ll_fse_rd_resp_r: chan in, + ml_fse_rd_req_s: chan out, + ml_fse_rd_resp_r: chan in, + of_fse_rd_req_s: chan out, + of_fse_rd_resp_r: chan in, + ) { + spawn FseDecoder< + INST_RAM_DATA_W, INST_RAM_ADDR_W, INST_RAM_NUM_PARTITIONS, + INST_AXI_DATA_W, + >( + ctrl_r, finish_s, + rsb_ctrl_s, rsb_data_r, + command_s, + ll_fse_rd_req_s, ll_fse_rd_resp_r, + ml_fse_rd_req_s, ml_fse_rd_resp_r, + of_fse_rd_req_s, of_fse_rd_resp_r, + ); + } + + init { () } + + next (state: ()) {} +} + +// test data was generated using decodecorpus and educational_decoder from zstd repository +// block #0 seed: 58602 +// block #1 seed: 48401 + +const TEST_OF_TABLE = u32[256][2]:[[ + u32:0x00_03_0008, u32:0x02_02_0004, u32:0x03_02_0014, u32:0x03_02_0018, u32:0x04_03_0008, u32:0x00_03_0010, u32:0x02_02_0008, u32:0x03_02_001c, + u32:0x03_01_0000, u32:0x04_03_0010, u32:0x02_02_000c, u32:0x02_02_0010, u32:0x03_01_0002, u32:0x04_03_0018, u32:0x00_03_0018, u32:0x02_02_0014, + u32:0x03_01_0004, u32:0x03_01_0006, u32:0x04_02_0000, u32:0x02_02_0018, u32:0x02_02_001c, u32:0x03_01_0008, u32:0x03_01_000a, u32:0x00_02_0000, + u32:0x02_01_0000, u32:0x03_01_000c, u32:0x03_01_000e, u32:0x04_02_0004, u32:0x00_02_0004, u32:0x02_01_0002, u32:0x03_01_0010, u32:0x03_01_0012, + u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0031, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x31_51_0100, u32:0x00_00_0101, u32:0x00_00_0003, u32:0x00_00_0101, u32:0x00_00_0301, u32:0x00_00_0101, u32:0x00_00_0301, u32:0x00_00_0100, + u32:0x03_08_0101, u32:0x02_00_0103, u32:0x02_04_0101, u32:0x02_00_0001, u32:0x03_14_0101, u32:0x03_00_0301, u32:0x02_18_0100, u32:0x02_00_0101, + u32:0x01_08_0000, u32:0x03_00_0000, u32:0x02_10_0000, u32:0x02_00_0000, u32:0x01_08_0031, u32:0x03_00_0000, u32:0x03_1c_0000, u32:0x02_00_0000, + u32:0x01_00_0103, u32:0x01_00_0101, u32:0x02_10_0303, u32:0x02_00_0101, u32:0x02_0c_0301, u32:0x01_00_0101, u32:0x01_10_0301, u32:0x02_00_0103, + u32:0x01_02_0000, u32:0x01_00_0002, u32:0x01_18_0000, u32:0x02_00_0200, u32:0x02_18_0000, u32:0x01_00_0200, u32:0x01_14_0002, u32:0x01_00_0000, + u32:0x00_04_0000, u32:0x00_00_0000, u32:0x00_06_0000, u32:0x00_00_0000, u32:0x00_00_0051, u32:0x00_00_0000, u32:0x00_18_0000, u32:0x00_00_0000, + u32:0x51_1c_0008, u32:0x00_00_000c, u32:0x00_08_000e, u32:0x00_00_0010, u32:0x00_0a_0008, u32:0x00_00_0010, u32:0x00_00_0012, u32:0x00_00_0014, + u32:0x08_00_0016, u32:0x00_00_0010, u32:0x04_0c_0018, u32:0x00_00_001a, u32:0x14_0e_001c, u32:0x00_00_0018, u32:0x18_04_0018, u32:0x00_00_001e, + u32:0x08_04_0000, u32:0x00_00_0001, u32:0x10_02_0000, u32:0x00_00_0002, u32:0x08_10_0003, u32:0x00_00_0004, u32:0x1c_12_0005, u32:0x00_00_0000, + u32:0x00_00_0006, u32:0x00_00_0007, u32:0x10_00_0008, u32:0x00_00_0004, u32:0x0c_00_0004, u32:0x00_00_0009, u32:0x10_00_000a, u32:0x00_00_000b, + u32:0x02_31_0000, u32:0x00_00_0000, u32:0x18_00_0000, u32:0x00_00_0000, u32:0x18_00_0411, u32:0x00_00_0000, u32:0x14_00_0000, u32:0x00_00_0000, + u32:0x04_00_3230, u32:0x00_01_3020, u32:0x06_01_2030, u32:0x00_01_3233, u32:0x00_03_3033, u32:0x00_00_2020, u32:0x18_01_3030, u32:0x00_01_3020, + u32:0x1c_01_2031, u32:0x00_03_3033, u32:0x08_01_3333, u32:0x00_01_2020, u32:0x0a_01_3830, u32:0x00_03_3020, u32:0x00_00_2031, u32:0x00_01_3333, + u32:0x00_01_3333, u32:0x00_01_2020, u32:0x0c_03_3030, u32:0x00_01_3020, u32:0x0e_01_2031, u32:0x00_01_3033, u32:0x04_01_3032, u32:0x00_00_2020, + u32:0x04_01_6530, u32:0x00_01_3020, u32:0x02_01_2031, u32:0x00_03_3032, u32:0x10_00_3133, u32:0x00_01_2020, u32:0x12_01_3030, u32:0x00_01_3020, + u32:0x00_00_2031, u32:0x00_00_3032, u32:0x00_00_3032, u32:0x00_00_2020, u32:0x00_00_3231, u32:0x00_00_3020, u32:0x00_00_2031, u32:0x00_00_3032, + u32:0x31_31_3133, u32:0x00_00_2020, u32:0x00_00_3030, u32:0x00_00_3020, u32:0x00_00_2030, u32:0x00_00_3033, u32:0x00_00_3233, u32:0x00_00_2020, + u32:0x00_03_000a, u32:0x01_01_0000, u32:0x01_01_0000, u32:0x01_01_0000, u32:0x03_03_0000, u32:0x00_03_0000, u32:0x01_01_0000, u32:0x01_01_0000, + u32:0x01_01_0000, u32:0x03_03_0000, u32:0x01_01_0000, u32:0x01_01_0000, u32:0x01_01_0000, u32:0x03_03_0000, u32:0x00_03_0000, u32:0x01_01_0000, + u32:0x01_00_0000, u32:0x01_00_0000, u32:0x03_02_0000, u32:0x01_00_0000, u32:0x01_00_0000, u32:0x01_00_0000, u32:0x01_00_0000, u32:0x00_02_0000, + u32:0x01_00_0000, u32:0x01_00_0000, u32:0x01_00_0000, u32:0x03_02_0000, u32:0x00_02_0000, u32:0x01_00_0000, u32:0x01_00_0000, u32:0x01_00_0000, + u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x31_51_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x03_08_0000, u32:0x01_00_0000, u32:0x01_0c_0000, u32:0x01_00_0000, u32:0x03_0e_0000, u32:0x03_00_0000, u32:0x01_10_0000, u32:0x01_00_0000, + u32:0x01_08_0000, u32:0x03_00_0000, u32:0x01_10_0000, u32:0x01_00_0000, u32:0x01_12_0000, u32:0x03_00_0000, u32:0x03_14_0000, u32:0x01_00_0000, + u32:0x00_16_0000, u32:0x00_00_0000, u32:0x02_10_0000, u32:0x00_00_0000, u32:0x00_18_0000, u32:0x00_00_0000, u32:0x00_1a_0000, u32:0x02_00_0000, + u32:0x00_1c_0000, u32:0x00_00_0000, u32:0x00_18_0000, u32:0x02_00_0000, u32:0x02_18_0000, u32:0x00_00_0000, u32:0x00_1e_0000, u32:0x00_00_0000, +],[ + u32:0x00_05_0000, u32:0x06_04_0000, u32:0x09_05_0000, u32:0x0f_05_0000, u32:0x15_05_0000, u32:0x03_05_0000, u32:0x07_04_0000, u32:0x0c_05_0000, + u32:0x12_05_0000, u32:0x17_05_0000, u32:0x05_05_0000, u32:0x08_04_0000, u32:0x0e_05_0000, u32:0x14_05_0000, u32:0x02_05_0000, u32:0x07_04_0010, + u32:0x0b_05_0000, u32:0x11_05_0000, u32:0x16_05_0000, u32:0x04_05_0000, u32:0x08_04_0010, u32:0x0d_05_0000, u32:0x13_05_0000, u32:0x01_05_0000, + u32:0x06_04_0010, u32:0x0a_05_0000, u32:0x10_05_0000, u32:0x1c_05_0000, u32:0x1b_05_0000, u32:0x1a_05_0000, u32:0x19_05_0000, u32:0x18_05_0000, + u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0051, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x31_51_0100, u32:0x00_00_0302, u32:0x00_00_0605, u32:0x00_00_0a08, u32:0x00_00_100d, u32:0x00_00_1613, u32:0x00_00_1c19, u32:0x00_00_211f, + u32:0x05_00_2523, u32:0x04_00_2927, u32:0x05_00_2d2b, u32:0x05_00_0201, u32:0x05_00_0403, u32:0x05_00_0706, u32:0x04_00_0c09, u32:0x05_00_120f, + u32:0x05_00_1815, u32:0x05_00_1e1b, u32:0x05_00_2220, u32:0x04_00_2624, u32:0x05_00_2a28, u32:0x05_00_012c, u32:0x05_00_0201, u32:0x04_00_0504, + u32:0x05_00_0807, u32:0x05_00_0e0b, u32:0x05_00_1411, u32:0x05_00_1a17, u32:0x04_00_341d, u32:0x05_00_3233, u32:0x05_00_3031, u32:0x05_00_2e2f, + u32:0x04_00_0000, u32:0x05_00_0000, u32:0x05_00_0000, u32:0x05_00_0000, u32:0x05_00_0051, u32:0x05_00_0000, u32:0x05_10_0000, u32:0x05_00_0000, + u32:0x00_00_0406, u32:0x00_00_0505, u32:0x00_00_0505, u32:0x00_00_0605, u32:0x00_00_0606, u32:0x00_00_0606, u32:0x00_00_0606, u32:0x00_00_0606, + u32:0x51_10_0606, u32:0x00_00_0606, u32:0x00_00_0606, u32:0x00_00_0404, u32:0x00_00_0505, u32:0x00_00_0505, u32:0x00_00_0606, u32:0x00_00_0606, + u32:0x00_10_0606, u32:0x00_00_0606, u32:0x00_00_0606, u32:0x00_00_0606, u32:0x00_00_0606, u32:0x00_00_0406, u32:0x00_00_0404, u32:0x00_00_0505, + u32:0x00_00_0505, u32:0x00_00_0606, u32:0x00_00_0606, u32:0x00_00_0606, u32:0x00_00_0606, u32:0x00_00_0606, u32:0x00_00_0606, u32:0x00_00_0606, + u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0091, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x00_51_0000, u32:0x00_00_0000, u32:0x00_00_0020, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x10_00_0000, u32:0x00_00_0000, + u32:0x00_00_0000, u32:0x00_01_0000, u32:0x00_02_0000, u32:0x00_03_0000, u32:0x00_05_0000, u32:0x00_06_0000, u32:0x00_08_0000, u32:0x00_0a_0000, + u32:0x10_0d_0000, u32:0x00_10_0000, u32:0x00_13_0000, u32:0x00_16_0000, u32:0x00_19_0000, u32:0x00_1c_0000, u32:0x00_1f_0010, u32:0x00_21_0000, + u32:0x10_23_0020, u32:0x00_25_0000, u32:0x00_27_0020, u32:0x00_29_0000, u32:0x00_2b_0000, u32:0x00_2d_0000, u32:0x00_01_0000, u32:0x00_02_0000, + u32:0x00_03_0000, u32:0x00_04_0000, u32:0x00_06_0000, u32:0x00_07_0000, u32:0x00_09_0000, u32:0x00_0c_0000, u32:0x00_0f_0000, u32:0x00_12_0000, + u32:0x00_15_0000, u32:0x00_18_0000, u32:0x00_1b_0000, u32:0x00_1e_0020, u32:0x00_20_0030, u32:0x00_22_0010, u32:0x00_24_0020, u32:0x00_26_0020, + u32:0x51_28_0020, u32:0x00_2a_0020, u32:0x00_2c_0000, u32:0x00_01_0000, u32:0x00_01_0000, u32:0x00_02_0000, u32:0x00_04_0000, u32:0x00_05_0000, + u32:0x00_07_0000, u32:0x01_08_0000, u32:0x02_0b_0000, u32:0x03_0e_0000, u32:0x05_11_0000, u32:0x06_14_0000, u32:0x08_17_0000, u32:0x0a_1a_0000, + u32:0x0d_1d_0000, u32:0x10_34_0000, u32:0x13_33_0000, u32:0x16_32_0000, u32:0x19_31_0411, u32:0x1c_30_0000, u32:0x1f_2f_0000, u32:0x21_2e_0000, + u32:0x23_00_6430, u32:0x25_00_3020, u32:0x27_00_2030, u32:0x29_00_3436, u32:0x2b_00_3033, u32:0x2d_00_2020, u32:0x01_00_3532, u32:0x02_00_3020, + u32:0x03_51_2030, u32:0x04_00_3033, u32:0x06_00_3333, u32:0x07_00_2020, u32:0x09_00_3630, u32:0x0c_00_3020, u32:0x0f_00_2030, u32:0x12_00_3333, + u32:0x15_06_3333, u32:0x18_04_2020, u32:0x1b_05_3730, u32:0x1e_05_3020, u32:0x20_05_2035, u32:0x22_05_3033, u32:0x24_05_3032, u32:0x26_06_2020, + u32:0x28_06_3032, u32:0x2a_06_3020, u32:0x2c_06_2035, u32:0x01_06_3032, u32:0x01_06_3533, u32:0x02_06_2020, u32:0x04_06_3230, u32:0x05_06_3020, + u32:0x07_06_2036, u32:0x08_06_3032, u32:0x0b_06_3032, u32:0x0e_06_2020, u32:0x11_06_3430, u32:0x14_06_3020, u32:0x17_04_2036, u32:0x1a_04_3032, + u32:0x1d_05_3633, u32:0x34_05_2020, u32:0x33_05_6131, u32:0x32_05_3020, u32:0x31_06_2034, u32:0x30_06_3033, u32:0x2f_06_3233, u32:0x2e_06_2020, + u32:0x00_06_000a, u32:0x00_06_0000, u32:0x00_06_0000, u32:0x00_06_0000, u32:0x00_06_0000, u32:0x00_06_0000, u32:0x00_06_0000, u32:0x00_06_0000, + u32:0x51_06_0000, u32:0x00_06_0000, u32:0x00_06_0000, u32:0x00_04_0000, u32:0x00_04_0000, u32:0x00_04_0000, u32:0x00_05_0000, u32:0x00_05_0000, +]]; + +const TEST_ML_TABLE = u32[256][2]:[[ + u32:0x00_03_0008, u32:0x01_01_000c, u32:0x01_01_000e, u32:0x01_01_0010, u32:0x03_03_0008, u32:0x00_03_0010, u32:0x01_01_0012, u32:0x01_01_0014, + u32:0x01_01_0016, u32:0x03_03_0010, u32:0x01_01_0018, u32:0x01_01_001a, u32:0x01_01_001c, u32:0x03_03_0018, u32:0x00_03_0018, u32:0x01_01_001e, + u32:0x01_00_0000, u32:0x01_00_0001, u32:0x03_02_0000, u32:0x01_00_0002, u32:0x01_00_0003, u32:0x01_00_0004, u32:0x01_00_0005, u32:0x00_02_0000, + u32:0x01_00_0006, u32:0x01_00_0007, u32:0x01_00_0008, u32:0x03_02_0004, u32:0x00_02_0004, u32:0x01_00_0009, u32:0x01_00_000a, u32:0x01_00_000b, + u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0411, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x31_51_3030, u32:0x00_00_3520, u32:0x00_00_2031, u32:0x00_00_3033, u32:0x00_00_3033, u32:0x00_00_2020, u32:0x00_00_3030, u32:0x00_00_3020, + u32:0x03_08_2030, u32:0x01_00_3533, u32:0x01_0c_3333, u32:0x01_00_2020, u32:0x03_0e_3130, u32:0x03_00_3020, u32:0x01_10_2063, u32:0x01_00_3333, + u32:0x01_08_3333, u32:0x03_00_2020, u32:0x01_10_3130, u32:0x01_00_3020, u32:0x01_12_2030, u32:0x03_00_3033, u32:0x03_14_3032, u32:0x01_00_2020, + u32:0x00_16_3130, u32:0x00_00_3120, u32:0x02_10_2032, u32:0x00_00_3032, u32:0x00_18_3033, u32:0x00_00_2020, u32:0x00_1a_3030, u32:0x02_00_3020, + u32:0x00_1c_2030, u32:0x00_00_3032, u32:0x00_18_3032, u32:0x02_00_2020, u32:0x02_18_3030, u32:0x00_00_3120, u32:0x00_1e_2061, u32:0x00_00_3032, + u32:0x00_00_3136, u32:0x00_00_2020, u32:0x00_01_3030, u32:0x00_00_3020, u32:0x00_00_2030, u32:0x00_00_3033, u32:0x00_02_3233, u32:0x00_00_2020, + u32:0x51_03_000a, u32:0x00_00_0000, u32:0x00_04_0000, u32:0x00_00_0000, u32:0x00_05_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x08_06_0000, u32:0x00_00_0000, u32:0x0c_07_0000, u32:0x00_00_0000, u32:0x0e_08_0000, u32:0x00_00_0000, u32:0x10_04_0000, u32:0x00_00_0000, + u32:0x08_04_0000, u32:0x00_00_0000, u32:0x10_09_0000, u32:0x00_00_0000, u32:0x12_0a_0000, u32:0x00_00_0000, u32:0x14_0b_0000, u32:0x00_00_0000, + u32:0x16_00_0000, u32:0x00_00_0000, u32:0x10_00_0000, u32:0x00_00_0000, u32:0x18_00_0000, u32:0x00_00_0000, u32:0x1a_00_0000, u32:0x00_00_0000, + u32:0x1c_11_0000, u32:0x00_04_0000, u32:0x18_00_0000, u32:0x00_00_0000, u32:0x18_00_0000, u32:0x00_00_0000, u32:0x1e_00_0000, u32:0x00_00_0000, + u32:0x00_31_0000, u32:0x00_30_0000, u32:0x01_20_0000, u32:0x00_33_0000, u32:0x00_31_0000, u32:0x00_20_0000, u32:0x02_30_0000, u32:0x00_30_0000, + u32:0x03_30_0000, u32:0x00_30_0000, u32:0x04_20_0000, u32:0x00_20_0000, u32:0x05_30_0000, u32:0x00_30_0000, u32:0x00_20_0000, u32:0x00_33_0000, + u32:0x06_30_0000, u32:0x00_20_0000, u32:0x07_30_0000, u32:0x00_30_0000, u32:0x08_30_0000, u32:0x00_30_0000, u32:0x04_20_0000, u32:0x00_20_0000, + u32:0x04_30_0000, u32:0x00_37_0000, u32:0x09_20_0000, u32:0x00_32_0000, u32:0x0a_30_0000, u32:0x00_20_0000, u32:0x0b_30_0000, u32:0x00_30_0000, + u32:0x00_30_0000, u32:0x00_30_0000, u32:0x00_20_0000, u32:0x00_20_0000, u32:0x00_30_0000, u32:0x00_30_0000, u32:0x00_20_0000, u32:0x00_32_0000, + u32:0x11_30_0000, u32:0x04_20_0000, u32:0x00_30_0000, u32:0x00_30_0000, u32:0x00_30_0000, u32:0x00_30_0000, u32:0x00_20_0000, u32:0x00_20_0000, + u32:0x31_30_0000, u32:0x31_30_0000, u32:0x20_20_0000, u32:0x33_33_0000, u32:0x30_30_0000, u32:0x20_20_0000, u32:0x30_30_0000, u32:0x30_30_0000, + u32:0x30_30_0000, u32:0x30_30_0000, u32:0x20_20_0000, u32:0x20_20_0000, u32:0x33_32_0000, u32:0x30_30_0000, u32:0x20_20_0000, u32:0x33_33_0000, + u32:0x30_30_0000, u32:0x20_20_0000, u32:0x30_30_0000, u32:0x30_30_0000, u32:0x30_30_0000, u32:0x30_30_0000, u32:0x20_20_0000, u32:0x20_20_0000, + u32:0x33_32_0000, u32:0x30_30_0000, u32:0x20_20_0000, u32:0x32_32_0000, u32:0x30_30_0000, u32:0x20_20_0000, u32:0x30_30_0000, u32:0x30_30_0000, + u32:0x30_30_0000, u32:0x30_30_0000, u32:0x20_20_0000, u32:0x20_20_0000, u32:0x32_33_0000, u32:0x30_30_0000, u32:0x20_20_0000, u32:0x32_33_0000, + u32:0x30_33_0000, u32:0x20_20_0000, u32:0x30_30_0000, u32:0x30_30_0000, u32:0x30_30_0000, u32:0x30_30_0000, u32:0x20_20_0000, u32:0x20_20_0000, + u32:0x33_0a_0000, u32:0x30_00_0000, u32:0x20_00_0000, u32:0x33_00_0000, u32:0x30_00_0000, u32:0x20_00_0000, u32:0x30_00_0000, u32:0x30_00_0000, + u32:0x30_00_0000, u32:0x30_00_0000, u32:0x20_00_0000, u32:0x20_00_0000, u32:0x32_00_0000, u32:0x30_00_0000, u32:0x20_00_0000, u32:0x30_00_0000, + u32:0x30_00_0000, u32:0x20_00_0000, u32:0x30_00_0000, u32:0x30_00_0000, u32:0x30_00_0000, u32:0x30_00_0000, u32:0x20_00_0000, u32:0x20_00_0000, + u32:0x32_00_0000, u32:0x30_00_0000, u32:0x20_00_0000, u32:0x30_00_0000, u32:0x30_00_0000, u32:0x20_00_0000, u32:0x30_00_0000, u32:0x30_00_0000, +],[ + u32:0x00_06_0000, u32:0x01_04_0000, u32:0x02_05_0020, u32:0x03_05_0000, u32:0x05_05_0000, u32:0x06_05_0000, u32:0x08_05_0000, u32:0x0a_06_0000, + u32:0x0d_06_0000, u32:0x10_06_0000, u32:0x13_06_0000, u32:0x16_06_0000, u32:0x19_06_0000, u32:0x1c_06_0000, u32:0x1f_06_0000, u32:0x21_06_0000, + u32:0x23_06_0000, u32:0x25_06_0000, u32:0x27_06_0000, u32:0x29_06_0000, u32:0x2b_06_0000, u32:0x2d_06_0000, u32:0x01_04_0010, u32:0x02_04_0000, + u32:0x03_05_0020, u32:0x04_05_0000, u32:0x06_05_0020, u32:0x07_05_0000, u32:0x09_06_0000, u32:0x0c_06_0000, u32:0x0f_06_0000, u32:0x12_06_0000, + u32:0x15_06_0000, u32:0x18_06_0000, u32:0x1b_06_0000, u32:0x1e_06_0000, u32:0x20_06_0000, u32:0x22_06_0000, u32:0x24_06_0000, u32:0x26_06_0000, + u32:0x28_06_0000, u32:0x2a_06_0000, u32:0x2c_06_0000, u32:0x01_04_0020, u32:0x01_04_0030, u32:0x02_04_0010, u32:0x04_05_0020, u32:0x05_05_0020, + u32:0x07_05_0020, u32:0x08_05_0020, u32:0x0b_06_0000, u32:0x0e_06_0000, u32:0x11_06_0000, u32:0x14_06_0000, u32:0x17_06_0000, u32:0x1a_06_0000, + u32:0x1d_06_0000, u32:0x34_06_0000, u32:0x33_06_0000, u32:0x32_06_0000, u32:0x31_06_0000, u32:0x30_06_0000, u32:0x2f_06_0000, u32:0x2e_06_0000, + u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0411, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x51_91_3030, u32:0x00_00_3920, u32:0x00_00_2031, u32:0x00_00_3033, u32:0x00_00_3033, u32:0x00_00_2020, u32:0x00_00_3030, u32:0x00_00_3020, + u32:0x06_00_2030, u32:0x04_00_3933, u32:0x05_00_3333, u32:0x05_00_2020, u32:0x05_20_3530, u32:0x05_00_3020, u32:0x05_00_2030, u32:0x06_00_3333, + u32:0x06_00_3333, u32:0x06_00_2020, u32:0x06_00_3530, u32:0x06_00_3020, u32:0x06_00_2030, u32:0x06_00_3033, u32:0x06_00_3032, u32:0x06_00_2020, + u32:0x06_00_3630, u32:0x06_00_3020, u32:0x06_00_2030, u32:0x06_00_3032, u32:0x06_00_3033, u32:0x06_00_2020, u32:0x04_00_3630, u32:0x04_00_3020, + u32:0x05_00_2030, u32:0x05_00_3032, u32:0x05_00_3032, u32:0x05_00_2020, u32:0x06_00_3430, u32:0x06_00_3020, u32:0x06_00_2030, u32:0x06_00_3032, + u32:0x06_00_3033, u32:0x06_00_2020, u32:0x06_00_3630, u32:0x06_00_3020, u32:0x06_00_2030, u32:0x06_00_3033, u32:0x06_00_3233, u32:0x06_00_2020, + u32:0x06_00_000a, u32:0x06_00_0000, u32:0x06_00_0000, u32:0x04_00_0000, u32:0x04_10_0000, u32:0x04_00_0000, u32:0x05_00_0000, u32:0x05_00_0000, + u32:0x05_20_0000, u32:0x05_00_0000, u32:0x06_00_0000, u32:0x06_00_0000, u32:0x06_20_0000, u32:0x06_00_0000, u32:0x06_00_0000, u32:0x06_00_0000, + u32:0x06_00_0000, u32:0x06_00_0000, u32:0x06_00_0000, u32:0x06_00_0000, u32:0x06_00_0000, u32:0x06_00_0000, u32:0x06_00_0000, u32:0x06_00_0000, + u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x91_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x20_00_0000, u32:0x00_00_0000, u32:0x00_20_0000, u32:0x00_00_0000, + u32:0x00_30_0000, u32:0x00_00_0000, u32:0x00_10_0000, u32:0x00_00_0000, u32:0x00_20_0000, u32:0x00_00_0000, u32:0x00_20_0000, u32:0x00_00_0000, + u32:0x00_20_0000, u32:0x00_00_0000, u32:0x00_20_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x10_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x20_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x20_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x00_11_0000, u32:0x00_04_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x00_30_0000, u32:0x00_30_0000, u32:0x00_20_0000, u32:0x00_33_0000, u32:0x00_30_0000, u32:0x00_20_0000, u32:0x00_30_0000, u32:0x00_30_0000, + u32:0x00_30_0000, u32:0x00_30_0000, u32:0x00_20_0000, u32:0x00_20_0000, u32:0x00_30_0000, u32:0x00_30_0000, u32:0x00_20_0000, u32:0x00_33_0000, + u32:0x00_30_0000, u32:0x00_20_0000, u32:0x00_30_0000, u32:0x00_30_0000, u32:0x00_30_0000, u32:0x00_30_0000, u32:0x20_20_0000, u32:0x00_20_0000, + u32:0x30_30_0000, u32:0x00_30_0000, u32:0x10_20_0000, u32:0x00_32_0000, u32:0x20_30_0000, u32:0x00_20_0000, u32:0x20_30_0000, u32:0x00_30_0000, +]]; + +const TEST_LL_TABLE = u32[256][2]:[[ + u32:0x00_01_000e, u32:0x00_01_0010, u32:0x00_01_0012, u32:0x00_01_0014, u32:0x01_02_0004, u32:0x00_01_0016, u32:0x00_01_0018, u32:0x00_01_001a, + u32:0x01_02_0008, u32:0x01_02_000c, u32:0x00_01_001c, u32:0x00_01_001e, u32:0x00_00_0000, u32:0x01_02_0010, u32:0x00_00_0001, u32:0x00_00_0002, + u32:0x00_00_0003, u32:0x01_02_0014, u32:0x01_02_0018, u32:0x00_00_0004, u32:0x00_00_0005, u32:0x00_00_0006, u32:0x01_02_001c, u32:0x00_00_0007, + u32:0x00_00_0008, u32:0x00_00_0009, u32:0x00_00_000a, u32:0x01_01_0000, u32:0x00_00_000b, u32:0x00_00_000c, u32:0x00_00_000d, u32:0x01_01_0002, + u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0031, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x31_51_0200, u32:0x00_00_0303, u32:0x00_00_0004, u32:0x00_00_0302, u32:0x00_00_0403, u32:0x00_00_0202, u32:0x00_00_0403, u32:0x00_00_0200, + u32:0x01_0e_0303, u32:0x01_00_0204, u32:0x01_10_0302, u32:0x01_00_0003, u32:0x02_12_0302, u32:0x01_00_0403, u32:0x01_14_0200, u32:0x01_00_0303, + u32:0x02_04_0000, u32:0x02_00_0000, u32:0x01_16_0000, u32:0x01_00_0000, u32:0x00_18_0031, u32:0x02_00_0000, u32:0x00_1a_0000, u32:0x00_00_0000, + u32:0x00_08_0203, u32:0x02_00_0202, u32:0x02_0c_0303, u32:0x00_00_0202, u32:0x00_1c_0301, u32:0x00_00_0202, u32:0x02_1e_0301, u32:0x00_00_0203, + u32:0x00_00_0101, u32:0x00_00_0202, u32:0x00_10_0102, u32:0x01_00_0201, u32:0x00_01_0101, u32:0x00_00_0201, u32:0x00_02_0102, u32:0x01_00_0101, + u32:0x00_03_0000, u32:0x00_00_0000, u32:0x00_14_0000, u32:0x00_00_0000, u32:0x00_18_0051, u32:0x00_00_0000, u32:0x00_04_0000, u32:0x00_00_0000, + u32:0x51_05_0008, u32:0x00_00_0004, u32:0x00_06_0014, u32:0x00_00_0018, u32:0x00_1c_0008, u32:0x00_00_0010, u32:0x00_07_0008, u32:0x00_00_001c, + u32:0x0e_08_0000, u32:0x00_00_0010, u32:0x10_09_000c, u32:0x00_00_0010, u32:0x12_0a_0002, u32:0x00_00_0018, u32:0x14_00_0018, u32:0x00_00_0014, + u32:0x04_0b_0004, u32:0x00_00_0006, u32:0x16_0c_0000, u32:0x00_00_0018, u32:0x18_0d_001c, u32:0x00_00_0008, u32:0x1a_02_000a, u32:0x00_00_0000, + u32:0x08_00_0000, u32:0x00_00_000c, u32:0x0c_00_000e, u32:0x00_00_0004, u32:0x1c_00_0004, u32:0x00_00_0002, u32:0x1e_00_0010, u32:0x00_00_0012, + u32:0x00_31_0000, u32:0x00_00_0000, u32:0x10_00_0000, u32:0x00_00_0000, u32:0x01_00_0031, u32:0x00_00_0000, u32:0x02_00_0000, u32:0x00_00_0000, + u32:0x03_00_0100, u32:0x00_02_0101, u32:0x14_03_0003, u32:0x00_03_0101, u32:0x18_04_0301, u32:0x00_00_0101, u32:0x04_02_0301, u32:0x00_03_0100, + u32:0x05_03_0101, u32:0x00_04_0103, u32:0x06_02_0101, u32:0x00_02_0001, u32:0x1c_03_0101, u32:0x00_04_0301, u32:0x07_00_0100, u32:0x00_02_0101, + u32:0x08_03_0000, u32:0x00_03_0000, u32:0x09_04_0000, u32:0x00_02_0000, u32:0x0a_02_0031, u32:0x00_03_0000, u32:0x00_03_0000, u32:0x00_00_0000, + u32:0x0b_02_0103, u32:0x00_03_0101, u32:0x0c_03_0303, u32:0x00_04_0101, u32:0x0d_00_0301, u32:0x00_02_0101, u32:0x02_03_0301, u32:0x00_03_0103, + u32:0x00_00_0000, u32:0x00_00_0002, u32:0x00_00_0000, u32:0x00_00_0200, u32:0x00_00_0000, u32:0x00_00_0200, u32:0x00_00_0002, u32:0x00_00_0000, + u32:0x31_31_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0051, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x00_03_0008, u32:0x02_02_000c, u32:0x03_02_000e, u32:0x03_02_0010, u32:0x04_03_0008, u32:0x00_03_0010, u32:0x02_02_0012, u32:0x03_02_0014, + u32:0x03_01_0016, u32:0x04_03_0010, u32:0x02_02_0018, u32:0x02_02_001a, u32:0x03_01_001c, u32:0x04_03_0018, u32:0x00_03_0018, u32:0x02_02_001e, + u32:0x03_01_0000, u32:0x03_01_0001, u32:0x04_02_0000, u32:0x02_02_0002, u32:0x02_02_0003, u32:0x03_01_0004, u32:0x03_01_0005, u32:0x00_02_0000, + u32:0x02_01_0006, u32:0x03_01_0007, u32:0x03_01_0008, u32:0x04_02_0004, u32:0x00_02_0004, u32:0x02_01_0009, u32:0x03_01_000a, u32:0x03_01_000b, + u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0411, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x31_51_3030, u32:0x00_00_3520, u32:0x00_00_2031, u32:0x00_00_3033, u32:0x00_00_3033, u32:0x00_00_2020, u32:0x00_00_3030, u32:0x00_00_3020, + u32:0x03_08_2030, u32:0x02_00_3533, u32:0x02_04_3333, u32:0x02_00_2020, u32:0x03_14_3230, u32:0x03_00_3020, u32:0x02_18_2034, u32:0x02_00_3333, + u32:0x01_08_3333, u32:0x03_00_2020, u32:0x02_10_3230, u32:0x02_00_3020, u32:0x01_08_2030, u32:0x03_00_3033, u32:0x03_1c_3032, u32:0x02_00_2020, + u32:0x01_00_3130, u32:0x01_00_3020, u32:0x02_10_2038, u32:0x02_00_3032, u32:0x02_0c_3033, u32:0x01_00_2020, u32:0x01_10_3130, u32:0x02_00_3020, + u32:0x01_02_2030, u32:0x01_00_3032, u32:0x01_18_3032, u32:0x02_00_2020, u32:0x02_18_3130, u32:0x01_00_3120, u32:0x01_14_2030, u32:0x01_00_3032, +],[ + u32:0x00_02_0010, u32:0x00_02_0014, u32:0x01_03_0008, u32:0x03_03_0008, u32:0x0d_03_0008, u32:0x00_02_0018, u32:0x00_02_001c, u32:0x03_03_0010, + u32:0x05_03_0008, u32:0x0d_03_0010, u32:0x00_01_0000, u32:0x01_03_0010, u32:0x03_03_0018, u32:0x0d_03_0018, u32:0x00_01_0002, u32:0x00_01_0004, + u32:0x01_03_0018, u32:0x05_03_0010, u32:0x0d_02_0000, u32:0x00_01_0006, u32:0x01_02_0000, u32:0x03_02_0000, u32:0x05_03_0018, u32:0x00_01_0008, + u32:0x00_01_000a, u32:0x01_02_0004, u32:0x05_02_0000, u32:0x0d_02_0004, u32:0x00_01_000c, u32:0x00_01_000e, u32:0x03_02_0004, u32:0x05_02_0004, + u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0031, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x31_51_0600, u32:0x00_00_0f09, u32:0x00_00_0315, u32:0x00_00_0c07, u32:0x00_00_1712, u32:0x00_00_0805, u32:0x00_00_140e, u32:0x00_00_0702, + u32:0x02_10_110b, u32:0x02_00_0416, u32:0x03_14_0d08, u32:0x03_00_0113, u32:0x03_08_0a06, u32:0x02_00_1c10, u32:0x02_08_1a1b, u32:0x03_00_1819, + u32:0x03_08_0000, u32:0x03_00_0000, u32:0x01_18_0000, u32:0x03_00_0000, u32:0x03_1c_0031, u32:0x03_00_0000, u32:0x01_10_0000, u32:0x01_00_0000, + u32:0x03_08_0405, u32:0x03_00_0505, u32:0x02_10_0505, u32:0x01_00_0504, u32:0x02_00_0505, u32:0x02_00_0405, u32:0x03_10_0505, u32:0x01_00_0405, + u32:0x01_18_0505, u32:0x02_00_0505, u32:0x02_18_0504, u32:0x02_00_0505, u32:0x01_02_0504, u32:0x01_00_0505, u32:0x02_04_0505, u32:0x02_00_0505, + u32:0x00_18_0000, u32:0x00_00_0000, u32:0x00_10_0000, u32:0x00_00_0000, u32:0x00_00_0051, u32:0x00_00_0000, u32:0x00_06_0000, u32:0x00_00_0000, + u32:0x51_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_18_0000, u32:0x00_00_0000, u32:0x00_08_0000, u32:0x00_00_0000, + u32:0x10_0a_0000, u32:0x00_00_0000, u32:0x14_04_0000, u32:0x00_00_0000, u32:0x08_00_0000, u32:0x00_00_0000, u32:0x08_04_0000, u32:0x00_00_0010, + u32:0x08_0c_0000, u32:0x00_00_0000, u32:0x18_0e_0000, u32:0x00_00_0000, u32:0x1c_04_0010, u32:0x00_00_0000, u32:0x10_04_0000, u32:0x00_00_0000, + u32:0x08_00_0010, u32:0x00_00_0000, u32:0x10_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x10_00_0000, u32:0x00_00_0000, + u32:0x18_31_0000, u32:0x00_00_0000, u32:0x18_00_0000, u32:0x00_00_0000, u32:0x02_00_0051, u32:0x00_00_0000, u32:0x04_00_0000, u32:0x00_00_0000, + u32:0x18_00_0100, u32:0x00_06_0302, u32:0x10_09_0605, u32:0x00_0f_0a08, u32:0x00_15_100d, u32:0x00_03_1613, u32:0x06_07_1c19, u32:0x00_0c_211f, + u32:0x00_12_2523, u32:0x00_17_2927, u32:0x00_05_2d2b, u32:0x00_08_0201, u32:0x18_0e_0403, u32:0x00_14_0706, u32:0x08_02_0c09, u32:0x00_07_120f, + u32:0x0a_0b_1815, u32:0x00_11_1e1b, u32:0x04_16_2220, u32:0x00_04_2624, u32:0x00_08_2a28, u32:0x00_0d_012c, u32:0x04_13_0201, u32:0x00_01_0504, + u32:0x0c_06_0807, u32:0x00_0a_0e0b, u32:0x0e_10_1411, u32:0x00_1c_1a17, u32:0x04_1b_341d, u32:0x00_1a_3233, u32:0x04_19_3031, u32:0x00_18_2e2f, + u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0051, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x31_31_0406, u32:0x00_00_0505, u32:0x00_00_0505, u32:0x00_00_0605, u32:0x00_00_0606, u32:0x00_00_0606, u32:0x00_00_0606, u32:0x00_00_0606, + u32:0x00_05_0606, u32:0x06_04_0606, u32:0x09_05_0606, u32:0x0f_05_0404, u32:0x15_05_0505, u32:0x03_05_0505, u32:0x07_04_0606, u32:0x0c_05_0606, + u32:0x12_05_0606, u32:0x17_05_0606, u32:0x05_05_0606, u32:0x08_04_0606, u32:0x0e_05_0606, u32:0x14_05_0406, u32:0x02_05_0404, u32:0x07_04_0505, + u32:0x0b_05_0505, u32:0x11_05_0606, u32:0x16_05_0606, u32:0x04_05_0606, u32:0x08_04_0606, u32:0x0d_05_0606, u32:0x13_05_0606, u32:0x01_05_0606, + u32:0x06_04_0000, u32:0x0a_05_0000, u32:0x10_05_0000, u32:0x1c_05_0000, u32:0x1b_05_0091, u32:0x1a_05_0000, u32:0x19_05_0000, u32:0x18_05_0000, + u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0020, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x31_51_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, u32:0x00_00_0000, + u32:0x05_00_0000, u32:0x04_00_0000, u32:0x05_00_0000, u32:0x05_00_0000, u32:0x05_00_0000, u32:0x05_00_0000, u32:0x04_00_0010, u32:0x05_00_0000, + u32:0x05_00_0020, u32:0x05_00_0000, u32:0x05_00_0020, u32:0x04_00_0000, u32:0x05_00_0000, u32:0x05_00_0000, u32:0x05_00_0000, u32:0x04_00_0000, + u32:0x05_00_0000, u32:0x05_00_0000, u32:0x05_00_0000, u32:0x05_00_0000, u32:0x04_00_0000, u32:0x05_00_0000, u32:0x05_00_0000, u32:0x05_00_0000, + u32:0x04_00_0000, u32:0x05_00_0000, u32:0x05_00_0000, u32:0x05_00_0020, u32:0x05_00_0030, u32:0x05_00_0010, u32:0x05_10_0020, u32:0x05_00_0020, +]]; + +const TEST_SYNC = BlockSyncData[2]:[ + BlockSyncData {id: u32:1234, last_block: false}, + BlockSyncData {id: u32:1235, last_block: true}, +]; + +const TEST_CTRL = FseDecoderCtrl[2]:[ + FseDecoderCtrl { + sync: TEST_SYNC[0], + sequences_count: u24:8, + literals_count: u20:0, + of_acc_log: u7:5, + ll_acc_log: u7:5, + ml_acc_log: u7:5, + }, + FseDecoderCtrl { + sync: TEST_SYNC[1], + sequences_count: u24:7, + literals_count: u20:0, + of_acc_log: u7:5, + ll_acc_log: u7:5, + ml_acc_log: u7:6, + }, +]; + + +const TEST_AXI_DATA_W = u32:64; +const TEST_REFILLING_SB_DATA_W = TEST_AXI_DATA_W; +const TEST_REFILLING_SB_LENGTH_W = refilling_shift_buffer::length_width(TEST_REFILLING_SB_DATA_W); +const TEST_RAM_DATA_W = u32:32; +const TEST_RAM_SIZE = common::FSE_MAX_SYMBOLS; +const TEST_RAM_ADDR_W = std::clog2(TEST_RAM_SIZE); +const TEST_RAM_WORD_PARTITION_SIZE = TEST_RAM_DATA_W / u32:3; +const TEST_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_RAM_WORD_PARTITION_SIZE, TEST_RAM_DATA_W); + +type TestRefillingSBOutput = refilling_shift_buffer::RefillingShiftBufferOutput; + +const TEST_DATA_0 = TestRefillingSBOutput[48]:[ + // init states + TestRefillingSBOutput { error: false, data: u64:0b11111, length: u7:5}, + TestRefillingSBOutput { error: false, data: u64:0b101, length: u7:5}, + TestRefillingSBOutput { error: false, data: u64:0b10, length: u7:5}, + // symbols (seq #0) + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + // state update + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:1}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:1}, + TestRefillingSBOutput { error: false, data: u64:0b100, length: u7:3}, + // symbols (seq #1) + TestRefillingSBOutput { error: false, data: u64:0b10, length: u7:2}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + // state update + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:1}, + TestRefillingSBOutput { error: false, data: u64:0b110, length: u7:3}, + TestRefillingSBOutput { error: false, data: u64:0b10, length: u7:2}, + // symbols (seq #2) + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:3}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + // state update + TestRefillingSBOutput { error: false, data: u64:0b10, length: u7:2}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:1}, + // symbols (seq #3) + TestRefillingSBOutput { error: false, data: u64:0b11, length: u7:3}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + // state update + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:1}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:1}, + // symbols (seq #4) + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:4}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + // state update + TestRefillingSBOutput { error: false, data: u64:0b1, length: u7:1}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:3}, + // symbols (seq #5) + TestRefillingSBOutput { error: false, data: u64:0b101, length: u7:3}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + // state update + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:1}, + TestRefillingSBOutput { error: false, data: u64:0b1, length: u7:1}, + // symbols (seq #6) + TestRefillingSBOutput { error: false, data: u64:0b11, length: u7:2}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + // state update + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:2}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:2}, + // symbols (seq #7) + TestRefillingSBOutput { error: false, data: u64:0b1000, length: u7:4}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + // no state update for last sequence +]; + +const TEST_DATA_1 = TestRefillingSBOutput[42]:[ + // init states + TestRefillingSBOutput { error: false, data: u64:0b10000, length: u7:5}, + TestRefillingSBOutput { error: false, data: u64:0b1110, length: u7:5}, + TestRefillingSBOutput { error: false, data: u64:0b11001, length: u7:6}, + // symbols (seq #0) + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:2}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + // state update + TestRefillingSBOutput { error: false, data: u64:0b110, length: u7:3}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:5}, + TestRefillingSBOutput { error: false, data: u64:0b1110, length: u7:5}, + // symbols (seq #1) + TestRefillingSBOutput { error: false, data: u64:0b10, length: u7:2}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + // state update + TestRefillingSBOutput { error: false, data: u64:0b10, length: u7:2}, + TestRefillingSBOutput { error: false, data: u64:0b1, length: u7:6}, + TestRefillingSBOutput { error: false, data: u64:0b101, length: u7:5}, + // symbols (seq #2) + TestRefillingSBOutput { error: false, data: u64:0b110, length: u7:3}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + // state update + TestRefillingSBOutput { error: false, data: u64:0b11, length: u7:2}, + TestRefillingSBOutput { error: false, data: u64:0b1, length: u7:4}, + TestRefillingSBOutput { error: false, data: u64:0b10011, length: u7:5}, + // symbols (seq #3) + TestRefillingSBOutput { error: false, data: u64:0b11, length: u7:4}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + // state update + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:2}, + TestRefillingSBOutput { error: false, data: u64:0b1, length: u7:4}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:5}, + // symbols (seq #4) + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + // state update + TestRefillingSBOutput { error: false, data: u64:0b10, length: u7:3}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:4}, + TestRefillingSBOutput { error: false, data: u64:0b1010, length: u7:5}, + // symbols (seq #5) + TestRefillingSBOutput { error: false, data: u64:0b1110, length: u7:5}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + // state update + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:1}, + TestRefillingSBOutput { error: false, data: u64:0b11, length: u7:6}, + TestRefillingSBOutput { error: false, data: u64:0b10011, length: u7:5}, + // symbols (seq #6) + TestRefillingSBOutput { error: false, data: u64:0b10, length: u7:4}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0}, + // no state update for last sequence +]; +// FIXME: test error propagation with TestRefillingSBOutput { error: true, ...} + + +fn test_command(block_idx: u32, msg_type: SequenceExecutorMessageType, length: CopyOrMatchLength, content: CopyOrMatchContent, last: bool) -> CommandConstructorData { + CommandConstructorData { + sync: TEST_SYNC[block_idx], + data: SequenceExecutorPacket { + msg_type: msg_type, + length: length, + content: content, + last: last, + }, + } +} + +const TEST_EXPECTED_COMMANDS_0 = CommandConstructorData[16]:[ + // block #0 + // seq #0 + test_command(u32:0, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:1, CopyOrMatchContent:0, false), + test_command(u32:0, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:4, CopyOrMatchContent:1, false), + // seq #1 + test_command(u32:0, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:0, CopyOrMatchContent:0, false), + test_command(u32:0, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:3, CopyOrMatchContent:6, false), + // seq #2 + test_command(u32:0, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:1, CopyOrMatchContent:0, false), + test_command(u32:0, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:4, CopyOrMatchContent:8, false), + // seq #3 + test_command(u32:0, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:0, CopyOrMatchContent:0, false), + test_command(u32:0, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:4, CopyOrMatchContent:11, false), + // seq #4 + test_command(u32:0, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:0, CopyOrMatchContent:0, false), + test_command(u32:0, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:4, CopyOrMatchContent:16, false), + // seq #5 + test_command(u32:0, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:0, CopyOrMatchContent:0, false), + test_command(u32:0, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:4, CopyOrMatchContent:13, false), + // seq #6 + test_command(u32:0, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:0, CopyOrMatchContent:0, false), + test_command(u32:0, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:6, CopyOrMatchContent:7, false), + // seq #7 + test_command(u32:0, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:0, CopyOrMatchContent:0, false), + test_command(u32:0, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:3, CopyOrMatchContent:24, true), +]; + +const TEST_EXPECTED_COMMANDS_1 = CommandConstructorData[14]:[ + // block #1 + // seq #0 + test_command(u32:1, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:1, CopyOrMatchContent:0, false), + test_command(u32:1, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:7, CopyOrMatchContent:4, false), + // seq #1 + test_command(u32:1, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:3, CopyOrMatchContent:0, false), + test_command(u32:1, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:3, CopyOrMatchContent:6, false), + // seq #2 + test_command(u32:1, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:0, CopyOrMatchContent:0, false), + test_command(u32:1, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:4, CopyOrMatchContent:14, false), + // seq #3 + test_command(u32:1, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:5, CopyOrMatchContent:0, false), + test_command(u32:1, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:4, CopyOrMatchContent:19, false), + // seq #4 + test_command(u32:1, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:13, CopyOrMatchContent:0, false), + test_command(u32:1, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:4, CopyOrMatchContent:1, false), + // seq #5 + test_command(u32:1, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:0, CopyOrMatchContent:0, false), + test_command(u32:1, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:3, CopyOrMatchContent:46, false), + // seq #6 + test_command(u32:1, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:0, CopyOrMatchContent:0, false), + test_command(u32:1, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:6, CopyOrMatchContent:18, true), +]; + +//#[test_proc] +//proc FseDecoderTest { +// type FseRamRdReq = ram::ReadReq; +// type FseRamRdResp = ram::ReadResp; +// +// type FseRamWrReq = ram::WriteReq; +// type FseRamWrResp = ram::WriteResp; +// +// type RefillingSBCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; +// type RefillingSBOutput = refilling_shift_buffer::RefillingShiftBufferOutput; +// +// terminator: chan out; +// +// ctrl_s: chan out; +// finish_r: chan in; +// +// rsb_ctrl_r: chan in; +// rsb_data_s: chan out; +// +// command_r: chan in; +// +// ll_fse_wr_req_s: chan out; +// ll_fse_wr_resp_r: chan in; +// +// ml_fse_wr_req_s: chan out; +// ml_fse_wr_resp_r: chan in; +// +// of_fse_wr_req_s: chan out; +// of_fse_wr_resp_r: chan in; +// +// config (terminator: chan out) { +// let (ctrl_s, ctrl_r) = chan("ctrl"); +// let (finish_s, finish_r) = chan("finish"); +// +// let (rsb_ctrl_s, rsb_ctrl_r) = chan("rsb_ctrl"); +// let (rsb_data_s, rsb_data_r) = chan("rsb_out_data"); +// +// let (command_s, command_r) = chan("command"); +// +// // RAM with FSE lookup for Literal Lengths +// let (ll_fse_rd_req_s, ll_fse_rd_req_r) = chan("ll_fse_rd_req"); +// let (ll_fse_rd_resp_s, ll_fse_rd_resp_r) = chan("ll_fse_rd_resp"); +// let (ll_fse_wr_req_s, ll_fse_wr_req_r) = chan("ll_fse_wr_req"); +// let (ll_fse_wr_resp_s, ll_fse_wr_resp_r) = chan("ll_fse_wr_resp"); +// +// spawn ram::RamModel< +// TEST_RAM_DATA_W, +// TEST_RAM_SIZE, +// TEST_RAM_WORD_PARTITION_SIZE, +// >(ll_fse_rd_req_r, ll_fse_rd_resp_s, ll_fse_wr_req_r, ll_fse_wr_resp_s); +// +// // RAM with FSE lookup for Match Lengths +// let (ml_fse_rd_req_s, ml_fse_rd_req_r) = chan("ml_fse_rd_req"); +// let (ml_fse_rd_resp_s, ml_fse_rd_resp_r) = chan("ml_fse_rd_resp"); +// let (ml_fse_wr_req_s, ml_fse_wr_req_r) = chan("ml_fse_wr_req"); +// let (ml_fse_wr_resp_s, ml_fse_wr_resp_r) = chan("ml_fse_wr_resp"); +// +// spawn ram::RamModel< +// TEST_RAM_DATA_W, +// TEST_RAM_SIZE, +// TEST_RAM_WORD_PARTITION_SIZE, +// >(ml_fse_rd_req_r, ml_fse_rd_resp_s, ml_fse_wr_req_r, ml_fse_wr_resp_s); +// +// // RAM with FSE lookup for Offsets +// let (of_fse_rd_req_s, of_fse_rd_req_r) = chan("of_fse_rd_req"); +// let (of_fse_rd_resp_s, of_fse_rd_resp_r) = chan("of_fse_rd_resp"); +// let (of_fse_wr_req_s, of_fse_wr_req_r) = chan("of_fse_wr_req"); +// let (of_fse_wr_resp_s, of_fse_wr_resp_r) = chan("of_fse_wr_resp"); +// +// spawn ram::RamModel< +// TEST_RAM_DATA_W, +// TEST_RAM_SIZE, +// TEST_RAM_WORD_PARTITION_SIZE, +// >(of_fse_rd_req_r, of_fse_rd_resp_s, of_fse_wr_req_r, of_fse_wr_resp_s); +// +// spawn FseDecoder< +// TEST_RAM_DATA_W, TEST_RAM_ADDR_W, TEST_RAM_NUM_PARTITIONS, +// TEST_AXI_DATA_W, +// >( +// ctrl_r, finish_s, +// rsb_ctrl_s, rsb_data_r, +// command_s, +// ll_fse_rd_req_s, ll_fse_rd_resp_r, +// ml_fse_rd_req_s, ml_fse_rd_resp_r, +// of_fse_rd_req_s, of_fse_rd_resp_r, +// ); +// +// ( +// terminator, +// ctrl_s, finish_r, +// rsb_ctrl_r, rsb_data_s, +// command_r, +// ll_fse_wr_req_s, ll_fse_wr_resp_r, +// ml_fse_wr_req_s, ml_fse_wr_resp_r, +// of_fse_wr_req_s, of_fse_wr_resp_r, +// ) +// } +// +// init { u32:0 } +// +// next (state: u32) { +// let tok = join(); +// +// // write OF table +// let tok = for ((i, of_record), tok): ((u32, u32), token) in enumerate(TEST_OF_TABLE[state]) { +// let tok = send(tok, of_fse_wr_req_s, FseRamWrReq { +// addr: i as u8, +// data: of_record, +// mask: u4:0xf, +// }); +// let (tok, _) = recv(tok, of_fse_wr_resp_r); +// tok +// }(tok); +// +// // write ML table +// let tok = for ((i, ml_record), tok): ((u32, u32), token) in enumerate(TEST_ML_TABLE[state]) { +// let tok = send(tok, ml_fse_wr_req_s, FseRamWrReq { +// addr: i as u8, +// data: ml_record, +// mask: u4:0xf, +// }); +// let (tok, _) = recv(tok, ml_fse_wr_resp_r); +// tok +// }(tok); +// +// // write LL table +// let tok = for ((i, ll_record), tok): ((u32, u32), token) in enumerate(TEST_LL_TABLE[state]) { +// let tok = send(tok, ll_fse_wr_req_s, FseRamWrReq { +// addr: i as u8, +// data: ll_record, +// mask: u4:0xf, +// }); +// let (tok, _) = recv(tok, ll_fse_wr_resp_r); +// tok +// }(tok); +// +// // send ctrl +// let tok = send(tok, ctrl_s, TEST_CTRL[state]); +// trace_fmt!("Sent ctrl {:#x}", TEST_CTRL[state]); +// +// match state { +// u32:0 => { +// // block #0 +// // send data +// let tok = for ((i, data), tok): ((u32, RefillingSBOutput), token) in enumerate(TEST_DATA_0) { +// let (tok, buf_ctrl) = recv(tok, rsb_ctrl_r); +// trace_fmt!("Received #{} buf ctrl {:#x}", i + u32:1, buf_ctrl); +// assert_eq(RefillingSBCtrl {length: data.length}, buf_ctrl); +// let tok = send(tok, rsb_data_s, data); +// trace_fmt!("Sent #{} buf data {:#x}", i + u32:1, data); +// tok +// }(tok); +// +// // recv commands +// let tok = for ((i, expected_cmd), tok): ((u32, CommandConstructorData), token) in enumerate(TEST_EXPECTED_COMMANDS_0) { +// let (tok, cmd) = recv(tok, command_r); +// trace_fmt!("Received #{} cmd {:#x}", i + u32:1, cmd); +// assert_eq(expected_cmd, cmd); +// tok +// }(tok); +// +// // recv finish +// let (tok, _) = recv(tok, finish_r); +// }, +// u32:1 => { +// // block #1 +// // send data +// let tok = for ((i, data), tok): ((u32, RefillingSBOutput), token) in enumerate(TEST_DATA_1) { +// let (tok, buf_ctrl) = recv(tok, rsb_ctrl_r); +// trace_fmt!("Received #{} buf ctrl {:#x}", i + u32:1, buf_ctrl); +// assert_eq(RefillingSBCtrl {length: data.length}, buf_ctrl); +// let tok = send(tok, rsb_data_s, data); +// trace_fmt!("Sent #{} buf data {:#x}", i + u32:1, data); +// tok +// }(tok); +// +// // recv commands +// let tok = for ((i, expected_cmd), tok): ((u32, CommandConstructorData), token) in enumerate(TEST_EXPECTED_COMMANDS_1) { +// let (tok, cmd) = recv(tok, command_r); +// trace_fmt!("Received #{} cmd {:#x}", i + u32:1, cmd); +// assert_eq(expected_cmd, cmd); +// tok +// }(tok); +// +// // recv finish +// let (tok, _) = recv(tok, finish_r); +// +// send(tok, terminator, true); +// }, +// }; +// +// state + u32:1 +// } +//} diff --git a/xls/modules/zstd/fse_lookup_dec.x b/xls/modules/zstd/fse_lookup_dec.x new file mode 100644 index 0000000000..5c2fa5ec80 --- /dev/null +++ b/xls/modules/zstd/fse_lookup_dec.x @@ -0,0 +1,1049 @@ +// 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; +import xls.modules.zstd.common; +import xls.modules.zstd.memory.axi; +import xls.modules.zstd.memory.mem_reader; +import xls.modules.zstd.memory.axi_ram; +import xls.modules.zstd.fse_table_creator; +import xls.modules.zstd.refilling_shift_buffer; +import xls.modules.zstd.fse_proba_freq_dec; +import xls.modules.shift_buffer.shift_buffer; + +type AccuracyLog = common::FseAccuracyLog; + +pub enum FseLookupDecoderStatus: u1 { + OK = 0, + ERROR = 1, +} + +pub struct FseLookupDecoderReq { + addr: uN[AXI_ADDR_W] +} + +pub struct FseLookupDecoderResp { + status: FseLookupDecoderStatus, + accuracy_log: common::FseAccuracyLog +} + +pub proc FseLookupDecoder< + AXI_DATA_W: u32, AXI_ADDR_W: u32, + DPD_RAM_DATA_W: u32, DPD_RAM_ADDR_W: u32, DPD_RAM_NUM_PARTITIONS: u32, + TMP_RAM_DATA_W: u32, TMP_RAM_ADDR_W: u32, TMP_RAM_NUM_PARTITIONS: u32, + FSE_RAM_DATA_W: u32, FSE_RAM_ADDR_W: u32, FSE_RAM_NUM_PARTITIONS: u32, + SB_LENGTH_W: u32 = {refilling_shift_buffer::length_width(AXI_DATA_W)}, +> { + type Req = FseLookupDecoderReq; + type Resp = FseLookupDecoderResp; + type Status = FseLookupDecoderStatus; + + type FseTableStart = fse_table_creator::FseStartMsg; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + type MemReaderStatus = mem_reader::MemReaderStatus; + + type DpdRamWrReq = ram::WriteReq; + type DpdRamWrResp = ram::WriteResp; + type DpdRamRdReq = ram::ReadReq; + type DpdRamRdResp = ram::ReadResp; + + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; + type FseRamWrReq = ram::WriteReq; + type FseRamWrResp = ram::WriteResp; + + type TmpRamRdReq = ram::ReadReq; + type TmpRamRdResp = ram::ReadResp; + type TmpRamWrReq = ram::WriteReq; + type TmpRamWrResp = ram::WriteResp; + + type RefillerStartReq = refilling_shift_buffer::RefillStart; + type RefillerError = refilling_shift_buffer::RefillError; + type SBOutput = refilling_shift_buffer::RefillingShiftBufferOutput; + type SBCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + + type FsePFDecReq = fse_proba_freq_dec::FseProbaFreqDecoderReq; + type FsePFDecResp = fse_proba_freq_dec::FseProbaFreqDecoderResp; + type FsePFDecStatus = fse_proba_freq_dec::FseProbaFreqDecoderStatus; + + req_r: chan in; + resp_s: chan out; + + start_req_s: chan out; + stop_flush_req_s: chan<()> out; + flushing_done_r: chan<()> in; + + fse_pf_dec_req_s: chan out; + fse_pf_dec_resp_r: chan in; + fse_table_start_s: chan out; + fse_table_finish_r: chan<()> in; + + init {} + + config( + req_r: chan in, + resp_s: chan out, + + mem_rd_req_s: chan out, + mem_rd_resp_r: chan in, + + dpd_rd_req_s: chan out, + dpd_rd_resp_r: chan in, + dpd_wr_req_s: chan out, + dpd_wr_resp_r: chan in, + + tmp_rd_req_s: chan out, + tmp_rd_resp_r: chan in, + tmp_wr_req_s: chan out, + tmp_wr_resp_r: chan in, + + fse_rd_req_s: chan out, + fse_rd_resp_r: chan in, + fse_wr_req_s: chan out, + fse_wr_resp_r: chan in, + ) { + const CHANNEL_DEPTH = u32:1; + + let (fse_table_start_s, fse_table_start_r) = chan("fse_table_start"); + let (fse_table_finish_s, fse_table_finish_r) = chan<(), CHANNEL_DEPTH>("fse_table_finish"); + + spawn fse_table_creator::FseTableCreator< + DPD_RAM_DATA_W, DPD_RAM_ADDR_W, DPD_RAM_NUM_PARTITIONS, + FSE_RAM_DATA_W, FSE_RAM_ADDR_W, FSE_RAM_NUM_PARTITIONS, + TMP_RAM_DATA_W, TMP_RAM_ADDR_W, TMP_RAM_NUM_PARTITIONS, + >( + fse_table_start_r, fse_table_finish_s, + dpd_rd_req_s, dpd_rd_resp_r, + fse_rd_req_s, fse_rd_resp_r, fse_wr_req_s, fse_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + ); + + let (start_req_s, start_req_r) = chan("start_req"); + let (stop_flush_req_s, stop_flush_req_r) = chan<(), CHANNEL_DEPTH>("stop_flush_req"); + let (buffer_ctrl_s, buffer_ctrl_r) = chan("buffer_ctrl"); + let (buffer_data_out_s, buffer_data_out_r) = chan("buffer_data_out"); + let (flushing_done_s, flushing_done_r) = chan<(), CHANNEL_DEPTH>("flushing_done"); + + spawn refilling_shift_buffer::RefillingShiftBuffer( + mem_rd_req_s, + mem_rd_resp_r, + start_req_r, + stop_flush_req_r, + buffer_ctrl_r, + buffer_data_out_s, + flushing_done_s, + ); + + let (fse_pf_dec_req_s, fse_pf_dec_req_r) = chan("fse_pf_dec_req"); + let (fse_pf_dec_resp_s, fse_pf_dec_resp_r) = chan("fse_pf_dec_resp"); + + spawn fse_proba_freq_dec::FseProbaFreqDecoder< + DPD_RAM_DATA_W, DPD_RAM_ADDR_W, DPD_RAM_NUM_PARTITIONS, + >( + fse_pf_dec_req_r, fse_pf_dec_resp_s, + buffer_ctrl_s, buffer_data_out_r, + dpd_wr_req_s, dpd_wr_resp_r, + ); + + ( + req_r, resp_s, + start_req_s, + stop_flush_req_s, + flushing_done_r, + fse_pf_dec_req_s, fse_pf_dec_resp_r, + fse_table_start_s, fse_table_finish_r, + ) + } + + next(state: ()) { + let tok = join(); + let (tok, start_req) = recv(tok, req_r); + + // start refilling shift buffer + let tok_dec_pf1 = send(tok, start_req_s, RefillerStartReq { + start_addr: start_req.addr + }); + // start FSE probability frequency decoder + let tok_dec_pf2 = send(tok, fse_pf_dec_req_s, FsePFDecReq {}); + + // wait for completion from FSE probability frequency decoder + let tok = join(tok_dec_pf1, tok_dec_pf2); + let (tok_dec_resp, pf_dec_res) = recv(tok, fse_pf_dec_resp_r); + + // flush refilling shift buffer (regardless of any errors) + let tok_flush = send(tok_dec_resp, stop_flush_req_s, ()); + recv(tok_flush, flushing_done_r); + + let pf_dec_ok = pf_dec_res.status == FsePFDecStatus::OK; + // run FSE Table creation conditional or previous processing succeeding + let tok = send_if(tok_dec_resp, fse_table_start_s, pf_dec_ok, FseTableStart { + num_symbs: pf_dec_res.symbol_count, + accuracy_log: pf_dec_res.accuracy_log, + }); + // wait for completion from FSE table creator + let (tok, ()) = recv_if(tok, fse_table_finish_r, pf_dec_ok, ()); + + let resp = if pf_dec_ok { + Resp { status: Status::OK, accuracy_log: pf_dec_res.accuracy_log } + } else { + Resp { status: Status::ERROR, ..zero!() } + }; + send(tok, resp_s, resp); + } +} + + +const TEST_AXI_DATA_WIDTH = u32:64; +const TEST_AXI_ADDR_WIDTH = u32:32; +const TEST_AXI_ID_WIDTH = u32:8; +const TEST_AXI_DEST_WIDTH = u32:8; + +const TEST_CASE_RAM_DATA_WIDTH = u32:64; +const TEST_CASE_RAM_SIZE = u32:256; +const TEST_CASE_RAM_ADDR_WIDTH = std::clog2(TEST_CASE_RAM_SIZE); +const TEST_CASE_RAM_WORD_PARTITION_SIZE = TEST_CASE_RAM_DATA_WIDTH; +const TEST_CASE_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_CASE_RAM_WORD_PARTITION_SIZE, TEST_CASE_RAM_DATA_WIDTH); +const TEST_CASE_RAM_BASE_ADDR = u32:0; + +const TEST_DPD_RAM_DATA_WIDTH = u32:16; +const TEST_DPD_RAM_SIZE = u32:256; +const TEST_DPD_RAM_ADDR_WIDTH = std::clog2(TEST_DPD_RAM_SIZE); +const TEST_DPD_RAM_WORD_PARTITION_SIZE = TEST_DPD_RAM_DATA_WIDTH; +const TEST_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_DPD_RAM_WORD_PARTITION_SIZE, TEST_DPD_RAM_DATA_WIDTH); + +const TEST_FSE_RAM_DATA_WIDTH = u32:32; +const TEST_FSE_RAM_SIZE = u32:256; +const TEST_FSE_RAM_ADDR_WIDTH = std::clog2(TEST_FSE_RAM_SIZE); +const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_WIDTH / u32:3; +const TEST_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_FSE_RAM_WORD_PARTITION_SIZE, TEST_FSE_RAM_DATA_WIDTH); + +const TEST_TMP_RAM_DATA_WIDTH = u32:16; +const TEST_TMP_RAM_SIZE = u32:256; +const TEST_TMP_RAM_ADDR_WIDTH = std::clog2(TEST_TMP_RAM_SIZE); +const TEST_TMP_RAM_WORD_PARTITION_SIZE = TEST_TMP_RAM_DATA_WIDTH; +const TEST_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_TMP_RAM_WORD_PARTITION_SIZE, TEST_TMP_RAM_DATA_WIDTH); + +const TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_RAM_INITIALIZED = true; + +type FseTableRecord = common::FseTableRecord; + +const FSE_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE], FseLookupDecoderResp)[10] = [ + ( + u64[64]:[u64:0x72AAAAABBB1D25C0, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x16 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1a }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1e }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x1 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x2 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x3 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x5 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x6 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x7 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x9 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xa }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xb }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xd }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xe }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xf }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x11 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x12 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x13 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x15 }, + zero!(), ... + ], + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + ), + ( + u64[64]:[u64:0x1861862062081932, u64:0xC18628A106184184, u64:0x850720FACC49238, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x11, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x5, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x5, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x5, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x5, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x7, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x13, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x7, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x7, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x11, num_of_bits: u8:0x5, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x5, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x7, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x5, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x5, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x13, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x11, num_of_bits: u8:0x5, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x11, num_of_bits: u8:0x5, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x5, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x5, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x70 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x3, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x3, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x5, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x6, base: u16:0x40 }, + zero!(), ... + ], + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:7 } + ), + ( + u64[64]:[u64:0x60C3082082085072, u64:0x1C06F8077D850F20, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x5, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x38 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x7, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x3, base: u16:0x70 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x5, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x5, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x48 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x3, base: u16:0x78 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x28 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x5, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x50 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x4, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x5, base: u16:0x20 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x58 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x4, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x5, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x5, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x68 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x7, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x70 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x78 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x7, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x50 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x5, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x38 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x3c }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x44 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x7, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x48 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x7, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x4c }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x4, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x70 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x50 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x4, base: u16:0x50 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x54 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x58 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x5c }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x64 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x68 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x6c }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x58 }, + zero!(), ... + ], + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:7 } + ), + ( + u64[64]:[u64:0x41081C158003A5D0, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1a }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1e }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x1 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x2 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x3 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x5 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x6 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x7 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x9 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xa }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xb }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xd }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xe }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xf }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x11 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x12 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x13 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x15 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x16 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x17 }, + zero!(), ... + ], + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + ), + ( + u64[64]:[u64:0x1101141108088A1, u64:0xA210842108421011, u64:0xAC90E792007A5B4, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x11, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x3, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1c, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1c, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x13, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x3, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x3, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x3, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x1c, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x1c, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x1c, num_of_bits: u8:0x3, base: u16:0x8 }, + zero!(), ... + ], + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:6 } + ), + ( + u64[64]:[u64:0x4AF830AC90E7920, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x2 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x6 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0xa }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0xc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0xe }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x12 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x16 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0x2 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1a }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1e }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0x6 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x1 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0xa }, + zero!(), ... + ], + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + ), + ( + u64[64]:[u64:0xF47FFEBBFF1D25C0, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x16 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1a }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1e }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x1 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x2 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x3 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x5 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x6 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x7 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x9 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xa }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xb }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xd }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xe }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xf }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x11 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x12 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x13 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x15 }, + zero!(), ... + ], + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + ), + ( + u64[64]:[u64:0xA84DF134544CA40, u64:0xEEC609988403B0C, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x3, base: u16:0x8 }, + zero!(), ... + ], + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + ), + ( + u64[64]:[u64:0x38100EEC60998840, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x2, base: u16:0xc }, + zero!(), ... + ], + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + ), + ( + u64[64]:[u64:0x6B1CA24D0CE43810, u64:0x6651065104A4DFFD, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x24, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x24, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x3, base: u16:0x8 }, + zero!(), ... + ], + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + ), +]; + +#[test_proc] +proc FseLookupDecoderTest { + type Req = FseLookupDecoderReq; + type Resp = FseLookupDecoderResp; + type Status = FseLookupDecoderStatus; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + + type DpdRamWrReq = ram::WriteReq; + type DpdRamWrResp = ram::WriteResp; + type DpdRamRdReq = ram::ReadReq; + type DpdRamRdResp = ram::ReadResp; + + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; + type FseRamWrReq = ram::WriteReq; + type FseRamWrResp = ram::WriteResp; + + type TmpRamRdReq = ram::ReadReq; + type TmpRamRdResp = ram::ReadResp; + type TmpRamWrReq = ram::WriteReq; + type TmpRamWrResp = ram::WriteResp; + + type TestcaseRamRdReq = ram::ReadReq; + type TestcaseRamRdResp = ram::ReadResp; + type TestcaseRamWrReq = ram::WriteReq; + type TestcaseRamWrResp = ram::WriteResp; + + type AxiR = axi::AxiR; + type AxiAr = axi::AxiAr; + + terminator: chan out; + req_s: chan out; + resp_r: chan in; + fse_rd_req_s: chan out; + fse_rd_resp_r: chan in; + fse_wr_req_s: chan out; + fse_wr_resp_r: chan in; + testcase_wr_req_s: chan out; + testcase_wr_resp_r: chan in; + + config(terminator: chan out) { + let (req_s, req_r) = chan("req"); + let (resp_s, resp_r) = chan("resp"); + let (mem_rd_req_s, mem_rd_req_r) = chan("mem_rd_req"); + let (mem_rd_resp_s, mem_rd_resp_r) = chan("mem_rd_resp"); + + let (dpd_rd_req_s, dpd_rd_req_r) = chan("dpd_rd_req"); + let (dpd_rd_resp_s, dpd_rd_resp_r) = chan("dpd_rd_resp"); + let (dpd_wr_req_s, dpd_wr_req_r) = chan("dpd_wr_req"); + let (dpd_wr_resp_s, dpd_wr_resp_r) = chan("dpd_wr_resp"); + + let (tmp_rd_req_s, tmp_rd_req_r) = chan("tmp_rd_req"); + let (tmp_rd_resp_s, tmp_rd_resp_r) = chan("tmp_rd_resp"); + let (tmp_wr_req_s, tmp_wr_req_r) = chan("tmp_wr_req"); + let (tmp_wr_resp_s, tmp_wr_resp_r) = chan("tmp_wr_resp"); + + let (fse_rd_req_s, fse_rd_req_r) = chan("fse_rd_req"); + let (fse_rd_resp_s, fse_rd_resp_r) = chan("fse_rd_resp"); + let (fse_wr_req_s, fse_wr_req_r) = chan("fse_wr_req"); + let (fse_wr_resp_s, fse_wr_resp_r) = chan("fse_wr_resp"); + + let (testcase_rd_req_s, testcase_rd_req_r) = chan("testcase_rd_req"); + let (testcase_rd_resp_s, testcase_rd_resp_r) = chan("testcase_rd_resp"); + let (testcase_wr_req_s, testcase_wr_req_r) = chan("testcase_wr_req"); + let (testcase_wr_resp_s, testcase_wr_resp_r) = chan("testcase_wr_resp"); + + spawn FseLookupDecoder< + TEST_AXI_DATA_WIDTH, TEST_AXI_ADDR_WIDTH, + TEST_DPD_RAM_DATA_WIDTH, TEST_DPD_RAM_ADDR_WIDTH, TEST_DPD_RAM_NUM_PARTITIONS, + TEST_TMP_RAM_DATA_WIDTH, TEST_TMP_RAM_ADDR_WIDTH, TEST_TMP_RAM_NUM_PARTITIONS, + TEST_FSE_RAM_DATA_WIDTH, TEST_FSE_RAM_ADDR_WIDTH, TEST_FSE_RAM_NUM_PARTITIONS, + >( + req_r, + resp_s, + mem_rd_req_s, + mem_rd_resp_r, + dpd_rd_req_s, + dpd_rd_resp_r, + dpd_wr_req_s, + dpd_wr_resp_r, + tmp_rd_req_s, + tmp_rd_resp_r, + tmp_wr_req_s, + tmp_wr_resp_r, + fse_rd_req_s, + fse_rd_resp_r, + fse_wr_req_s, + fse_wr_resp_r, + ); + + spawn ram::RamModel< + TEST_DPD_RAM_DATA_WIDTH, TEST_DPD_RAM_SIZE, TEST_DPD_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, + >(dpd_rd_req_r, dpd_rd_resp_s, dpd_wr_req_r, dpd_wr_resp_s); + + spawn ram::RamModel< + TEST_FSE_RAM_DATA_WIDTH, TEST_FSE_RAM_SIZE, TEST_FSE_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, + >(fse_rd_req_r, fse_rd_resp_s, fse_wr_req_r, fse_wr_resp_s); + + spawn ram::RamModel< + TEST_TMP_RAM_DATA_WIDTH, TEST_TMP_RAM_SIZE, TEST_TMP_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, + >(tmp_rd_req_r, tmp_rd_resp_s, tmp_wr_req_r, tmp_wr_resp_s); + + spawn ram::RamModel< + TEST_CASE_RAM_DATA_WIDTH, TEST_CASE_RAM_SIZE, TEST_CASE_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, + >(testcase_rd_req_r, testcase_rd_resp_s, testcase_wr_req_r, testcase_wr_resp_s); + + let (testcase_axi_r_s, testcase_axi_r_r) = chan("testcase_axi_r"); + let (testcase_axi_ar_s, testcase_axi_ar_r) = chan("testcase_axi_ar"); + + spawn axi_ram::AxiRamReader< + TEST_AXI_ADDR_WIDTH, TEST_AXI_DATA_WIDTH, TEST_AXI_DEST_WIDTH, TEST_AXI_ID_WIDTH, + TEST_CASE_RAM_SIZE, TEST_CASE_RAM_BASE_ADDR, TEST_CASE_RAM_DATA_WIDTH, + TEST_CASE_RAM_ADDR_WIDTH, TEST_CASE_RAM_NUM_PARTITIONS, + >(testcase_axi_ar_r, testcase_axi_r_s, testcase_rd_req_s, testcase_rd_resp_r); + + spawn mem_reader::MemReader< + TEST_AXI_DATA_WIDTH, TEST_AXI_ADDR_WIDTH, TEST_AXI_DEST_WIDTH, TEST_AXI_ID_WIDTH + >(mem_rd_req_r, mem_rd_resp_s, testcase_axi_ar_s, testcase_axi_r_r); + + ( + terminator, req_s, resp_r, fse_rd_req_s, fse_rd_resp_r, + fse_wr_req_s, fse_wr_resp_r, testcase_wr_req_s, testcase_wr_resp_r, + ) + } + + init {} + + next(_: ()) { + let tok = join(); + // This has to be outside of unroll_for!, otherwise typechecker reports type mismatch on identical types + let req_start = Req { addr: uN[TEST_AXI_ADDR_WIDTH]:0x0 }; + + let tok = unroll_for!(test_i, tok): (u32, token) in range(u32:0, array_size(FSE_LOOKUP_DECODER_TESTCASES)) { + let (input, output, resp_ok) = FSE_LOOKUP_DECODER_TESTCASES[test_i]; + + trace_fmt!("Loading testcase {:x}", test_i); + let tok = for ((i, input_data), tok): ((u32, u64), token) in enumerate(input) { + let req = TestcaseRamWrReq { + addr: i as uN[TEST_CASE_RAM_ADDR_WIDTH], + data: input_data as uN[TEST_CASE_RAM_DATA_WIDTH], + mask: uN[TEST_CASE_RAM_NUM_PARTITIONS]:0x1 + }; + let tok = send(tok, testcase_wr_req_s, req); + let (tok, _) = recv(tok, testcase_wr_resp_r); + tok + }(tok); + + trace_fmt!("Running FSE lookup decoder on testcase {:x}", test_i); + let tok = send(tok, req_s, req_start); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, resp_ok); + + let tok = for ((i, output_data), tok): ((u32, FseTableRecord), token) in enumerate(output) { + let req = FseRamRdReq { + addr: i as uN[TEST_FSE_RAM_ADDR_WIDTH], + mask: uN[TEST_FSE_RAM_NUM_PARTITIONS]:0x7, + }; + let tok = send(tok, fse_rd_req_s, req); + let (tok, resp) = recv(tok, fse_rd_resp_r); + assert_eq(fse_table_creator::bits_to_fse_record(resp.data), output_data); + + // erase output for next test to start with clean memory + let clear_req = FseRamWrReq { + addr: i as uN[TEST_FSE_RAM_ADDR_WIDTH], + mask: uN[TEST_FSE_RAM_NUM_PARTITIONS]:0x7, + data: uN[TEST_FSE_RAM_DATA_WIDTH]:0x0, + }; + let tok = send(tok, fse_wr_req_s, clear_req); + let (tok, _) = recv(tok, fse_wr_resp_r); + tok + }(tok); + tok + }(tok); + + send(tok, terminator, true); + } +} diff --git a/xls/modules/zstd/fse_proba_freq_dec.x b/xls/modules/zstd/fse_proba_freq_dec.x index 19150851c5..5daf857aef 100644 --- a/xls/modules/zstd/fse_proba_freq_dec.x +++ b/xls/modules/zstd/fse_proba_freq_dec.x @@ -19,7 +19,8 @@ import std; import xls.examples.ram; import xls.modules.zstd.common; -import xls.modules.zstd.shift_buffer; +import xls.modules.shift_buffer.shift_buffer; +import xls.modules.zstd.refilling_shift_buffer; import xls.modules.zstd.ram_wr_handler as ram_wr; pub const FSE_MAX_SYMBOLS = u32:256; @@ -43,13 +44,17 @@ const ACCURACY_LOG_WIDTH = common::FSE_ACCURACY_LOG_WIDTH; pub struct Remainder { value: u1, valid: bool } -enum FseProbaFreqDecoderStatus: u1 { +pub enum FseProbaFreqDecoderStatus: u1 { OK = 0, ERROR = 1, } pub struct FseProbaFreqDecoderReq {} -pub struct FseProbaFreqDecoderResp { status: FseProbaFreqDecoderStatus } +pub struct FseProbaFreqDecoderResp { + status: FseProbaFreqDecoderStatus, + accuracy_log: AccuracyLog, + symbol_count: SymbolCount, +} enum Fsm : u4 { IDLE = 0, @@ -60,7 +65,8 @@ enum Fsm : u4 { RECV_ZERO_PROBA = 5, WRITE_ZERO_PROBA = 6, WAIT_FOR_COMPLETION = 7, - INVALID = 8, + CONSUME_PADDING = 8, + INVALID = 9, } struct State { @@ -79,45 +85,62 @@ struct State { written_symbol_count: SymbolCount, // number of processed zero probability symbols zero_proba_count: SymbolCount, + // indicates error condition: either passed on from ShiftBuffer or due to + // using up more probability points than were available + data_invalid: bool, + // number of bits read from RefillingShiftBuffer modulo 8 + read_bits_mod8: u3 } // Adapter for input data, converting the data to a shift buffer input type pub proc FseInputBuffer { - type BufferInput = shift_buffer::ShiftBufferInput; + type BufferInput = shift_buffer::ShiftBufferPacket; type BufferCtrl = shift_buffer::ShiftBufferCtrl; type BufferOutput = shift_buffer::ShiftBufferOutput; + type RefillingBufferOutput = refilling_shift_buffer::RefillingShiftBufferOutput; type Data = common::BlockData; type Length = bits[LENGTH_WIDTH]; in_data_r: chan in; buff_data_s: chan out; + out_s: chan out; + buff_data_out_r: chan in; config( data_r: chan in, ctrl_r: chan in, - out_s: chan out, + out_s: chan out, ) { - let (buff_data_s, buff_data_r) = chan("buff_in_data"); + const CHANNEL_DEPTH = u32:1; + + let (buff_data_s, buff_data_r) = chan("buff_in_data"); + let (buff_data_out_s, buff_data_out_r) = chan("buff_out_data"); spawn shift_buffer::ShiftBuffer( - buff_data_r, ctrl_r, out_s); + ctrl_r, buff_data_r, buff_data_out_s); - (data_r, buff_data_s) + (data_r, buff_data_s, out_s, buff_data_out_r) } init { } - next (state: ()) { + next(state: ()) { let tok0 = join(); - let (tok1, recv_data, recv_valid) = recv_non_blocking(tok0, in_data_r, zero!()); + let (tok1, recv_data, recv_valid) = recv_non_blocking(tok0, in_data_r, zero!()); let shift_buffer_data = BufferInput { data: recv_data.bytes as Data, length: recv_data.length as Length, - last: recv_data.last }; - send_if(tok1, buff_data_s, recv_valid, shift_buffer_data); + + let (tok2, recv_data_out, recv_data_out_valid) = recv_non_blocking(tok0, buff_data_out_r, zero!()); + let shift_buffer_data_out = RefillingBufferOutput { + data: recv_data_out.data, + length: recv_data_out.length, + error: false, + }; + send_if(tok2, out_s, recv_data_out_valid, shift_buffer_data_out); } } @@ -145,16 +168,14 @@ fn get_adjusted_value(data: u16, remainder: Remainder) -> u16 { // proc for filling probability frequencies table pub proc FseProbaFreqDecoder< RAM_DATA_WIDTH: u32, - RAM_SIZE: u32, - RAM_WORD_PARTITION_SIZE: u32, - RAM_ADDR_WIDTH: u32 = {std::clog2(RAM_SIZE)}, - RAM_NUM_PARTITIONS: u32 = {ram::num_partitions(RAM_WORD_PARTITION_SIZE, RAM_DATA_WIDTH)}, + RAM_ADDR_WIDTH: u32, + RAM_NUM_PARTITIONS: u32, DATA_WIDTH: u32 = {common::DATA_WIDTH}, - LENGTH_WIDTH: u32 = {common::BLOCK_PACKET_WIDTH} + LENGTH_WIDTH: u32 = {refilling_shift_buffer::length_width(DATA_WIDTH)}, > { type Length = bits[LENGTH_WIDTH]; - type BufferCtrl = shift_buffer::ShiftBufferCtrl; - type BufferOutput = shift_buffer::ShiftBufferOutput; + type BufferCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + type BufferOutput = refilling_shift_buffer::RefillingShiftBufferOutput; type RamWriteReq = ram::WriteReq; type RamWriteResp = ram::WriteResp; type RamReadReq = ram::ReadReq; @@ -174,8 +195,6 @@ pub proc FseProbaFreqDecoder< resp_in_s: chan out; resp_out_r: chan in; - rd_req_s: chan out; - rd_resp_r: chan in; wr_req_s: chan out; config( @@ -188,13 +207,13 @@ pub proc FseProbaFreqDecoder< buff_out_data_r: chan in, // created lookup - rd_req_s: chan out, - rd_resp_r: chan in, wr_req_s: chan out, wr_resp_r: chan in ) { - let (resp_in_s, resp_in_r) = chan("resp_in"); - let (resp_out_s, resp_out_r) = chan("resp_out"); + const CHANNEL_DEPTH = u32:1; + + let (resp_in_s, resp_in_r) = chan("resp_in"); + let (resp_out_s, resp_out_r) = chan("resp_out"); spawn ram_wr::RamWrRespHandler( resp_in_r, resp_out_s, wr_resp_r @@ -204,7 +223,7 @@ pub proc FseProbaFreqDecoder< req_r, resp_s, buff_in_ctrl_s, buff_out_data_r, resp_in_s, resp_out_r, - rd_req_s, rd_resp_r, wr_req_s, + wr_req_s, ) } @@ -213,8 +232,8 @@ pub proc FseProbaFreqDecoder< next(state: State) { let tok0 = join(); - type BufferCtrl = shift_buffer::ShiftBufferCtrl; - type BufferOutput = shift_buffer::ShiftBufferOutput; + type BufferCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + type BufferOutput = refilling_shift_buffer::RefillingShiftBufferOutput; type RamWriteReq = ram::WriteReq; type RamWriteResp = ram::WriteResp; @@ -223,15 +242,19 @@ pub proc FseProbaFreqDecoder< let do_recv_req = (state.fsm == Fsm::IDLE); + let tok0 = join(); let (tok1_0, _) = recv_if(tok0, req_r, do_recv_req, zero!()); let do_buff_data_recv = match (state.fsm) { Fsm::RECV_ACCURACY_LOG => true, Fsm::RECV_SYMBOL => true, Fsm::RECV_ZERO_PROBA => true, + Fsm::CONSUME_PADDING => state.read_bits_mod8 != u3:0, _ => false, }; let (tok1_1, out_data) = recv_if(tok0, buff_out_data_r, do_buff_data_recv, zero!()); + let data_invalid = state.data_invalid || out_data.error; + let read_bits_mod8 = state.read_bits_mod8 + out_data.length as u3; let (tok1_2, written_symbol_count, written_symb_count_valid) = recv_non_blocking(tok0, resp_out_r, state.written_symbol_count); @@ -268,6 +291,8 @@ pub proc FseProbaFreqDecoder< accuracy_log, remaining_proba, written_symbol_count, + data_invalid, + read_bits_mod8, ..state }, ) @@ -301,14 +326,16 @@ pub proc FseProbaFreqDecoder< let proba = value as s16 - s16:1; let proba_points = if proba < s16:0 { RemainingProba:1 } else { proba as RemainingProba }; - assert!(proba_points <= state.remaining_proba, "corrupted_data"); let remaining_proba = state.remaining_proba - proba_points; + let remaining_proba_invalid = proba_points > state.remaining_proba; let symbol_count = state.symbol_count + SymbolCount:1; let remainder_count = if remainder.valid { u16:1 } else { u16:0 }; - // received all the symbols - if remaining_proba == RemainingProba:0 { + let data_invalid = data_invalid || remaining_proba_invalid; + // received all the symbols or the data is invalid either due to corrupted data + // or error propagated from ShiftBuffer + if remaining_proba == RemainingProba:0 || data_invalid { ( (false, zero!()), (true, RamWriteReq { @@ -323,6 +350,8 @@ pub proc FseProbaFreqDecoder< symbol_count, remaining_proba, remainder, + data_invalid, + read_bits_mod8, ..state }, ) @@ -343,6 +372,7 @@ pub proc FseProbaFreqDecoder< symbol_count, remaining_proba, remainder, + read_bits_mod8, ..state }, ) @@ -363,6 +393,7 @@ pub proc FseProbaFreqDecoder< symbol_count, remaining_proba, remainder, + read_bits_mod8, ..state } ) @@ -393,13 +424,15 @@ pub proc FseProbaFreqDecoder< }; ( - (true, zero!()), + (false, zero!()), (false, zero!()), (false, zero!()), State { fsm: new_fsm, remainder: zero!(), written_symbol_count, + data_invalid, + read_bits_mod8, ..state }, ) @@ -416,6 +449,8 @@ pub proc FseProbaFreqDecoder< written_symbol_count, zero_proba_count, next_recv_zero, + data_invalid, + read_bits_mod8, ..state }, ) @@ -477,10 +512,17 @@ pub proc FseProbaFreqDecoder< Fsm::WAIT_FOR_COMPLETION => { if written_symbol_count == state.symbol_count { ( - (false, zero!()), + if state.read_bits_mod8 != u3:0 { + (true, BufferCtrl { length: Length:8 - state.read_bits_mod8 as Length }) + } else { + (false, zero!()) + }, (false, zero!()), - (true, Resp { status: Status::OK }), - zero!(), + (false, zero!()), + State { + fsm: Fsm::CONSUME_PADDING, + ..state + } ) } else { ( @@ -491,6 +533,20 @@ pub proc FseProbaFreqDecoder< ) } }, + Fsm::CONSUME_PADDING => { + ( + (false, zero!()), + (false, zero!()), + // sending this response is conditioned on receiving response from + // RefillingShiftBuffer if there was padding to be consumed + (true, Resp { + status: if state.data_invalid { Status::ERROR } else { Status::OK }, + accuracy_log: state.accuracy_log, + symbol_count: state.symbol_count, + }), + zero!() + ) + }, _ => { trace_fmt!("Invalid state"); fail!( @@ -514,10 +570,6 @@ pub proc FseProbaFreqDecoder< let (do_send_finish, finish_data) = resp_option; let tok2_3 = send_if(tok1, resp_s, do_send_finish, finish_data); - // unused channels - send_if(tok0, rd_req_s, false, zero!()); - recv_if(tok0, rd_resp_r, false, zero!()); - new_state } } @@ -528,29 +580,22 @@ const INST_RAM_DATA_WIDTH = get_bit_width(RemainingProba:1 << common::FSE_MAX_AC const INST_RAM_WORD_PARTITION_SIZE = INST_RAM_DATA_WIDTH; const INST_RAM_NUM_PARTITIONS = ram::num_partitions(INST_RAM_WORD_PARTITION_SIZE, INST_RAM_DATA_WIDTH); const INST_DATA_WIDTH = common::DATA_WIDTH; -const INST_LENGTH_WIDTH = common::BLOCK_PACKET_WIDTH; +const INST_LENGTH_WIDTH = refilling_shift_buffer::length_width(INST_DATA_WIDTH); proc FseProbaFreqDecoderInst { - rd_req_s: chan> out; - rd_resp_r: chan> in; - config( req_r: chan in, resp_s: chan out, - buff_in_ctrl_s: chan> out, - buff_out_data_r: chan> in, - rd_req_s: chan> out, - rd_resp_r: chan> in, + buff_in_ctrl_s: chan> out, + buff_out_data_r: chan> in, wr_req_s: chan> out, wr_resp_r: chan in) { - spawn FseProbaFreqDecoder( + spawn FseProbaFreqDecoder( req_r, resp_s, buff_in_ctrl_s, buff_out_data_r, - rd_req_s, rd_resp_r, wr_req_s, wr_resp_r + wr_req_s, wr_resp_r ); - - (rd_req_s, rd_resp_r) } init { } @@ -563,7 +608,7 @@ const TEST_RAM_ADDR_WIDTH = std::clog2(TEST_RAM_SIZE); 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_DATA_WIDTH = common::DATA_WIDTH; -const TEST_LENGTH_WIDTH = common::BLOCK_PACKET_WIDTH; +const TEST_LENGTH_WIDTH = refilling_shift_buffer::length_width(TEST_DATA_WIDTH); #[test_proc] proc FseProbaFreqDecoderTest { @@ -571,8 +616,8 @@ proc FseProbaFreqDecoderTest { type ReadResp = ram::ReadResp; type WriteReq = ram::WriteReq; type WriteResp = ram::WriteResp; - type BufferCtrl = shift_buffer::ShiftBufferCtrl; - type BufferOutput = shift_buffer::ShiftBufferOutput; + type BufferCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + type BufferOutput = refilling_shift_buffer::RefillingShiftBufferOutput; type RamAddr = bits[TEST_RAM_ADDR_WIDTH]; type RamData = uN[TEST_RAM_DATA_WIDTH]; type RamDataSigned = sN[TEST_RAM_DATA_WIDTH]; @@ -587,6 +632,8 @@ proc FseProbaFreqDecoderTest { rd_resp_r: chan in; wr_req_s: chan out; wr_resp_r: chan in; + buff_in_ctrl_s: chan out; + buff_out_data_r: chan in; config(terminator: chan out) { // RAM channels @@ -607,22 +654,20 @@ proc FseProbaFreqDecoderTest { spawn FseInputBuffer( seq_data_r, buff_in_ctrl_r, buff_out_data_s); - spawn FseProbaFreqDecoder( + spawn FseProbaFreqDecoder( req_r, resp_s, buff_in_ctrl_s, buff_out_data_r, - rd_req_s, rd_resp_r, wr_req_s, wr_resp_r); + wr_req_s, wr_resp_r); spawn ram::RamModel( rd_req_r, rd_resp_s, wr_req_r, wr_resp_s); - (terminator, seq_data_s, req_s, resp_r, rd_req_s, rd_resp_r, wr_req_s, wr_resp_r) + (terminator, seq_data_s, req_s, resp_r, rd_req_s, rd_resp_r, wr_req_s, wr_resp_r, buff_in_ctrl_s, buff_out_data_r) } init { } next(state: ()) { - let tok = join(); - // * accuracy_log = 8 // * probability frequencies: // | value | probability | bits (real) | symbol number | @@ -651,13 +696,37 @@ proc FseProbaFreqDecoderTest { RamData:5 ]; + let tok = join(); + let tok = send(tok, seq_data_s, common::SequenceData { - bytes: u64:0b111_000_00_001_011_01_11_001_110111_01110101_01100001_0011, - length: u32:47, + // 1 bit of padding for 8-bit alignment + bytes: u64:0b0111_000_00_001_011_01_11_001_110111_01110101_01100001_0011, + length: u32:48, last: false }); let tok = send(tok, req_s, zero!()); - let (tok, _) = recv(tok, resp_r); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: FseProbaFreqDecoderStatus::OK, + accuracy_log: AccuracyLog:8, + symbol_count: SymbolCount:12, + }); + + // check that the proc consumed the padding by sending request + // and checking over 100 cycles that it won't be served + let tok = send(tok, buff_in_ctrl_s, BufferCtrl { length: u7:0x1 }); + let tok = for (_, tok): (u32, token) in range(u32:0, u32:100) { + let (tok, _, valid) = recv_non_blocking(tok, buff_out_data_r, zero!()); + assert_eq(valid, false); + tok + }(tok); + // add input data to permit processing the request + let tok = send(tok, seq_data_s, common::SequenceData { + bytes: u64:1, + length: u32:1, + last: false, + }); + let (tok, _) = recv(tok, buff_out_data_r); for ((i, exp_val), tok): ((u32, RamData), token) in enumerate(EXPECTED_RAM_CONTENTS) { let tok = send(tok, rd_req_s, ReadReq { @@ -688,7 +757,12 @@ proc FseProbaFreqDecoderTest { last: false }); let tok = send(tok, req_s, zero!()); - let (tok, _) = recv(tok, resp_r); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: FseProbaFreqDecoderStatus::OK, + accuracy_log: AccuracyLog:9, + symbol_count: SymbolCount:2, + }); for ((i, exp_val), tok): ((u32, RamData), token) in enumerate(EXPECTED_RAM_CONTENTS) { let tok = send(tok, rd_req_s, ReadReq { @@ -719,7 +793,12 @@ proc FseProbaFreqDecoderTest { last: false }); let tok = send(tok, req_s, zero!()); - let (tok, _) = recv(tok, resp_r); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: FseProbaFreqDecoderStatus::OK, + accuracy_log: AccuracyLog:9, + symbol_count: SymbolCount:2, + }); for ((i, exp_val), tok): ((u32, RamData), token) in enumerate(EXPECTED_RAM_CONTENTS) { let tok = send(tok, rd_req_s, ReadReq { @@ -732,6 +811,9 @@ proc FseProbaFreqDecoderTest { tok }((tok)); + // FIXME: test error path: error propagated from ShiftBuffer and assigning more + // probability points than available + let tok = send(tok, terminator, true); } } diff --git a/xls/modules/zstd/fse_table_creator.x b/xls/modules/zstd/fse_table_creator.x new file mode 100644 index 0000000000..3eca396d90 --- /dev/null +++ b/xls/modules/zstd/fse_table_creator.x @@ -0,0 +1,652 @@ +// 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. + +// FseTableCreator generates a decoding table from a probability distribution. +// The algorithm for creating the decoding lookup is described in: +// https://datatracker.ietf.org/doc/html/rfc8878#section-4.1.1. + +import std; +import xls.examples.ram; +import xls.modules.zstd.common; +import xls.modules.zstd.ram_wr_handler as ram_wr; +import xls.modules.zstd.fse_table_iterator as fse_table_iterator; + +type SymbolCount = common::FseSymbolCount; +type AccuracyLog = common::FseAccuracyLog; + +enum Status : u4 { + RECEIVE_START = 0, + TEST_NEGATIVE_PROB = 1, + HANDLE_NEGATIVE_PROB = 2, + TEST_POSITIVE_PROB = 3, + HANDLE_POSITIVE_PROB = 4, + HANDLE_POSITIVE_PROB_WRITE_STATE_DESC = 5, + INNER_FOR_GET_POS = 6, + INNER_FOR_WRITE_SYM = 7, + LAST_FOR = 8, + GET_STATE_DESC = 9, + SET_STATE_DESC = 10, + SEND_FINISH = 11, + START_ITERATING_POS = 12, +} + +struct FseTableCreatorState { + status: Status, + req: bool, + idx: u8, + // TODO: num_symbs is u8, possibly other fields as well + num_symbs: u8, + curr_symbol: u8, + state_desc_for_symbol: u16, + accuracy_log: u16, + high_threshold: u16, + inner_for_idx: u16, + inner_for_range: u16, + dpd_data: u16, + pos: u16, +} + +type FseTableRecord = common::FseTableRecord; + +pub struct FseStartMsg { num_symbs: SymbolCount, accuracy_log: AccuracyLog } + +pub fn fse_record_to_bits(record: FseTableRecord) -> u32 { + record.base ++ record.num_of_bits ++ record.symbol +} + +#[test] +fn test_fse_record_to_bits() { + let bit = fse_record_to_bits( + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x05, base: u16:0x0020 } + ); + assert_eq(bit, u32:0x0020_05_17); +} + +pub fn bits_to_fse_record(bit: u32) -> FseTableRecord { + FseTableRecord { + symbol: bit[0:8], + num_of_bits: bit[8:16], + base: bit[16:32] + } +} + +#[test] +fn test_bits_to_fse_record() { + let record = bits_to_fse_record(u32:0x0020_05_17); + assert_eq(record, FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x05, base: u16:0x0020 }); +} + +pub proc FseTableCreator< + // Default Probability Distribution RAM parameters + DPD_RAM_DATA_WIDTH: u32, DPD_RAM_ADDR_WIDTH: u32, DPD_RAM_NUM_PARTITIONS: u32, + // FSE lookup table parameters + FSE_RAM_DATA_WIDTH: u32, FSE_RAM_ADDR_WIDTH: u32, FSE_RAM_NUM_PARTITIONS: u32, + // Temp RAM parameters + TMP_RAM_DATA_WIDTH: u32, TMP_RAM_ADDR_WIDTH: u32, TMP_RAM_NUM_PARTITIONS: u32, +> { + type State = FseTableCreatorState; + + type DpdRamReadReq = ram::ReadReq; + type DpdRamReadResp = ram::ReadResp; + + type FseRamWriteReq = ram::WriteReq; + type FseRamWriteResp = ram::WriteResp; + type FseRamReadReq = ram::ReadReq; + type FseRamReadResp = ram::ReadResp; + + type TmpRamWriteReq = ram::WriteReq; + type TmpRamWriteResp = ram::WriteResp; + type TmpRamReadReq = ram::ReadReq; + type TmpRamReadResp = ram::ReadResp; + + type IterCtrl = common::FseTableCreatorCtrl; + type IterIndex = common::FseTableIndex; + + dpd_rd_req_s: chan out; + dpd_rd_resp_r: chan in; + + // a request to start creating the FSE decoding table + fse_table_start_r: chan in; + // a response with information that the table has been saved to RAM + fse_table_finish_s: chan<()> out; + + fse_rd_req_s: chan out; + fse_rd_resp_r: chan in; + fse_wr_req_s: chan out; + fse_wr_resp_r: chan in; + + tmp_rd_req_s: chan out; + tmp_rd_resp_r: chan in; + tmp_wr_req_s: chan out; + tmp_wr_resp_r: chan in; + + it_ctrl_s: chan out; + it_index_r: chan in; + + config( + fse_table_start_r: chan in, + fse_table_finish_s: chan<()> out, + + // RAM with default probability distribution + dpd_rd_req_s: chan out, + dpd_rd_resp_r: chan in, + + // Ram with FSE decoding table + fse_rd_req_s: chan out, + fse_rd_resp_r: chan in, + fse_wr_req_s: chan out, + fse_wr_resp_r: chan in, + + tmp_rd_req_s: chan out, + tmp_rd_resp_r: chan in, + tmp_wr_req_s: chan out, + tmp_wr_resp_r: chan in + ) { + let (it_ctrl_s, it_ctrl_r) = chan("it_ctrl"); + let (it_index_s, it_index_r) = chan("it_index"); + spawn fse_table_iterator::FseTableIterator(it_ctrl_r, it_index_s); + + ( + dpd_rd_req_s, dpd_rd_resp_r, + fse_table_start_r, fse_table_finish_s, + fse_rd_req_s, fse_rd_resp_r, fse_wr_req_s, fse_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + it_ctrl_s, it_index_r + ) + } + + init { zero!() } + + next(state: State) { + const DPD_RAM_REQ_MASK_ALL = std::unsigned_max_value(); + const FSE_RAM_REQ_MASK_ALL = std::unsigned_max_value(); + const FSE_RAM_REQ_MASK_SYMBOL = uN[FSE_RAM_NUM_PARTITIONS]:1; + const TMP_RAM_REQ_MASK_ALL = std::unsigned_max_value(); + + // Type definitions repeated because of https://github.com/google/xls/issues/1368 + type DpdRamReadReq = ram::ReadReq; + type FseRamWriteReq = ram::WriteReq; + type FseRamWriteResp = ram::WriteResp; + type FseRamReadReq = ram::ReadReq; + type TmpRamWriteReq = ram::WriteReq; + type TestRamWriteResp = ram::WriteResp; + type TmpRamReadReq = ram::ReadReq; + + let tok0 = join(); + + let receive_start = (state.status == Status::RECEIVE_START); + let (tok1, fse_start_msg) = recv_if(tok0, fse_table_start_r, receive_start, zero!()); + + let get_dpd_data = state.status == Status::TEST_NEGATIVE_PROB || + state.status == Status::TEST_POSITIVE_PROB || + state.status == Status::HANDLE_POSITIVE_PROB; + + let send_dpd_req = get_dpd_data && state.req; + let tok_dpd_req = send_if(tok0, dpd_rd_req_s, send_dpd_req, + DpdRamReadReq { + addr: checked_cast(state.idx), + mask: DPD_RAM_REQ_MASK_ALL + }); + let get_dpd_resp = get_dpd_data && !state.req; + let (tok_dpd_resp, dpd_resp) = recv_if(tok0, dpd_rd_resp_r, get_dpd_resp, zero!()); + + let handle_negative_prob_req = state.status == Status::HANDLE_NEGATIVE_PROB; + let decreased_high_threshold = state.high_threshold - u16:1; + let index_as_symbol_record = FseTableRecord { + symbol: state.idx, + num_of_bits: u8:0, + base: u16:0 + }; + let fse_record_as_bits = fse_record_to_bits(index_as_symbol_record); + let fse_wr_req = if handle_negative_prob_req { + FseRamWriteReq { + addr: checked_cast(decreased_high_threshold), + data: checked_cast(fse_record_as_bits), + mask: FSE_RAM_REQ_MASK_SYMBOL + } + } else { + zero!() + }; + let tok3 = send_if(tok0, fse_wr_req_s, handle_negative_prob_req, fse_wr_req); + let handle_negative_prob_resp = (state.status == Status::HANDLE_NEGATIVE_PROB); + let (tok3, _) = recv_if(tok3, fse_wr_resp_r, handle_negative_prob_resp, FseRamWriteResp {}); + + let tok5 = send_if(tok0, tmp_wr_req_s, handle_negative_prob_req, + TmpRamWriteReq { + addr: checked_cast(state.idx), + data: checked_cast(u16:1), + mask: TMP_RAM_REQ_MASK_ALL + }); + let (tok5, _) = recv_if(tok5, tmp_wr_resp_r, handle_negative_prob_resp, TestRamWriteResp {}); + + let handle_positive_prob_write_state_desc = (state.status == Status::HANDLE_POSITIVE_PROB_WRITE_STATE_DESC); + let tok6 = send_if(tok0, tmp_wr_req_s, handle_positive_prob_write_state_desc, + TmpRamWriteReq { + addr: checked_cast(state.idx), + data: checked_cast(state.dpd_data), + mask: TMP_RAM_REQ_MASK_ALL + } + ); + let (tok6, _) = recv_if(tok6, tmp_wr_resp_r, handle_positive_prob_write_state_desc, TmpRamWriteResp {}); + + let inner_for_start_counting = state.status == Status::START_ITERATING_POS; + let negative_proba_count = (u16:1 << state.accuracy_log) - state.high_threshold; + let tok7 = send_if( tok0, it_ctrl_s, inner_for_start_counting, + IterCtrl { + accuracy_log: checked_cast(state.accuracy_log), + negative_proba_count: checked_cast(negative_proba_count), + } + ); + let inner_for_get_pos = (state.status == Status::INNER_FOR_GET_POS); + let (_, pos) = recv_if(tok0, it_index_r, inner_for_get_pos, zero!()); + + let inner_for_write_sym = state.status == Status::INNER_FOR_WRITE_SYM; + let tok4 = send_if( tok0, fse_wr_req_s, inner_for_write_sym, + FseRamWriteReq { + addr: checked_cast(state.pos), + data: checked_cast(fse_record_as_bits), + mask: FSE_RAM_REQ_MASK_SYMBOL + } + ); + + let (tok4, _) = recv_if(tok4, fse_wr_resp_r, inner_for_write_sym, FseRamWriteResp {}); + + let last_for = state.status == Status::LAST_FOR; + let tok8 = send_if(tok0, fse_rd_req_s, last_for, + FseRamReadReq { + addr: checked_cast(state.idx), + mask: FSE_RAM_REQ_MASK_SYMBOL + } + ); + let (tok8, fse_resp) = recv_if(tok8, fse_rd_resp_r, last_for, zero!()); + let fse_record = bits_to_fse_record(fse_resp.data); + + let get_state_desc = state.status == Status::GET_STATE_DESC; + let symbol = state.curr_symbol; + let tok8 = send_if(tok8, tmp_rd_req_s, get_state_desc, + TmpRamReadReq { + addr: checked_cast(symbol), + mask: TMP_RAM_REQ_MASK_ALL + } + ); + let (tok8, tmp_resp) = recv_if(tok8, tmp_rd_resp_r, get_state_desc, zero!()); + + let set_state_desc = state.status == Status::SET_STATE_DESC; + let tok9 = send_if(tok8, tmp_wr_req_s, set_state_desc, + TmpRamWriteReq { + addr: checked_cast(symbol), + data: checked_cast(state.state_desc_for_symbol + u16:1), + mask: TMP_RAM_REQ_MASK_ALL + } + ); + let (tok9, _) = recv_if(tok9, tmp_wr_resp_r, set_state_desc, TmpRamWriteResp {}); + + let num_bits = state.accuracy_log - common::highest_set_bit(state.state_desc_for_symbol); + let size = u16:1 << state.accuracy_log; + let new_state_base = (state.state_desc_for_symbol << num_bits) - size; + + let complete_record = FseTableRecord { + symbol: symbol, + num_of_bits: checked_cast(num_bits), + base: new_state_base + }; + let complete_record_as_bits = fse_record_to_bits(complete_record); + let tok10 = send_if(tok8, fse_wr_req_s, set_state_desc, + FseRamWriteReq { + addr: checked_cast(state.idx), + data: checked_cast(complete_record_as_bits), + mask: FSE_RAM_REQ_MASK_ALL + } + ); + let (tok10, _) = recv_if(tok10, fse_wr_resp_r, set_state_desc, FseRamWriteResp {}); + + let send_finish = state.status == Status::SEND_FINISH; + let tok11 = send_if(tok0, fse_table_finish_s, send_finish, ()); + + trace_fmt!("fse lookup state: {:#x}", state); + + if state.req && ( + state.status == Status::TEST_NEGATIVE_PROB || + state.status == Status::TEST_POSITIVE_PROB || + state.status == Status::HANDLE_POSITIVE_PROB) { + State { req: false, ..state } + } else { + match (state.status) { + Status::RECEIVE_START => { + State { + status: Status::TEST_NEGATIVE_PROB, + req: true, + num_symbs: checked_cast(fse_start_msg.num_symbs), + accuracy_log: checked_cast(fse_start_msg.accuracy_log), + high_threshold: u16:1 << fse_start_msg.accuracy_log, + ..state + } + }, + Status::TEST_NEGATIVE_PROB => { + if dpd_resp.data == s16:-1 as u16 { + State { status: Status::HANDLE_NEGATIVE_PROB, ..state } + } else { + let next_idx = state.idx + u8:1; + if next_idx < state.num_symbs { + State { status: Status::TEST_NEGATIVE_PROB, req: true, idx: next_idx, ..state } + } else { + State { status: Status::START_ITERATING_POS, req: true, idx: u8:0, ..state } + } + } + }, + Status::HANDLE_NEGATIVE_PROB => { + // https://github.com/facebook/zstd/blob/9f42fa0a043aa389534cf10ff086976c4c6b10a6/doc/educational_decoder/zstd_decompress.c#L2143-L2146 + let next_idx = state.idx + u8:1; + if next_idx < state.num_symbs { + State { status: Status::TEST_NEGATIVE_PROB, req: true, idx: next_idx, high_threshold: decreased_high_threshold, ..state } + } else { + State { status: Status::START_ITERATING_POS, req: true, idx: u8:0, high_threshold: decreased_high_threshold, ..state } + } + }, + Status::START_ITERATING_POS => { + State { status: Status::TEST_POSITIVE_PROB, ..state } + }, + Status::TEST_POSITIVE_PROB => { + if dpd_resp.data as s16 > s16:0 { + State { status: Status::HANDLE_POSITIVE_PROB, req: true, ..state } + } else { + let next_idx = state.idx + u8:1; + if next_idx < state.num_symbs { + State { status: Status::TEST_POSITIVE_PROB, req: true, idx: next_idx, ..state } + } else { + State { status: Status::LAST_FOR, idx: u8:0, ..state } + } + } + }, + Status::HANDLE_POSITIVE_PROB => { + // https://github.com/facebook/zstd/blob/9f42fa0a043aa389534cf10ff086976c4c6b10a6/doc/educational_decoder/zstd_decompress.c#L2161 + State { status: Status::HANDLE_POSITIVE_PROB_WRITE_STATE_DESC, dpd_data: dpd_resp.data, ..state } + }, + Status::HANDLE_POSITIVE_PROB_WRITE_STATE_DESC => { + State { status: Status::INNER_FOR_GET_POS, inner_for_idx: u16:0, inner_for_range: checked_cast(state.dpd_data), ..state } + }, + Status::INNER_FOR_GET_POS => { + // https://github.com/facebook/zstd/blob/9f42fa0a043aa389534cf10ff086976c4c6b10a6/doc/educational_decoder/zstd_decompress.c#L2165 + State { status: Status::INNER_FOR_WRITE_SYM, pos: checked_cast(pos), ..state } + }, + Status::INNER_FOR_WRITE_SYM => { + let next_idx = state.inner_for_idx + u16:1; + if next_idx < state.inner_for_range { + State { status: Status::INNER_FOR_GET_POS, inner_for_idx: next_idx, ..state } + } else { + assert!(pos == IterIndex:0, "corruption_detected_while_decompressing"); + let next_idx = state.idx + u8:1; + if next_idx < state.num_symbs { + State { status: Status::TEST_POSITIVE_PROB, req: true, idx: next_idx, ..state } + } else { + State { status: Status::LAST_FOR, idx: u8:0, ..state } + } + } + }, + Status::LAST_FOR => { + // https://github.com/facebook/zstd/blob/9f42fa0a043aa389534cf10ff086976c4c6b10a6/doc/educational_decoder/zstd_decompress.c#L2183 + State { status: Status::GET_STATE_DESC, curr_symbol: fse_record.symbol, ..state } + }, + Status::GET_STATE_DESC => { + // https://github.com/facebook/zstd/blob/9f42fa0a043aa389534cf10ff086976c4c6b10a6/doc/educational_decoder/zstd_decompress.c#L2184 + State { status: Status::SET_STATE_DESC, state_desc_for_symbol: tmp_resp.data, ..state } + }, + Status::SET_STATE_DESC => { + let next_idx = state.idx + u8:1; + if next_idx as u16 < size { + State { status: Status::LAST_FOR, idx: next_idx, ..state } + } else { + State { status: Status::SEND_FINISH, ..state } + } + }, + Status::SEND_FINISH => { State { status: Status::RECEIVE_START, ..zero!() } }, + _ => fail!("impossible_case", zero!()), + } + } + } +} + +const TEST_DPD_RAM_DATA_WIDTH = u32:16; +const TEST_DPD_RAM_SIZE = u32:256; +const TEST_DPD_RAM_ADDR_WIDTH = std::clog2(TEST_DPD_RAM_SIZE); +const TEST_DPD_RAM_WORD_PARTITION_SIZE = TEST_DPD_RAM_DATA_WIDTH; +const TEST_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_DPD_RAM_WORD_PARTITION_SIZE, TEST_DPD_RAM_DATA_WIDTH); + +const TEST_FSE_RAM_DATA_WIDTH = u32:32; +const TEST_FSE_RAM_SIZE = u32:256; +const TEST_FSE_RAM_ADDR_WIDTH = std::clog2(TEST_FSE_RAM_SIZE); +const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_WIDTH / u32:3; +const TEST_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_FSE_RAM_WORD_PARTITION_SIZE, TEST_FSE_RAM_DATA_WIDTH); + +const TEST_TMP_RAM_DATA_WIDTH = u32:16; +const TEST_TMP_RAM_SIZE = u32:256; +const TEST_TMP_RAM_ADDR_WIDTH = std::clog2(TEST_TMP_RAM_SIZE); +const TEST_TMP_RAM_WORD_PARTITION_SIZE = TEST_TMP_RAM_DATA_WIDTH; +const TEST_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_TMP_RAM_WORD_PARTITION_SIZE, TEST_TMP_RAM_DATA_WIDTH); + +proc FseTableCreatorInst { + type DpdRamReadReq = ram::ReadReq; + type DpdRamReadResp = ram::ReadResp; + + type FseRamReadReq = ram::ReadReq; + type FseRamReadResp = ram::ReadResp; + type FseRamWriteReq = ram::WriteReq; + type FseRamWriteResp = ram::WriteResp; + + type TmpRamWriteReq = ram::WriteReq; + type TmpRamWriteResp = ram::WriteResp; + type TmpRamReadReq = ram::ReadReq; + type TmpRamReadResp = ram::ReadResp; + + config( + fse_table_start_r: chan in, + fse_table_finish_s: chan<()> out, + + dpd_rd_req_s: chan out, + dpd_rd_resp_r: chan in, + + fse_rd_req_s: chan out, + fse_rd_resp_r: chan in, + fse_wr_req_s: chan out, + fse_wr_resp_r: chan in, + + tmp_rd_req_s: chan out, + tmp_rd_resp_r: chan in, + tmp_wr_req_s: chan out, + tmp_wr_resp_r: chan in + ) { + spawn FseTableCreator< + TEST_DPD_RAM_DATA_WIDTH, TEST_DPD_RAM_ADDR_WIDTH, TEST_DPD_RAM_NUM_PARTITIONS, + TEST_FSE_RAM_DATA_WIDTH, TEST_FSE_RAM_ADDR_WIDTH, TEST_FSE_RAM_NUM_PARTITIONS, + TEST_TMP_RAM_DATA_WIDTH, TEST_TMP_RAM_ADDR_WIDTH, TEST_TMP_RAM_NUM_PARTITIONS, + >( + fse_table_start_r, fse_table_finish_s, + dpd_rd_req_s, dpd_rd_resp_r, + fse_rd_req_s, fse_rd_resp_r, fse_wr_req_s, fse_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r + ); + } + + init { } + + next(state: ()) { } +} + +const TEST_OFFSET_CODE_TABLE = FseTableRecord[32]:[ + FseTableRecord { symbol: u8:0, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:6, num_of_bits: u8:4, base: u16:0 }, + FseTableRecord { symbol: u8:9, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:15, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:21, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:3, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:7, num_of_bits: u8:4, base: u16:0 }, + FseTableRecord { symbol: u8:12, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:18, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:23, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:5, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:8, num_of_bits: u8:4, base: u16:0 }, + FseTableRecord { symbol: u8:14, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:20, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:2, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:7, num_of_bits: u8:4, base: u16:16 }, + FseTableRecord { symbol: u8:11, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:17, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:22, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:4, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:8, num_of_bits: u8:4, base: u16:16 }, + FseTableRecord { symbol: u8:13, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:19, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:1, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:6, num_of_bits: u8:4, base: u16:16 }, + FseTableRecord { symbol: u8:10, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:16, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:28, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:27, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:26, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:25, num_of_bits: u8:5, base: u16:0 }, + FseTableRecord { symbol: u8:24, num_of_bits: u8:5, base: u16:0 }, +]; + +#[test_proc] +proc FseTableCreatorTest { + type DpdRamReadReq = ram::ReadReq; + type DpdRamReadResp = ram::ReadResp; + type DpdRamWriteReq = ram::WriteReq; + type DpdRamWriteResp = ram::WriteResp; + + type FseRamReadReq = ram::ReadReq; + type FseRamReadResp = ram::ReadResp; + type FseRamWriteReq = ram::WriteReq; + type FseRamWriteResp = ram::WriteResp; + + type TmpRamReadReq = ram::ReadReq; + type TmpRamReadResp = ram::ReadResp; + type TmpRamWriteReq = ram::WriteReq; + type TmpRamWriteResp = ram::WriteResp; + + terminator: chan out; + fse_table_start_s: chan out; + fse_table_finish_r: chan<()> in; + + dpd_wr_req_s: chan out; + dpd_wr_resp_r: chan in; + + fse_rd_req_s: chan out; + fse_rd_resp_r: chan in; + + config(terminator: chan out) { + let (dpd_rd_req_s, dpd_rd_req_r) = chan("dpd_rd_req"); + let (dpd_rd_resp_s, dpd_rd_resp_r) = chan("dpd_rd_resp"); + let (dpd_wr_req_s, dpd_wr_req_r) = chan("dpd_wr_req"); + let (dpd_wr_resp_s, dpd_wr_resp_r) = chan("dpd_wr_resp"); + + spawn ram::RamModel< + TEST_DPD_RAM_DATA_WIDTH, TEST_DPD_RAM_SIZE, TEST_DPD_RAM_WORD_PARTITION_SIZE>( + dpd_rd_req_r, dpd_rd_resp_s, dpd_wr_req_r, dpd_wr_resp_s); + + let (fse_rd_req_s, fse_rd_req_r) = chan("fse_rd_req"); + let (fse_rd_resp_s, fse_rd_resp_r) = chan("fse_rd_resp"); + let (fse_wr_req_s, fse_wr_req_r) = chan("fse_wr_req"); + let (fse_wr_resp_s, fse_wr_resp_r) = chan("fse_wr_resp"); + + spawn ram::RamModel< + TEST_FSE_RAM_DATA_WIDTH, TEST_FSE_RAM_SIZE, TEST_FSE_RAM_WORD_PARTITION_SIZE>( + fse_rd_req_r, fse_rd_resp_s, fse_wr_req_r, fse_wr_resp_s); + + let (tmp_rd_req_s, tmp_rd_req_r) = chan("tmp_rd_req"); + let (tmp_rd_resp_s, tmp_rd_resp_r) = chan("tmp_rd_resp"); + let (tmp_wr_req_s, tmp_wr_req_r) = chan("tmp_wr_req"); + let (tmp_wr_resp_s, tmp_wr_resp_r) = chan("tmp_wr_resp"); + + spawn ram::RamModel< + TEST_TMP_RAM_DATA_WIDTH, TEST_TMP_RAM_SIZE, TEST_TMP_RAM_WORD_PARTITION_SIZE>( + tmp_rd_req_r, tmp_rd_resp_s, tmp_wr_req_r, tmp_wr_resp_s); + + let (fse_table_start_s, fse_table_start_r) = chan("fse_table_start"); + let (fse_table_finish_s, fse_table_finish_r) = chan<()>("fse_table_finish"); + + spawn FseTableCreator< + TEST_DPD_RAM_DATA_WIDTH, TEST_DPD_RAM_ADDR_WIDTH, TEST_DPD_RAM_NUM_PARTITIONS, + TEST_FSE_RAM_DATA_WIDTH, TEST_FSE_RAM_ADDR_WIDTH, TEST_FSE_RAM_NUM_PARTITIONS, + TEST_TMP_RAM_DATA_WIDTH, TEST_TMP_RAM_ADDR_WIDTH, TEST_TMP_RAM_NUM_PARTITIONS, + >( + fse_table_start_r, fse_table_finish_s, + dpd_rd_req_s, dpd_rd_resp_r, + fse_rd_req_s, fse_rd_resp_r, fse_wr_req_s, fse_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r + ); + + ( + terminator, + fse_table_start_s, fse_table_finish_r, + dpd_wr_req_s, dpd_wr_resp_r, + fse_rd_req_s, fse_rd_resp_r, + ) + } + + init { } + + next(state: ()) { + const DPD_RAM_REQ_MASK_ALL = std::unsigned_max_value(); + const FSE_RAM_REQ_MASK_ALL = std::unsigned_max_value(); + + let tok = join(); + + let dist_arr_length = array_size(common::FSE_OFFSET_DEFAULT_DIST); + let accuracy_log = AccuracyLog:5; + // 1. Fill the DPD Ram with default probability distribution + let tok = for (idx, tok): (u32, token) in range(u32:0, dist_arr_length) { + let tok = send( + tok, dpd_wr_req_s, + DpdRamWriteReq { + addr: checked_cast(idx), + data: checked_cast( + std::to_unsigned(common::FSE_OFFSET_DEFAULT_DIST[idx]) + ), + mask: DPD_RAM_REQ_MASK_ALL + }); + let (tok, _) = recv(tok, dpd_wr_resp_r); + (tok) + }(tok); + // 2. send start request over the fse_table_start_s channel + let tok = send(tok, fse_table_start_s, FseStartMsg { + num_symbs: checked_cast(dist_arr_length), + accuracy_log + }); + // 3. wait for finish response on fse_table_finish_r channel + let (tok, _) = recv(tok, fse_table_finish_r); + // 4. Read FSE Ram and verify values + // (https://datatracker.ietf.org/doc/html/rfc8878#section-appendix.a) + let code_length = u16:1 << accuracy_log; + let tok = for (idx, tok): (u16, token) in range(u16:0, code_length) { + let tok = send(tok, fse_rd_req_s, + FseRamReadReq { + addr: checked_cast(idx), + mask: FSE_RAM_REQ_MASK_ALL + } + ); + let (tok, resp) = recv(tok, fse_rd_resp_r); + let fse_record = bits_to_fse_record(resp.data); + assert_eq(fse_record.symbol, TEST_OFFSET_CODE_TABLE[idx].symbol); + assert_eq(fse_record.num_of_bits, TEST_OFFSET_CODE_TABLE[idx].num_of_bits); + assert_eq(fse_record.base, TEST_OFFSET_CODE_TABLE[idx].base); + (tok) + }(tok); + + let tok = send(tok, terminator, true); + } +} diff --git a/xls/modules/zstd/fse_table_iterator.x b/xls/modules/zstd/fse_table_iterator.x new file mode 100644 index 0000000000..f652d793ef --- /dev/null +++ b/xls/modules/zstd/fse_table_iterator.x @@ -0,0 +1,124 @@ +// 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 proc provides the order in which the FSE decoding table should be +// filled with symbols. The algorithm is described in: +// https://datatracker.ietf.org/doc/html/rfc8878#section-4.1.1 + +import std; +import xls.modules.zstd.common; + +type Reset = bool; +type Index = common::FseTableIndex; +type Ctrl = common::FseTableCreatorCtrl; + +type AccuracyLog = common::FseAccuracyLog; +type SymbolCount = common::FseSymbolCount; + +enum Status : u1 { + CONFIGURE = 0, + SEND = 1, +} + +struct State { status: Status, ctrl: Ctrl, cnt: u16, pos: u16 } + +pub proc FseTableIterator { + ctrl_r: chan in; + idx_s: chan out; + + config( + ctrl_r: chan in, + idx_s: chan out + ) { (ctrl_r, idx_s) } + + init { zero!() } + + next(state: State) { + const ZERO_STATE = zero!(); + const ZERO_IDX_OPTION = (false, u16:0); + + let tok0 = join(); + + let do_recv_ctrl = state.status == Status::CONFIGURE; + let (tok1, ctrl) = recv_if(tok0, ctrl_r, do_recv_ctrl, zero!()); + + let ((do_send_idx, idx), new_state) = match (state.status) { + Status::CONFIGURE => { + ((true, u16:0), State { ctrl, status: Status::SEND, ..ZERO_STATE }) + }, + Status::SEND => { + let size = u16:1 << state.ctrl.accuracy_log; + let high_threshold = size - state.ctrl.negative_proba_count as u16; + let step = (size >> 1) + (size >> 3) + u16:3; + let mask = size - u16:1; + + let pos = (state.pos + step) & mask; + + let valid = pos < high_threshold; + let next_cnt = state.cnt + u16:1; + let last = (valid && (next_cnt == high_threshold - u16:1)); + + if last { + ((true, pos), ZERO_STATE) + } else if valid { + ((true, pos), State { cnt: next_cnt, pos, ..state }) + } else { + (ZERO_IDX_OPTION, State { cnt: state.cnt, pos, ..state }) + } + }, + _ => fail!("incorrect_state", (ZERO_IDX_OPTION, ZERO_STATE)), + }; + + let tok2 = send_if(tok1, idx_s, do_send_idx, checked_cast(idx)); + if do_send_idx { trace_fmt!("[IO]: Send index: {}", idx); } else { }; + + new_state + } +} + +const TEST_EXPECTRED_IDX = Index[27]:[ + Index:0, Index:23, Index:14, Index:5, Index:19, Index:10, Index:1, Index:24, Index:15, Index:6, + Index:20, Index:11, Index:2, Index:25, Index:16, Index:7, Index:21, Index:12, Index:3, Index:26, + Index:17, Index:8, Index:22, Index:13, Index:4, Index:18, Index:9, +]; + +#[test_proc] +proc FseTableIteratorTest { + terminator: chan out; + ctrl_s: chan out; + idx_r: chan in; + + config(terminator: chan out) { + let (ctrl_s, ctrl_r) = chan("ctrl"); + let (idx_s, idx_r) = chan("idx"); + + spawn FseTableIterator(ctrl_r, idx_s); + (terminator, ctrl_s, idx_r) + } + + init { } + + next(state: ()) { + let tok = join(); + let tok = send( + tok, ctrl_s, Ctrl { accuracy_log: AccuracyLog:5, negative_proba_count: SymbolCount:5 }); + let tok = for (exp_idx, tok): (Index, token) in TEST_EXPECTRED_IDX { + let (tok, idx) = recv(tok, idx_r); + assert_eq(idx, exp_idx); + (tok) + }(tok); + + send(tok, terminator, true); + } +} From b6b971b9558f38a8158f927ad886e6e2c7a8a313 Mon Sep 17 00:00:00 2001 From: Ryszard Rozak Date: Wed, 24 Apr 2024 09:55:46 +0200 Subject: [PATCH 38/85] modules/zstd: Add CommandConstructor proc Internal-tag: [#58422] Signed-off-by: Ryszard Rozak --- xls/modules/zstd/BUILD | 75 ++++++ xls/modules/zstd/command_constructor.x | 339 +++++++++++++++++++++++++ 2 files changed, 414 insertions(+) create mode 100644 xls/modules/zstd/command_constructor.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 3360a55e03..55ae6ffdf7 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1472,6 +1472,80 @@ place_and_route( target_die_utilization_percentage = "10", ) +xls_dslx_library( + name = "command_constructor_dslx", + srcs = ["command_constructor.x"], + deps = [ + ":common_dslx", + ], +) + +xls_dslx_test( + name = "command_constructor_dslx_test", + dslx_test_args = {"compare": "none"}, + library = ":command_constructor_dslx", +) + +xls_dslx_verilog( + name = "command_constructor_verilog", + codegen_args = { + "module_name": "CommandConstructor", + "delay_model": "asap7", + "pipeline_stages": "2", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "CommandConstructor", + library = ":command_constructor_dslx", + verilog_file = "command_constructor.v", +) + +xls_benchmark_ir( + name = "command_constructor_opt_ir_benchmark", + src = ":command_constructor_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "8", + "delay_model": "asap7", + }, +) + +xls_benchmark_verilog( + name = "command_constructor_verilog_benchmark", + verilog_target = "command_constructor_verilog", +) + +verilog_library( + name = "command_constructor_lib", + srcs = [ + ":command_constructor.v", + ], +) + +synthesize_rtl( + name = "command_constructor_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "CommandConstructor", + deps = [ + ":command_constructor_lib", + ], +) + +benchmark_synth( + name = "command_constructor_benchmark_synth", + synth_target = ":command_constructor_asap7", +) + +place_and_route( + name = "command_constructor_place_and_route", + clock_period = "750", + core_padding_microns = 2, + min_pin_distance = "0.3", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":command_constructor_asap7", + target_die_utilization_percentage = "10", +) + xls_dslx_library( name = "fse_dec_dslx", srcs = [ @@ -1559,3 +1633,4 @@ place_and_route( tags = ["manual"], target_die_utilization_percentage = "10", ) + diff --git a/xls/modules/zstd/command_constructor.x b/xls/modules/zstd/command_constructor.x new file mode 100644 index 0000000000..9fd3f9eb9d --- /dev/null +++ b/xls/modules/zstd/command_constructor.x @@ -0,0 +1,339 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains implementation of CommandConstructor, which adjusts +// the data obtained from the SequenceDecoder for the SequenceExecutor block. +// It can receive two types of values: sequences that are copied directly +// to the output, and length-only literals packets, which before sending +// them to the output, should be redirected to the LiteralsBuffer to be filled +// with actual data. + +import std; +import xls.modules.zstd.common; + +type SequenceExecutorMessageType = common::SequenceExecutorMessageType; +type CopyOrMatchContent = common::CopyOrMatchContent; +type CopyOrMatchLength = common::CopyOrMatchLength; + +type SequenceExecutorPacket = common::SequenceExecutorPacket; +type LiteralsBufferCtrl = common::LiteralsBufferCtrl; +type CommandConstructorData = common::CommandConstructorData; +type ExtendedBlockDataPacket = common::ExtendedBlockDataPacket; +type BlockSyncData = common::BlockSyncData; +type BlockDataPacket = common::BlockDataPacket; + +enum Status : u1 { + RECV_COMMAND = 0, + RECV_LITERALS = 1, +} + +struct State { + status: Status, + received_literals: CopyOrMatchLength, + literals_to_receive: CopyOrMatchLength, + sync: BlockSyncData, +} + +pub proc CommandConstructor { + sequence_decoder_r: chan in; + command_aggregator_s: chan out; + literals_buffer_resp_r: chan in; + literals_buffer_req_s: chan out; + + config(sequence_decoder_r: chan in, + command_aggregator_s: chan out, + literals_buffer_resp_r: chan in, + literals_buffer_req_s: chan out) { + (sequence_decoder_r, command_aggregator_s, literals_buffer_resp_r, literals_buffer_req_s) + } + + init { zero!() } + + next(state: State) { + let tok0 = join(); + + let recv_command = state.status == Status::RECV_COMMAND; + let (tok1_0, command) = + recv_if(tok0, sequence_decoder_r, recv_command, zero!()); + + let recv_literals = state.status == Status::RECV_LITERALS; + let (tok1_1, literals) = + recv_if(tok0, literals_buffer_resp_r, recv_literals, zero!()); + + let tok1 = join(tok1_0, tok1_1); + + let (new_state, do_send_command, do_send_literals_req) = match (state.status) { + Status::RECV_COMMAND => { + if command.data.msg_type == SequenceExecutorMessageType::LITERAL { + ( + State { + status: Status::RECV_LITERALS, + received_literals: CopyOrMatchLength:0, + literals_to_receive: command.data.length, + sync: command.sync, + }, false, true, + ) + } else { + (zero!(), true, false) + } + }, + Status::RECV_LITERALS => { + let received_literals = state.received_literals + literals.length; + if received_literals < state.literals_to_receive { + (State { received_literals, ..state }, true, false) + } else { + assert!( + received_literals >= state.literals_to_receive, + "Too many literals received"); + (zero!(), true, false) + } + }, + _ => fail!("impossible_case", (zero!(), false, false)), + }; + + let req = LiteralsBufferCtrl { length: command.data.length as u32, last: command.data.last}; // FIXME: remove cast after unifying types of 'length' fields + send_if(tok1, literals_buffer_req_s, do_send_literals_req, req); + + let resp = match(state.status) { + // sent only if the original message was of type SEQUENCE + Status::RECV_COMMAND => ExtendedBlockDataPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { + last: command.data.last, + last_block: command.sync.last_block, + id: command.sync.id, + data: command.data.content, + length: command.data.length as u32, // FIXME: remove cast after unifying types of 'length' fields + } + }, + Status::RECV_LITERALS => ExtendedBlockDataPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { + last: literals.last, + last_block: state.sync.last_block, + id: state.sync.id, + data: literals.content, + length: literals.length as u32, // FIXME: remove cast after unifying types of 'length' fields + } + }, + _ => fail!("resp_match_unreachable", zero!()) + }; + send_if(tok1, command_aggregator_s, do_send_command, resp); + + new_state + } +} + +// Tests + +enum FakeLiteralsBufferStatus : u1 { + RECV = 0, + SEND = 1, +} + +struct FakeLiteralsBufferState { + status: FakeLiteralsBufferStatus, + literals_left_to_send: CopyOrMatchLength, +} + +pub fn get_dummy_content(length: CopyOrMatchLength) -> CopyOrMatchContent { + let value = std::unsigned_max_value() >> (CopyOrMatchLength:64 - length); + value as CopyOrMatchContent +} + +proc FakeLiteralsBuffer { + literals_buffer_resp_s: chan out; + literals_buffer_req_r: chan in; + + config(literals_buffer_resp_s: chan out, + literals_buffer_req_r: chan in) { + (literals_buffer_resp_s, literals_buffer_req_r) + } + + init { zero!() } + + next(state: FakeLiteralsBufferState) { + let tok = join(); + let do_recv_req = state.status == FakeLiteralsBufferStatus::RECV; + let (tok, resp) = + recv_if(tok, literals_buffer_req_r, do_recv_req, zero!()); + + let (new_state, do_send, resp) = match (state.status) { + FakeLiteralsBufferStatus::RECV => { + ( + FakeLiteralsBufferState { + status: FakeLiteralsBufferStatus::SEND, + literals_left_to_send: resp.length as u64 // FIXME: remove cast after unifying types of 'length' fields + }, false, zero!(), + ) + }, + FakeLiteralsBufferStatus::SEND => { + let length = std::umin(state.literals_left_to_send, CopyOrMatchLength:64); + let next_left_to_send = state.literals_left_to_send - length; + let last = next_left_to_send == CopyOrMatchLength:0; + let resp = SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + content: get_dummy_content(length), + length, + last + }; + + if last { + ( + FakeLiteralsBufferState { + status: FakeLiteralsBufferStatus::RECV, + literals_left_to_send: CopyOrMatchLength:0 + }, true, resp, + ) + } else { + ( + FakeLiteralsBufferState { + status: FakeLiteralsBufferStatus::SEND, + literals_left_to_send: next_left_to_send + }, true, resp, + ) + } + }, + _ => { + fail!( + "impossible_case", + (zero!(), false, zero!())) + }, + }; + + send_if(tok, literals_buffer_resp_s, do_send, resp); + new_state + } +} + +fn cmd_constr_to_ext_block(data: CommandConstructorData) -> ExtendedBlockDataPacket { + ExtendedBlockDataPacket { + msg_type: data.data.msg_type, + packet: BlockDataPacket { + last: data.data.last, + last_block: data.sync.last_block, + id: data.sync.id, + data: data.data.content, + length: data.data.length as u32, + } + } +} + +#[test_proc] +proc CommandConstructorTest { + terminator: chan out; + sequence_decoder_s: chan out; + command_aggregator_r: chan in; + + config(terminator: chan out) { + let (sequence_decoder_s, sequence_decoder_r) = chan("sequence_decoder"); + let (command_aggregator_s, command_aggregator_r) = chan("command_aggregator"); + + let (literals_buffer_resp_s, literals_buffer_resp_r) = chan("literals_buffer_resp"); + let (literals_buffer_req_s, literals_buffer_req_r) = chan("literals_buffer_req"); + + spawn CommandConstructor( + sequence_decoder_r, command_aggregator_s, literals_buffer_resp_r, literals_buffer_req_s); + + spawn FakeLiteralsBuffer(literals_buffer_resp_s, literals_buffer_req_r); + + (terminator, sequence_decoder_s, command_aggregator_r) + } + + init { } + + next(state: ()) { + const EMPTY_PACKET = zero!(); + + let tok = join(); + + let sequence_packet1 = CommandConstructorData { + data: SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + content: CopyOrMatchContent:11, + length: CopyOrMatchLength:4, + last: true, + }, + sync: BlockSyncData { + id: u32:1234, + last_block: false, + }, + }; + let tok = send(tok, sequence_decoder_s, sequence_packet1); + let (tok, resp) = recv(tok, command_aggregator_r); + assert_eq(cmd_constr_to_ext_block(sequence_packet1), resp); + + let literals_packet1 = CommandConstructorData { + data: SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, length: CopyOrMatchLength:4, + ..EMPTY_PACKET + }, + sync: BlockSyncData { + id: u32:1234, + last_block: false, + }, + }; + let tok = send(tok, sequence_decoder_s, literals_packet1); + let (tok, resp) = recv(tok, command_aggregator_r); + assert_eq(get_dummy_content(CopyOrMatchLength:4), resp.packet.data); + + let literals_packet2 = CommandConstructorData { + data: SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, length: CopyOrMatchLength:65, + ..EMPTY_PACKET + }, + sync: BlockSyncData { + id: u32:1234, + last_block: false, + }, + }; + let tok = send(tok, sequence_decoder_s, literals_packet2); + let (tok, resp) = recv(tok, command_aggregator_r); + assert_eq(get_dummy_content(CopyOrMatchLength:64), resp.packet.data); + let (tok, resp) = recv(tok, command_aggregator_r); + assert_eq(get_dummy_content(CopyOrMatchLength:1), resp.packet.data); + + let literals_packet3 = CommandConstructorData { + data: SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, length: CopyOrMatchLength:64, + ..EMPTY_PACKET + }, + sync: BlockSyncData { + id: u32:1234, + last_block: false, + }, + }; + let tok = send(tok, sequence_decoder_s, literals_packet3); + let (tok, resp) = recv(tok, command_aggregator_r); + assert_eq(get_dummy_content(CopyOrMatchLength:64), resp.packet.data); + + let literals_packet4 = CommandConstructorData { + data: SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, length: CopyOrMatchLength:128, + ..EMPTY_PACKET + }, + sync: BlockSyncData { + id: u32:1234, + last_block: false, + }, + }; + let tok = send(tok, sequence_decoder_s, literals_packet4); + let (tok, resp) = recv(tok, command_aggregator_r); + assert_eq(get_dummy_content(CopyOrMatchLength:64), resp.packet.data); + let (tok, resp) = recv(tok, command_aggregator_r); + assert_eq(get_dummy_content(CopyOrMatchLength:64), resp.packet.data); + + let tok = send(tok, terminator, true); + } +} From 824c2cee591ce9d44c8175dca5c6193afc98844f Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Thu, 25 Apr 2024 12:48:06 +0200 Subject: [PATCH 39/85] modules/zstd: Add Ram demux Internal-tag: [#58557] Co-authored-by: Ryszard Rozak Co-authored-by: Maciej Torhan Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 159 +++++++ xls/modules/zstd/ram_demux.x | 782 +++++++++++++++++++++++++++++++++++ 2 files changed, 941 insertions(+) create mode 100644 xls/modules/zstd/ram_demux.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 55ae6ffdf7..6769a643be 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1546,6 +1546,165 @@ place_and_route( target_die_utilization_percentage = "10", ) +xls_dslx_library( + name = "ram_demux_dslx", + srcs = ["ram_demux.x"], + deps = [ + "//xls/examples:ram_dslx", + ], +) + +xls_dslx_test( + name = "ram_demux_dslx_test", + dslx_test_args = {"compare": "none"}, + library = ":ram_demux_dslx", +) + +xls_dslx_verilog( + name = "ram_demux_verilog", + codegen_args = { + "module_name": "RamDemux", + "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 = "ram_demux__rd_req{}_s".format(num), + rd_resp = "ram_demux__rd_resp{}_r".format(num), + wr_req = "ram_demux__wr_req{}_s".format(num), + wr_resp = "ram_demux__wr_resp{}_r".format(num), + ) + for num in range(2) + ]), + "pipeline_stages": "6", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "RamDemuxInst", + library = ":ram_demux_dslx", + opt_ir_args = { + "inline_procs": "true", + "top": "__ram_demux__RamDemuxInst__RamDemux_0__5_8_0_8_5_next", + }, + verilog_file = "ram_demux.v", +) + +xls_benchmark_ir( + name = "ram_demux_opt_ir_benchmark", + src = "ram_demux_verilog.opt.ir", + benchmark_ir_args = { + "top": "__ram_demux__RamDemuxInst__RamDemux_0__5_8_0_8_5_next", + }, + codegen_args = { + "pipeline_stages": "10", + }, +) + +verilog_library( + name = "ram_demux_verilog_lib", + srcs = [ + ":ram_demux.v", + ], +) + +synthesize_rtl( + name = "ram_demux_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "RamDemux", + deps = [ + ":ram_demux_verilog_lib", + ], +) + +benchmark_synth( + name = "ram_demux_benchmark_synth", + synth_target = ":ram_demux_synth_asap7", +) + +place_and_route( + name = "ram_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 = ":ram_demux_synth_asap7", + target_die_utilization_percentage = "5", +) + +xls_dslx_verilog( + name = "ram_demux_naive_verilog", + codegen_args = { + "module_name": "RamDemuxNaive", + "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 = "ram_demux__rd_req{}_s".format(num), + rd_resp = "ram_demux__rd_resp{}_r".format(num), + wr_req = "ram_demux__wr_req{}_s".format(num), + wr_resp = "ram_demux__wr_resp{}_r".format(num), + ) + for num in range(2) + ]), + "pipeline_stages": "6", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "RamDemuxNaiveInst", + library = ":ram_demux_dslx", + opt_ir_args = { + "top": "__ram_demux__RamDemuxNaiveInst__RamDemuxNaive_0__5_8_0_8_next", + }, + verilog_file = "ram_demux_naive.v", +) + +xls_benchmark_ir( + name = "ram_demux_naive_opt_ir_benchmark", + src = "ram_demux_naive_verilog.opt.ir", + benchmark_ir_args = { + "top": "__ram_demux__RamDemuxNaiveInst__RamDemuxNaive_0__5_8_0_8_next", + }, + codegen_args = { + "pipeline_stages": "10", + }, +) + +verilog_library( + name = "ram_demux_naive_verilog_lib", + srcs = [ + ":ram_demux_naive.v", + ], +) + +synthesize_rtl( + name = "ram_demux_naive_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "RamDemuxNaive", + deps = [ + ":ram_demux_naive_verilog_lib", + ], +) + +benchmark_synth( + name = "ram_demux_naive_benchmark_synth", + synth_target = ":ram_demux_naive_synth_asap7", +) + +place_and_route( + name = "ram_demux_naive_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 = ":ram_demux_naive_synth_asap7", + target_die_utilization_percentage = "5", +) + xls_dslx_library( name = "fse_dec_dslx", srcs = [ diff --git a/xls/modules/zstd/ram_demux.x b/xls/modules/zstd/ram_demux.x new file mode 100644 index 0000000000..2b188de92b --- /dev/null +++ b/xls/modules/zstd/ram_demux.x @@ -0,0 +1,782 @@ +// 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 a RamDemux implementation that can be used to connect +// a single proc with two RAM instances, by using a single RAM interface and +// switching between the RAMs, when requested. The switching occurs only after +// each request has received the corresponding response. +// Additionally, a "naive" implementation is provided that does not ensure +// any synchronization when switching RAMs. + +import std; +import xls.examples.ram; + +// First bit of queue is not used to simplify the implementation. +// Queue end is encoded using one-hot and if it is equal to 1, +// then the queue is empty. Queue length should be greater or equal +// to RAM latency, otherways the demux might not work properly. +struct RamDemuxState { + sel: u1, + sel_q_rd: uN[QUEUE_LEN + u32:1], + sel_q_wr: uN[QUEUE_LEN + u32:1], + sel_q_rd_end: uN[QUEUE_LEN + u32:1], + sel_q_wr_end: uN[QUEUE_LEN + u32:1], +} + +pub proc RamDemux< + ADDR_WIDTH: u32, + DATA_WIDTH: u32, + NUM_PARTITIONS: u32, + INIT_SEL: u1 = {u1:0}, + QUEUE_LEN: u32 = {u32:5} +> { + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp; + + type Queue = uN[QUEUE_LEN + u32:1]; + + sel_req_r: chan in; + sel_resp_s: chan<()> out; + + rd_req_r: chan in; + rd_resp_s: chan out; + wr_req_r: chan in; + wr_resp_s: chan out; + + rd_req0_s: chan out; + rd_resp0_r: chan in; + wr_req0_s: chan out; + wr_resp0_r: chan in; + + rd_req1_s: chan out; + rd_resp1_r: chan in; + wr_req1_s: chan out; + wr_resp1_r: chan in; + + config( + sel_req_r: chan in, + sel_resp_s: chan<()> out, + rd_req_r: chan in, + rd_resp_s: chan out, + wr_req_r: chan in, + wr_resp_s: chan out, + + rd_req0_s: chan out, + rd_resp0_r: chan in, + wr_req0_s: chan out, + wr_resp0_r: chan in, + + rd_req1_s: chan out, + rd_resp1_r: chan in, + wr_req1_s: chan out, + wr_resp1_r: chan in + ) { + ( + sel_req_r, sel_resp_s, + rd_req_r, rd_resp_s, wr_req_r, wr_resp_s, + rd_req0_s, rd_resp0_r, wr_req0_s, wr_resp0_r, + rd_req1_s, rd_resp1_r, wr_req1_s, wr_resp1_r, + ) + } + + init { + RamDemuxState { + sel: INIT_SEL, + sel_q_rd: Queue:0, + sel_q_wr: Queue:0, + sel_q_rd_end: Queue:1, + sel_q_wr_end: Queue:1 + } + } + + next(state: RamDemuxState) { + let sel = state.sel; + let sel_q_rd = state.sel_q_rd; + let sel_q_wr = state.sel_q_wr; + let sel_q_rd_end = state.sel_q_rd_end; + let sel_q_wr_end = state.sel_q_wr_end; + + let tok = join(); + + // receive requests from input channel + // conditional reading is not required here ase the queue would + // never be full (assuming its length is greater or equal to RAM + // latency), as there would be at maxiumum one new request added + // to queue per cycle and the response for the first one should + // be received after number of cycles equal to RAM latency (which + // is less or equal to queue length) + let (tok1_0, rd_req, rd_req_valid) = recv_non_blocking(tok, rd_req_r, zero!()); + let (sel_q_rd_end, sel_q_rd) = if rd_req_valid { + (sel_q_rd_end << u32:1, (sel_q_rd << u32:1) | ((sel as Queue) << u32:1)) + } else { + (sel_q_rd_end, sel_q_rd) + }; + + let (tok1_1, wr_req, wr_req_valid) = recv_non_blocking(tok, wr_req_r, zero!()); + let (sel_q_wr_end, sel_q_wr) = if wr_req_valid { + (sel_q_wr_end << u32:1, (sel_q_wr << u32:1) | ((sel as Queue) << u32:1)) + } else { + (sel_q_wr_end, sel_q_wr) + }; + + // send requests to output channel 0 + let rd_req0_cond = ((sel_q_rd >> u32:1) as u1 == u1:0 && rd_req_valid); + let tok1_2 = send_if(tok, rd_req0_s, rd_req0_cond, rd_req); + + let wr_req0_cond = ((sel_q_wr >> u32:1) as u1 == u1:0 && wr_req_valid); + let tok1_3 = send_if(tok, wr_req0_s, wr_req0_cond, wr_req); + + // send requests to output channel 1 + let rd_req1_cond = ((sel_q_rd >> u32:1) as u1 == u1:1 && rd_req_valid); + let tok1_4 = send_if(tok, rd_req1_s, rd_req1_cond, rd_req); + + let wr_req1_cond = ((sel_q_wr >> u32:1) as u1 == u1:1 && wr_req_valid); + let tok1_5 = send_if(tok, wr_req1_s, wr_req1_cond, wr_req); + + // join tokens + let tok1 = join(tok1_0, tok1_1, tok1_2, tok1_3, tok1_4, tok1_5); + + // check which channel should be used for read/write + let rd_resp_ch = if (sel_q_rd & sel_q_rd_end) == Queue:0 { u1:0 } else { u1:1 }; + let wr_resp_ch = if (sel_q_wr & sel_q_wr_end) == Queue:0 { u1:0 } else { u1:1 }; + + // receive responses from output channel 0 + let (tok2_0, rd_resp0, rd_resp0_valid) = + recv_if_non_blocking(tok1, rd_resp0_r, rd_resp_ch == u1:0, zero!()); + let (tok2_1, wr_resp0, wr_resp0_valid) = + recv_if_non_blocking(tok1, wr_resp0_r, wr_resp_ch == u1:0, zero!()); + + // receive responses from output channel 1 + let (tok2_2, rd_resp1, rd_resp1_valid) = + recv_if_non_blocking(tok1, rd_resp1_r, rd_resp_ch == u1:1, zero!()); + let (tok2_3, wr_resp1, wr_resp1_valid) = + recv_if_non_blocking(tok1, wr_resp1_r, wr_resp_ch == u1:1, zero!()); + + // prepare read output values + let (rd_resp, rd_resp_valid) = if rd_resp_ch == u1:0 { + (rd_resp0, rd_resp0_valid) + } else { + (rd_resp1, rd_resp1_valid) + }; + + // prepare write output values + let (wr_resp, wr_resp_valid) = if wr_resp_ch == u1:0 { + (wr_resp0, wr_resp0_valid) + } else { + (wr_resp1, wr_resp1_valid) + }; + + // send responses to input channel + let tok2_4 = send_if(tok1, rd_resp_s, rd_resp_valid, rd_resp); + let sel_q_rd_end = if rd_resp_valid { sel_q_rd_end >> u32:1 } else { sel_q_rd_end }; + + let tok2_5 = send_if(tok1, wr_resp_s, wr_resp_valid, wr_resp); + let sel_q_wr_end = if wr_resp_valid { sel_q_wr_end >> u32:1 } else { sel_q_wr_end }; + + // handle select + let (tok1_6, sel, sel_valid) = recv_non_blocking(tok, sel_req_r, sel); + + let tok1_7 = send_if(tok1_6, sel_resp_s, sel_valid, ()); + + RamDemuxState { sel, sel_q_rd, sel_q_wr, sel_q_rd_end, sel_q_wr_end } + } +} + +const TEST_RAM_SIZE = u32:32; +const TEST_RAM_DATA_WIDTH = u32:8; +const TEST_RAM_ADDR_WIDTH = std::clog2(TEST_RAM_SIZE); +const TEST_RAM_WORD_PARTITION_SIZE = u32:1; +const TEST_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_RAM_WORD_PARTITION_SIZE, TEST_RAM_DATA_WIDTH); +const TEST_DEMUX_INIT_SEL = u1:0; +const TEST_DEMUX_QUEUE_LEN = u32:5; + +type TestWriteReq = ram::WriteReq; +type TestReadResp = ram::ReadResp; +type TestReadReq = ram::ReadReq; +type TestWriteResp = ram::WriteResp; +type TestDemuxAddr = uN[TEST_RAM_ADDR_WIDTH]; +type TestDemuxData = uN[TEST_RAM_DATA_WIDTH]; + +fn TestDemuxWriteWordReq(addr: TestDemuxAddr, data: TestDemuxData) -> TestWriteReq { + ram::WriteWordReq(addr, data) +} + +fn TestDemuxReadWordReq(addr: TestDemuxAddr) -> TestReadReq { + ram::ReadWordReq(addr) +} + +#[test_proc] +proc RamDemuxTest { + terminator: chan out; + + sel_req_s: chan out; + sel_resp_r: chan<()> in; + + rd_req_s: chan out; + rd_resp_r: chan in; + wr_req_s: chan out; + wr_resp_r: chan in; + + rd_req0_s: chan out; + rd_resp0_r: chan in; + wr_req0_s: chan out; + wr_resp0_r: chan in; + + rd_req1_s: chan out; + rd_resp1_r: chan in; + wr_req1_s: chan out; + wr_resp1_r: chan in; + + config(terminator: chan out) { + let (sel_req_s, sel_req_r) = chan("sel_req"); + let (sel_resp_s, sel_resp_r) = chan<()>("sel_resp"); + + let (rd_req_s, rd_req_r) = chan("rd_req"); + let (rd_resp_s, rd_resp_r) = chan("rd_resp"); + let (wr_req_s, wr_req_r) = chan("wr_req"); + let (wr_resp_s, wr_resp_r) = chan("wr_resp"); + + let (rd_req0_s, rd_req0_r) = chan("rd_req0"); + let (rd_resp0_s, rd_resp0_r) = chan("rd_resp0"); + let (wr_req0_s, wr_req0_r) = chan("wr_req0"); + let (wr_resp0_s, wr_resp0_r) = chan("wr_resp0"); + + let (rd_req1_s, rd_req1_r) = chan("rd_req1"); + let (rd_resp1_s, rd_resp1_r) = chan("rd_resp1"); + let (wr_req1_s, wr_req1_r) = chan("wr_req1"); + let (wr_resp1_s, wr_resp1_r) = chan("wr_resp1"); + + spawn RamDemux< + TEST_RAM_ADDR_WIDTH, TEST_RAM_DATA_WIDTH, TEST_RAM_NUM_PARTITIONS, + TEST_DEMUX_INIT_SEL, TEST_DEMUX_QUEUE_LEN + >( + sel_req_r, sel_resp_s, + rd_req_r, rd_resp_s, wr_req_r, wr_resp_s, + rd_req0_s, rd_resp0_r, wr_req0_s, wr_resp0_r, + rd_req1_s, rd_resp1_r, wr_req1_s, wr_resp1_r + ); + + spawn ram::RamModel( + rd_req0_r, rd_resp0_s, wr_req0_r, wr_resp0_s); + + spawn ram::RamModel( + rd_req1_r, rd_resp1_s, wr_req1_r, wr_resp1_s); + ( + terminator, sel_req_s, sel_resp_r, + rd_req_s, rd_resp_r, wr_req_s, wr_resp_r, + rd_req0_s, rd_resp0_r, wr_req0_s, wr_resp0_r, + rd_req1_s, rd_resp1_r, wr_req1_s, wr_resp1_r, + ) + } + + init { } + + next(state: ()) { + let tok = join(); + // test case 0: write data with demux to ram0 and read directly + let addr = TestDemuxAddr:0; + // request + let req = TestDemuxWriteWordReq(addr, TestDemuxData:0x12); + // set sel to 0 + let tok = send(tok, sel_req_s, u1:0); + let (tok, _) = recv(tok, sel_resp_r); + // write via demux + let tok = send(tok, wr_req_s, req); + let (tok, _) = recv(tok, wr_resp_r); + // read directly from ram0 + let tok = send(tok, rd_req0_s, TestDemuxReadWordReq(addr)); + let (tok, resp) = recv(tok, rd_resp0_r); + assert_eq(resp.data, req.data); + + // test case 1: write data with demux to ram1 and read directly + let addr = TestDemuxAddr:1; + // request + let req = TestDemuxWriteWordReq(addr, TestDemuxData:0x34); + // set sel to 1 + let tok = send(tok, sel_req_s, u1:1); + let (tok, _) = recv(tok, sel_resp_r); + // write via demux + let tok = send(tok, wr_req_s, req); + let (tok, _) = recv(tok, wr_resp_r); + // read directly from ram1 + let tok = send(tok, rd_req1_s, TestDemuxReadWordReq(addr)); + let (tok, resp) = recv(tok, rd_resp1_r); + assert_eq(resp.data, req.data); + + // test case 2: write data directly to ram0 and read with demux + let addr = TestDemuxAddr:0; + // request + let req = TestDemuxWriteWordReq(addr, TestDemuxData:0x56); + // write directly to ram0 + let tok = send(tok, wr_req0_s, req); + let (tok, _) = recv(tok, wr_resp0_r); + // set sel to 0 + let tok = send(tok, sel_req_s, u1:0); + let (tok, _) = recv(tok, sel_resp_r); + // read via demux + let tok = send(tok, rd_req_s, TestDemuxReadWordReq(addr)); + let (tok, resp) = recv(tok, rd_resp_r); + assert_eq(resp.data, req.data); + + // test case 3: write data directly to ram1 and read with demux + let addr = TestDemuxAddr:0; + // request + let req = TestDemuxWriteWordReq(addr, TestDemuxData:0x78); + // write directly to ram1 + let tok = send(tok, wr_req1_s, req); + let (tok, _) = recv(tok, wr_resp1_r); + // set sel to 1 + let tok = send(tok, sel_req_s, u1:1); + let (tok, _) = recv(tok, sel_resp_r); + // read via demux + let tok = send(tok, rd_req_s, TestDemuxReadWordReq(addr)); + let (tok, resp) = recv(tok, rd_resp_r); + assert_eq(resp.data, req.data); + + // test case 4: try to switch sel during write + let addr = TestDemuxAddr:1; + // request + let req0 = TestDemuxWriteWordReq(addr, TestDemuxData:0xAB); + let req1 = TestDemuxWriteWordReq(addr, TestDemuxData:0xCD); + // set sel to 0 + let tok = send(tok, sel_req_s, u1:0); + let (tok, _) = recv(tok, sel_resp_r); + // start write via demux + let tok = send(tok, wr_req_s, req0); + // set sel to 1 during read + let tok = send(tok, sel_req_s, u1:1); + let (tok, _) = recv(tok, sel_resp_r); + // finish write via demux + let (tok, _) = recv(tok, wr_resp_r); + // perform second write + let tok = send(tok, wr_req_s, req1); + let (tok, _) = recv(tok, wr_resp_r); + // read directly from ram0 and assert data from req0 was written + let tok = send(tok, rd_req0_s, TestDemuxReadWordReq(addr)); + let (tok, resp) = recv(tok, rd_resp0_r); + assert_eq(resp.data, req0.data); + // read directly from ram1 and assert data from req1 was written + let tok = send(tok, rd_req1_s, TestDemuxReadWordReq(addr)); + let (tok, resp) = recv(tok, rd_resp1_r); + assert_eq(resp.data, req1.data); + + // test case 5: try to switch sel during read + let addr = TestDemuxAddr:1; + // request + let req0 = TestDemuxWriteWordReq(addr, TestDemuxData:0xAB); + // write directly to ram0 + let tok = send(tok, wr_req0_s, req0); + let (tok, _) = recv(tok, wr_resp0_r); + let req1 = TestDemuxWriteWordReq(addr, TestDemuxData:0xCD); + // write directly to ram1 + let tok = send(tok, wr_req1_s, req1); + let (tok, _) = recv(tok, wr_resp1_r); + // set sel to 0 + let tok = send(tok, sel_req_s, u1:0); + let (tok, _) = recv(tok, sel_resp_r); + // start read via demux + let tok = send(tok, rd_req_s, TestDemuxReadWordReq(addr)); + // set sel to 1 during read + let tok = send(tok, sel_req_s, u1:1); + let (tok, _) = recv(tok, sel_resp_r); + // finish read via demux + let (tok, resp0) = recv(tok, rd_resp_r); + // perform second read + let tok = send(tok, rd_req_s, TestDemuxReadWordReq(addr)); + let (tok, resp1) = recv(tok, rd_resp_r); + // assert that first read returned data from ram0 + assert_eq(resp0.data, req0.data); + // assert that second read returned data from ram1 + assert_eq(resp1.data, req1.data); + + // test case 6: sending more write requests than queue can hold + // set sel to 0 + let tok = send(tok, sel_req_s, u1:0); + let (tok, _) = recv(tok, sel_resp_r); + // send 8 write requests + let tok = for (i, tok): (u32, token) in range(u32:0, TEST_DEMUX_QUEUE_LEN + u32:3) { + let req = TestDemuxWriteWordReq(i as TestDemuxAddr, i as TestDemuxData); + let tok = send(tok, wr_req_s, req); + let (tok, _) = recv(tok, wr_resp_r); + tok + }(tok); + // read values directly from ram + let tok = for (i, tok): (u32, token) in range(u32:0, TEST_DEMUX_QUEUE_LEN + u32:3) { + let req0 = TestDemuxReadWordReq(i as TestDemuxAddr); + let tok = send(tok, rd_req0_s, req0); + let (tok, resp0) = recv(tok, rd_resp0_r); + assert_eq(resp0.data, i as TestDemuxData); + tok + }(tok); + + // test case 7: sending more read requests than queue can hold + // set sel to 1 + let tok = send(tok, sel_req_s, u1:1); + let (tok, _) = recv(tok, sel_resp_r); + // write values directly to ram + let tok = for (i, tok): (u32, token) in range(u32:0, TEST_DEMUX_QUEUE_LEN + u32:3) { + let req1 = TestDemuxWriteWordReq(i as TestDemuxAddr, i as TestDemuxData); + let tok = send(tok, wr_req1_s, req1); + let (tok, _) = recv(tok, wr_resp1_r); + tok + }(tok); + // send 8 write requests + let tok = for (i, tok): (u32, token) in range(u32:0, TEST_DEMUX_QUEUE_LEN + u32:3) { + let req = TestDemuxReadWordReq(i as TestDemuxAddr); + let tok = send(tok, rd_req_s, req); + let (tok, resp) = recv(tok, rd_resp_r); + assert_eq(resp.data, i as TestDemuxData); + tok + }(tok); + + let tok = send(tok, terminator, true); + } +} + +const RAM_SIZE = u32:32; +const RAM_DATA_WIDTH = u32:8; +const RAM_ADDR_WIDTH = std::clog2(RAM_SIZE); +const RAM_WORD_PARTITION_SIZE = u32:1; +const RAM_NUM_PARTITIONS = ram::num_partitions(RAM_WORD_PARTITION_SIZE, RAM_DATA_WIDTH); + +// Sample for codegen +pub proc RamDemuxInst { + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp; + + config( + sel_req_r: chan in, + sel_resp_s: chan<()> out, + + rd_req_r: chan in, + rd_resp_s: chan out, + wr_req_r: chan in, + wr_resp_s: chan out, + + rd_req0_s: chan out, + rd_resp0_r: chan in, + wr_req0_s: chan out, + wr_resp0_r: chan in, + + rd_req1_s: chan out, + rd_resp1_r: chan in, + wr_req1_s: chan out, + wr_resp1_r: chan in + ) { + spawn RamDemux( + sel_req_r, sel_resp_s, + rd_req_r, rd_resp_s, wr_req_r, wr_resp_s, + rd_req0_s, rd_resp0_r, wr_req0_s, wr_resp0_r, + rd_req1_s, rd_resp1_r, wr_req1_s, wr_resp1_r + ); + } + + init { } + + next(state: ()) { } +} + +struct RamDemuxNaiveState { sel: u1 } + +// This implementation does not support sel switching during read/write operation +proc RamDemuxNaive< + ADDR_WIDTH: u32, + DATA_WIDTH: u32, + NUM_PARTITIONS: u32, + INIT_SEL: u1 = {u1:0} +> { + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp; + + sel_req_r: chan in; + sel_resp_s: chan<()> out; + + rd_req_r: chan in; + rd_resp_s: chan out; + wr_req_r: chan in; + wr_resp_s: chan out; + + rd_req0_s: chan out; + rd_resp0_r: chan in; + wr_req0_s: chan out; + wr_resp0_r: chan in; + + rd_req1_s: chan out; + rd_resp1_r: chan in; + wr_req1_s: chan out; + wr_resp1_r: chan in; + + config( + sel_req_r: chan in, + sel_resp_s: chan<()> out, + rd_req_r: chan in, + rd_resp_s: chan out, + wr_req_r: chan in, + wr_resp_s: chan out, + + rd_req0_s: chan out, + rd_resp0_r: chan in, + wr_req0_s: chan out, + wr_resp0_r: chan in, + + rd_req1_s: chan out, + rd_resp1_r: chan in, + wr_req1_s: chan out, + wr_resp1_r: chan in + ) { + ( + sel_req_r, sel_resp_s, + rd_req_r, rd_resp_s, wr_req_r, wr_resp_s, + rd_req0_s, rd_resp0_r, wr_req0_s, wr_resp0_r, + rd_req1_s, rd_resp1_r, wr_req1_s, wr_resp1_r, + ) + } + + init { RamDemuxNaiveState { sel: INIT_SEL } } + + next(state: RamDemuxNaiveState) { + let tok = join(); + + let sel = state.sel; + + // receive requests from input channel + let (tok1_0, rd_req, rd_req_valid) = recv_non_blocking(tok, rd_req_r, zero!()); + let (tok1_1, wr_req, wr_req_valid) = recv_non_blocking(tok, wr_req_r, zero!()); + + // send requests to output channel 0 + let rd_req0_cond = (sel == u1:0 && rd_req_valid); + let tok1_2 = send_if(tok, rd_req0_s, rd_req0_cond, rd_req); + + let wr_req0_cond = (sel == u1:0 && wr_req_valid); + let tok1_3 = send_if(tok, wr_req0_s, wr_req0_cond, wr_req); + + // send requests to output channel 1 + let rd_req1_cond = (sel == u1:1 && rd_req_valid); + let tok1_4 = send_if(tok, rd_req1_s, rd_req1_cond, rd_req); + + let wr_req1_cond = (sel == u1:1 && wr_req_valid); + let tok1_5 = send_if(tok, wr_req1_s, wr_req1_cond, wr_req); + + // join tokens + let tok1 = join(tok1_0, tok1_1, tok1_2, tok1_3, tok1_4, tok1_5); + + // receive responses from output channel 0 + let (tok2_0, rd_resp0, rd_resp0_valid) = + recv_if_non_blocking(tok1, rd_resp0_r, sel == u1:0, zero!()); + let (tok2_1, wr_resp0, wr_resp0_valid) = + recv_if_non_blocking(tok1, wr_resp0_r, sel == u1:0, zero!()); + + // receive responses from output channel 1 + let (tok2_2, rd_resp1, rd_resp1_valid) = + recv_if_non_blocking(tok1, rd_resp1_r, sel == u1:1, zero!()); + let (tok2_3, wr_resp1, wr_resp1_valid) = + recv_if_non_blocking(tok1, wr_resp1_r, sel == u1:1, zero!()); + + // prepare output values + let (rd_resp, rd_resp_valid, wr_resp, wr_resp_valid) = if sel == u1:0 { + (rd_resp0, rd_resp0_valid, wr_resp0, wr_resp0_valid) + } else { + (rd_resp1, rd_resp1_valid, wr_resp1, wr_resp1_valid) + }; + + // send responses to input channel + let tok2_4 = send_if(tok1, rd_resp_s, rd_resp_valid, rd_resp); + let tok2_5 = send_if(tok1, wr_resp_s, wr_resp_valid, wr_resp); + + // handle select + let (tok1_6, sel, sel_valid) = recv_non_blocking(tok, sel_req_r, sel); + + let tok1_7 = send_if(tok1_6, sel_resp_s, sel_valid, ()); + + RamDemuxNaiveState { sel } + } +} + +#[test_proc] +proc RamDemuxNaiveTest { + terminator: chan out; + + sel_req_s: chan out; + sel_resp_r: chan<()> in; + + rd_req_s: chan out; + rd_resp_r: chan in; + wr_req_s: chan out; + wr_resp_r: chan in; + + rd_req0_s: chan out; + rd_resp0_r: chan in; + wr_req0_s: chan out; + wr_resp0_r: chan in; + + rd_req1_s: chan out; + rd_resp1_r: chan in; + wr_req1_s: chan out; + wr_resp1_r: chan in; + + config(terminator: chan out) { + let (sel_req_s, sel_req_r) = chan("sel_req"); + let (sel_resp_s, sel_resp_r) = chan<()>("sel_resp"); + + let (rd_req_s, rd_req_r) = chan("rd_req"); + let (rd_resp_s, rd_resp_r) = chan("rd_resp"); + let (wr_req_s, wr_req_r) = chan("wr_req"); + let (wr_resp_s, wr_resp_r) = chan("wr_resp"); + + let (rd_req0_s, rd_req0_r) = chan("rd_req0"); + let (rd_resp0_s, rd_resp0_r) = chan("rd_resp0"); + let (wr_req0_s, wr_req0_r) = chan("wr_req0"); + let (wr_resp0_s, wr_resp0_r) = chan("wr_resp0"); + + let (rd_req1_s, rd_req1_r) = chan("wr_req1"); + let (rd_resp1_s, rd_resp1_r) = chan("wr_resp1"); + let (wr_req1_s, wr_req1_r) = chan("wr_req1"); + let (wr_resp1_s, wr_resp1_r) = chan("wr_resp1"); + + spawn RamDemuxNaive( + sel_req_r, sel_resp_s, + rd_req_r, rd_resp_s, wr_req_r, wr_resp_s, + rd_req0_s, rd_resp0_r, wr_req0_s, wr_resp0_r, + rd_req1_s, rd_resp1_r, wr_req1_s, wr_resp1_r + ); + + spawn ram::RamModel( + rd_req0_r, rd_resp0_s, wr_req0_r, wr_resp0_s); + + spawn ram::RamModel( + rd_req1_r, rd_resp1_s, wr_req1_r, wr_resp1_s); + ( + terminator, sel_req_s, sel_resp_r, + rd_req_s, rd_resp_r, wr_req_s, wr_resp_r, + rd_req0_s, rd_resp0_r, wr_req0_s, wr_resp0_r, + rd_req1_s, rd_resp1_r, wr_req1_s, wr_resp1_r, + ) + } + + init { } + + next(state: ()) { + let tok = join(); + // test case 0: write data with demux to ram0 and read directly + let addr = TestDemuxAddr:0; + // request + let req = TestDemuxWriteWordReq(addr, TestDemuxData:0x12); + // set sel to 0 + let tok = send(tok, sel_req_s, u1:0); + let (tok, _) = recv(tok, sel_resp_r); + // write via demux + let tok = send(tok, wr_req_s, req); + let (tok, _) = recv(tok, wr_resp_r); + // read directly from ram0 + let tok = send(tok, rd_req0_s, TestDemuxReadWordReq(addr)); + let (tok, resp) = recv(tok, rd_resp0_r); + assert_eq(resp.data, req.data); + + // test case 1: write data with demux to ram1 and read directly + let addr = TestDemuxAddr:1; + // request + let req = TestDemuxWriteWordReq(addr, TestDemuxData:0x34); + // set sel to 1 + let tok = send(tok, sel_req_s, u1:1); + let (tok, _) = recv(tok, sel_resp_r); + // write via demux + let tok = send(tok, wr_req_s, req); + let (tok, _) = recv(tok, wr_resp_r); + // read directly from ram1 + let tok = send(tok, rd_req1_s, TestDemuxReadWordReq(addr)); + let (tok, resp) = recv(tok, rd_resp1_r); + assert_eq(resp.data, req.data); + + // test case 2: write data directly to ram0 and read with demux + let addr = TestDemuxAddr:0; + // request + let req = TestDemuxWriteWordReq(addr, TestDemuxData:0x56); + // write directly to ram0 + let tok = send(tok, wr_req0_s, req); + let (tok, _) = recv(tok, wr_resp0_r); + // set sel to 0 + let tok = send(tok, sel_req_s, u1:0); + let (tok, _) = recv(tok, sel_resp_r); + // read via demux + let tok = send(tok, rd_req_s, TestDemuxReadWordReq(addr)); + let (tok, resp) = recv(tok, rd_resp_r); + assert_eq(resp.data, req.data); + + // test case 3: write data directly to ram1 and read with demux + let addr = TestDemuxAddr:0; + // request + let req = TestDemuxWriteWordReq(addr, TestDemuxData:0x78); + // write directly to ram1 + let tok = send(tok, wr_req1_s, req); + let (tok, _) = recv(tok, wr_resp1_r); + // set sel to 1 + let tok = send(tok, sel_req_s, u1:1); + let (tok, _) = recv(tok, sel_resp_r); + // read via demux + let tok = send(tok, rd_req_s, TestDemuxReadWordReq(addr)); + let (tok, resp) = recv(tok, rd_resp_r); + assert_eq(resp.data, req.data); + + // test cases 4 and 5 from RamDemuxTest are not relevant here as this naive + // implementation does not support sel switching during read/write operations + + let tok = send(tok, terminator, true); + } +} + +// Sample for codegen +pub proc RamDemuxNaiveInst { + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp; + + config( + sel_req_r: chan in, + sel_resp_s: chan<()> out, + + rd_req_r: chan in, + rd_resp_s: chan out, + wr_req_r: chan in, + wr_resp_s: chan out, + + rd_req0_s: chan out, + rd_resp0_r: chan in, + wr_req0_s: chan out, + wr_resp0_r: chan in, + + rd_req1_s: chan out, + rd_resp1_r: chan in, + wr_req1_s: chan out, + wr_resp1_r: chan in + ) { + spawn RamDemuxNaive( + sel_req_r, sel_resp_s, + rd_req_r, rd_resp_s, wr_req_r, wr_resp_s, + rd_req0_s, rd_resp0_r, wr_req0_s, wr_resp0_r, + rd_req1_s, rd_resp1_r, wr_req1_s, wr_resp1_r + ); + } + + init { } + + next(state: ()) { } +} From e551ac5fcc74b41ecc5000d33882cf30d0cbf478 Mon Sep 17 00:00:00 2001 From: Maciej Torhan Date: Fri, 10 May 2024 15:21:33 +0200 Subject: [PATCH 40/85] modules/zstd: Add literals decoding Co-authored-by: Pawel Czarnecki Co-authored-by: Ryszard Rozak Signed-off-by: Maciej Torhan Signed-off-by: Pawel Czarnecki Signed-off-by: Ryszard Rozak --- xls/modules/zstd/BUILD | 445 ++++++++- xls/modules/zstd/common.x | 71 +- xls/modules/zstd/dec_mux.x | 2 +- xls/modules/zstd/literals_buffer.x | 1185 ++++++++++++++++++++++++ xls/modules/zstd/literals_decoder.x | 709 ++++++++++++++ xls/modules/zstd/literals_dispatcher.x | 243 +++++ xls/modules/zstd/parallel_rams.x | 720 ++++++++++++++ xls/modules/zstd/raw_literals_dec.x | 81 ++ xls/modules/zstd/rle_literals_dec.x | 409 ++++++++ xls/modules/zstd/sequence_executor.x | 856 ++--------------- xls/modules/zstd/zstd_dec.x | 2 +- 11 files changed, 3957 insertions(+), 766 deletions(-) create mode 100644 xls/modules/zstd/literals_buffer.x create mode 100644 xls/modules/zstd/literals_decoder.x create mode 100644 xls/modules/zstd/literals_dispatcher.x create mode 100644 xls/modules/zstd/parallel_rams.x create mode 100644 xls/modules/zstd/raw_literals_dec.x create mode 100644 xls/modules/zstd/rle_literals_dec.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 6769a643be..9fe726bbe2 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -602,6 +602,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 = [ @@ -610,6 +624,7 @@ xls_dslx_library( deps = [ ":common_dslx", ":ram_printer_dslx", + ":parallel_rams_dslx", "//xls/examples:ram_dslx", "//xls/modules/zstd/memory:mem_writer_dslx", ], @@ -707,6 +722,8 @@ place_and_route( clock_period = CLOCK_PERIOD_PS, core_padding_microns = 2, min_pin_distance = "0.4", + die_height_microns = 120, + die_width_microns = 120, placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":sequence_executor_asap7", @@ -1711,7 +1728,6 @@ xls_dslx_library( "fse_dec.x", ], deps = [ - "//xls/examples:ram_dslx", ":common_dslx", ":math_dslx", ":fse_table_creator_dslx", @@ -1783,13 +1799,436 @@ place_and_route( name = "fse_dec_place_and_route", clock_period = "750", core_padding_microns = 2, - min_pin_distance = "0.5", - placement_density = "0.30", die_height_microns = 100, die_width_microns = 100, + min_pin_distance = "0.5", + placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":fse_dec_synth_asap7", 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 = 64, + die_width_microns = 64, + 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", +) + +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", +) + +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", +) + +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_0__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", +) + +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_0__LiteralsBuffer_0__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/common.x b/xls/modules/zstd/common.x index 13773aad47..68ff6bf81a 100644 --- a/xls/modules/zstd/common.x +++ b/xls/modules/zstd/common.x @@ -26,6 +26,7 @@ pub const BUFFER_WIDTH = u32:128; pub const MAX_BLOCK_SIZE_KB = u32:64; pub const BLOCK_PACKET_WIDTH = u32:32; +pub const SYMBOLS_IN_PACKET = DATA_WIDTH/SYMBOL_WIDTH; pub type BlockData = bits[DATA_WIDTH]; pub type BlockPacketLength = bits[BLOCK_PACKET_WIDTH]; @@ -59,10 +60,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_W * u32:8], // Literal data or match offset last: bool, // Last packet in frame } @@ -73,7 +74,7 @@ pub struct BlockSyncData { pub struct CommandConstructorData { sync: BlockSyncData, - data: SequenceExecutorPacket, + data: SequenceExecutorPacket, } // Defines output format of the ZSTD Decoder @@ -238,3 +239,67 @@ pub type SeqDecShiftBufferInput = shift_buffer::ShiftBufferPacket; pub type SeqDecShiftBufferPacket = shift_buffer::ShiftBufferPacket; pub type SeqDecShiftBufferStatus = shift_buffer::ShiftBufferStatus; + +// 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 type LitID = u32; + +pub type DecompressedSize = u20; + +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: DecompressedSize, + literals_type: LiteralType, +} + +pub struct RleLiteralsData { + data: RleLitData, + repeat: RleLitRepeat, + last: bool, + id: LitID, +} + +pub struct LiteralsData { + data: LitData, + 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 ef24e9b542..6f0d1d7f8b 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..1ad9ff4407 --- /dev/null +++ b/xls/modules/zstd/literals_buffer.x @@ -0,0 +1,1185 @@ +// 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; +type RamWrRespHandlerResp = parallel_rams::RamWrRespHandlerResp; + +// Constants calculated from RAM parameters +pub const RAM_NUM = parallel_rams::RAM_NUM; +const RAM_NUM_WIDTH = parallel_rams::RAM_NUM_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]; + +// 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_INIT_HB_PTR_ADDR = u32:127; +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], + 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, 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), + calc + ) + } else { + ( + last, + literal_last, + packet_valid, + calc + ) + } + }((false, false, true, 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:0, 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: false, + }, + 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 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 { + type State = LiteralsBufferWriterState; + 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 State = LiteralsBufferWriterState; + 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 buffer 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, 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) { + 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 (literals_data_valid) { + 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 state + let state = if (literals_data_valid) { + State { + hyp_ptr: new_hyp_ptr, + hb_len: new_hb_len, + ..state + } + } else { + state + }; + + let state = if (comp_data_valid) { + trace_fmt!("COMP {:#x}", comp_data); + State { + literals_in_ram: state.literals_in_ram + comp_data.length as uN[RAM_ADDR_WIDTH + std::clog2(RAM_NUM)], + ..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: comp_data.length, + }; + 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; + + 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 { + type State = LiteralsBufferReaderState; + 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; + type State = LiteralsBufferReaderState; + + 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 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, last_access, !last_access + ); + 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 + } +} + +pub 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 INST_HISTORY_BUFFER_SIZE_KB = u32:64; +const INST_RAM_ADDR_WIDTH = parallel_rams::ram_addr_width(INST_HISTORY_BUFFER_SIZE_KB); +const INST_RAM_NUM_PARTITIONS = RAM_NUM_PARTITIONS; +const INST_RAM_DATA_WIDTH = RAM_DATA_WIDTH; +const INST_SYMBOL_WIDTH = common::SYMBOL_WIDTH; + +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"); + + spawn LiteralsBuffer< + TEST_HISTORY_BUFFER_SIZE_KB, + TEST_RAM_SIZE, + TEST_RAM_ADDR_WIDTH, + TEST_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_decoder.x b/xls/modules/zstd/literals_decoder.x new file mode 100644 index 0000000000..24c0a05d2e --- /dev/null +++ b/xls/modules/zstd/literals_decoder.x @@ -0,0 +1,709 @@ +// 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: true}, + 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: ()) { + 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); + 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 new file mode 100644 index 0000000000..39fe949321 --- /dev/null +++ b/xls/modules/zstd/literals_dispatcher.x @@ -0,0 +1,243 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file 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 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: DecompressedSize, + literals_id: LitID, +} + +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(state: LiteralsDispatcherState ) { + + 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 { + (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 is_empty = left_to_read == DecompressedSize:0 && !literals_data.last; + + let left_to_read = if (literals_type == LiteralType::RLE) { + DecompressedSize:0 + } else { + 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, + 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); + + 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!()); + + // 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, + } + } +} + +#[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(state: ()) { + let tok = join(); + let test_ctrl: LiteralsPathCtrl[6] = [ + 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 + 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: LiteralsDataWithSync[7] = [ + // 0. + LiteralsDataWithSync {data: LitData:0x1657_3465_A6DB_5DB0, length: LitLength:8, id: LitID:0, last: false, literals_last: true}, + // 3. + 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. + 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, id: LitID:1, last: false}, + // 2. + RleLiteralsData {data: RleLitData:0x35, repeat: RleLitRepeat:13, id: LitID:2, last: true}, + // 4. + 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); + 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, 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); + (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); + } +} diff --git a/xls/modules/zstd/parallel_rams.x b/xls/modules/zstd/parallel_rams.x new file mode 100644 index 0000000000..24f9718326 --- /dev/null +++ b/xls/modules/zstd/parallel_rams.x @@ -0,0 +1,720 @@ +// 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] } + +pub 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 }); +} + +pub 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!()); +} + +pub 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 (curr_seq, next_seq, next_seq_valid) = if seq.length > max_len as CopyOrMatchLength { + ( + Packet { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: max_len as CopyOrMatchLength, + content: seq.content, + last: false, + }, + Packet { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: seq.length - (max_len as CopyOrMatchLength), + content: seq.content + (max_len as uN[RAM_DATA_WIDTH * u32:8]), + last: seq.last + }, + true, + ) + } else { + (seq, zero!(), false) + }; + + let max_ptr = max_hb_ptr_for_sequence_packet(ptr, curr_seq); + let req0 = sequence_packet_to_single_read_req(ptr, max_ptr, curr_seq, RamNumber:0); + let req1 = sequence_packet_to_single_read_req(ptr, max_ptr, curr_seq, RamNumber:1); + let req2 = sequence_packet_to_single_read_req(ptr, max_ptr, curr_seq, RamNumber:2); + let req3 = sequence_packet_to_single_read_req(ptr, max_ptr, curr_seq, RamNumber:3); + let req4 = sequence_packet_to_single_read_req(ptr, max_ptr, curr_seq, RamNumber:4); + let req5 = sequence_packet_to_single_read_req(ptr, max_ptr, curr_seq, RamNumber:5); + let req6 = sequence_packet_to_single_read_req(ptr, max_ptr, curr_seq, RamNumber:6); + let req7 = sequence_packet_to_single_read_req(ptr, max_ptr, curr_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:18, + length: CopyOrMatchLength:1, + last: false + }, true, + ); + assert_eq(result, expected); +} + +pub struct RamWrRespHandlerData { + resp: bool[RAM_NUM], + ptr: HistoryBufferPtr, +} + +pub struct RamWrRespHandlerResp { + length: uN[std::clog2(RAM_NUM + u32:1)], + ptr: HistoryBufferPtr, +} + +pub 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, RamWrRespHandlerResp { + length: std::popcount(std::convert_to_bits_msb0(input.resp)) as uN[std::clog2(RAM_NUM + u32:1)], + ptr: input.ptr + }); + } +} + +pub struct RamRdRespHandlerData { + resp: bool[RAM_NUM], + read_start: RamReadStart, + read_len: RamReadLen, + last: bool +} + +pub 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 new file mode 100644 index 0000000000..1dbb971785 --- /dev/null +++ b/xls/modules/zstd/raw_literals_dec.x @@ -0,0 +1,81 @@ +// 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 LiteralsDataWithSync = common::LiteralsDataWithSync; +type LitData = common::LitData; +type LitLength = common::LitLength; + +pub 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(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"); + 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(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 = 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. + let tok = send(tok, dispatcher_s, data); + let (tok, resp) = recv(tok, buffer_r); + assert_eq(resp, data); + + let tok = send(tok, terminator, true); + } +} diff --git a/xls/modules/zstd/rle_literals_dec.x b/xls/modules/zstd/rle_literals_dec.x new file mode 100644 index 0000000000..dda39f8208 --- /dev/null +++ b/xls/modules/zstd/rle_literals_dec.x @@ -0,0 +1,409 @@ +// 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 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, +} + +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(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, id: input.id, 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(state: ()) { + let tok = join(); + let test_data: RleLiteralsData[5] = [ + 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) { + let expected_data_out = RleInput { + symbol: test_data.data, + count: test_data.repeat, + last: true, + }; + + let expected_sync_out = LiteralsSyncData { + count: test_data.repeat, + id: test_data.id, + 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_id: LitID, + 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(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_id, sync_last) = if (no_count_left) { + (sync_data.count, sync_data.id, sync_data.last) + } else { + (state.count_left, state.sync_id, 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 = 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!() + } + } else { + BatchPackerState { + batch: batch, + data_in_batch: data_in_batch, + count_left: count_left - RleLitRepeat:1, + sync_id: sync_id, + sync_last: sync_last, + } + }; + + (literals_data, do_send_batch, state) + } else if (sync_data.last) { + // handle empty literal with last set + ( + 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 + }, + ) + } else { + // handle empty literal with last not set + ( + zero!(), + false, + BatchPackerState { + count_left: count_left, + sync_id: sync_id, + 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(state: ()) { + let tok = join(); + let test_sync_data: LiteralsSyncData[4] = [ + 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 + 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: 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) { + 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, 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); + (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(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(state: ()) { + let tok = join(); + let test_rle_data: RleLiteralsData[7] = [ + 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: LiteralsDataWithSync[10] = [ + // 1st literal + 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 + LiteralsDataWithSync {data: LitData:0x0000_0000_0022_2222, length: LitLength:3, last: false, id: LitID:1, literals_last: true}, + // 3rd literal + 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 + LiteralsDataWithSync {data: LitData:0x0000_0000_0000_5555, length: LitLength:2, last: false, id: LitID:3, literals_last: true}, + // 6th literal + 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 + 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) { + 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, 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); + (tok) + }(tok); + + send(tok, terminator, true); + } +} diff --git a/xls/modules/zstd/sequence_executor.x b/xls/modules/zstd/sequence_executor.x index b46180a307..80a7a6e70a 100644 --- a/xls/modules/zstd/sequence_executor.x +++ b/xls/modules/zstd/sequence_executor.x @@ -15,28 +15,24 @@ import std; import xls.modules.zstd.common as common; import xls.modules.zstd.memory.mem_writer as mem_writer; +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; @@ -45,7 +41,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 } @@ -67,9 +62,14 @@ 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 RamWrRespHandlerResp = parallel_rams::RamWrRespHandlerResp; +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, @@ -86,728 +86,69 @@ 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, 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 convert_output_packet(packet: ZstdDecodedPacket) -> mem_writer::MemWriterDataPacket { +fn decode_literal_packet(packet: SequenceExecutorPacket) -> mem_writer::MemWriterDataPacket { type MemWriterDataPacket = mem_writer::MemWriterDataPacket; MemWriterDataPacket { - data: packet.data as uN[DATA_W], - length: std::div_pow2(packet.length, u32:8) as uN[ADDR_W], + data: packet.content as uN[DATA_W], + length: packet.length as uN[ADDR_W], last: packet.last } } #[test] -fn test_convert_output_packet() { +fn test_decode_literal_packet() { const DATA_W = u32:64; const ADDR_W = u32:16; type MemWriterDataPacket = mem_writer::MemWriterDataPacket; - let packet = ZstdDecodedPacket { - data: CopyOrMatchContent:0xAA00BB11CC22DD33, - length: BlockPacketLength:64, - last: false - }; - let expected = MemWriterDataPacket { - data: uN[DATA_W]:0xAA00BB11CC22DD33, - length: uN[ADDR_W]:8, - last: false - }; - - assert_eq(convert_output_packet(packet), expected) -} - -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)); + let content = CopyOrMatchContent:0xAA00_BB11_CC22_DD33; + let length = CopyOrMatchLength:8; + let last = false; 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::min(seq.length as u32, std::min(RAM_NUM, hb_len)); - - let (next_seq, next_seq_valid) = if seq.length > max_len as CopyOrMatchLength { - ( + decode_literal_packet( 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, + msg_type: SequenceExecutorMessageType::LITERAL, + length, + content, + last + } + ), + MemWriterDataPacket { + data: uN[DATA_W]:0xAA00BB11CC22DD33, + length: uN[ADDR_W]:8, 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 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 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 +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], ], @@ -815,7 +156,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], ], @@ -823,7 +164,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], ], @@ -831,7 +172,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], ], @@ -842,10 +183,10 @@ fn handle_reapeated_offset_for_sequences pub proc SequenceExecutor { type MemWriterDataPacket = mem_writer::MemWriterDataPacket; @@ -853,7 +194,7 @@ pub proc SequenceExecutor in; output_mem_wr_data_in_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; @@ -912,15 +253,15 @@ pub 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 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); @@ -950,7 +291,7 @@ pub 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 && @@ -989,13 +330,13 @@ pub 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, @@ -1020,22 +361,23 @@ pub 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!(), + packet: zero!(), packet_valid: false, hyp_ptr: new_hyp_ptr, real_ptr, @@ -1050,7 +392,7 @@ pub 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, @@ -1083,19 +425,19 @@ pub 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, @@ -1118,7 +460,7 @@ pub 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 }, ) }, @@ -1142,14 +484,14 @@ pub proc SequenceExecutor(decode_literal_packet(packet)); + let output_mem_wr_data_in = decode_literal_packet(packet); if do_write_output { trace_fmt!("Sending output MemWriter data: {:#x}", output_mem_wr_data_in); } else { }; let tok2_10_1 = send_if(tok1, output_mem_wr_data_in_s, do_write_output, output_mem_wr_data_in); @@ -1164,8 +506,8 @@ pub 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 { }; @@ -1176,8 +518,8 @@ pub proc SequenceExecutor; terminator: chan out; - input_s: chan> out; + input_s: chan out; output_mem_wr_data_in_r: chan in; print_start_s: chan<()> out; @@ -1353,7 +695,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_mem_wr_data_in_s, output_mem_wr_data_in_r) = chan("output_mem_wr_data_in"); let (looped_channel_s, looped_channel_r) = chan("looped_channels"); @@ -1443,8 +785,7 @@ proc SequenceExecutorLiteralsTest { 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 expected = decode_literal_packet(LITERAL_TEST_INPUT_DATA[i]); - let expected_mem_writer_data = convert_output_packet(expected); + let expected_mem_writer_data = decode_literal_packet(LITERAL_TEST_INPUT_DATA[i]); let (tok, recv_mem_writer_data) = recv(tok, output_mem_wr_data_in_r); assert_eq(expected_mem_writer_data, recv_mem_writer_data); } else {} @@ -1702,8 +1043,7 @@ proc SequenceExecutorSequenceTest { 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 expected = decode_literal_packet(LITERAL_TEST_INPUT_DATA[i]); - let expected_mem_writer_data = convert_output_packet(expected); + let expected_mem_writer_data = decode_literal_packet(LITERAL_TEST_INPUT_DATA[i]); let (tok, recv_mem_writer_data) = recv(tok, output_mem_wr_data_in_r); assert_eq(expected_mem_writer_data, recv_mem_writer_data); } else {} diff --git a/xls/modules/zstd/zstd_dec.x b/xls/modules/zstd/zstd_dec.x index 7bbd356cf4..4657e9845a 100644 --- a/xls/modules/zstd/zstd_dec.x +++ b/xls/modules/zstd/zstd_dec.x @@ -908,7 +908,7 @@ pub proc ZstdDecoder< type RleBlockDecoderReq = rle_block_dec::RleBlockDecoderReq; type RleBlockDecoderResp = rle_block_dec::RleBlockDecoderResp; - type SequenceExecutorPacket = common::SequenceExecutorPacket; + type SequenceExecutorPacket = common::SequenceExecutorPacket; type ZstdDecodedPacket = common::ZstdDecodedPacket; type RamRdReq = ram::ReadReq; From a2d63f650d7c5c0b215063a5822bb2bbd2a00d7b Mon Sep 17 00:00:00 2001 From: Maciej Torhan Date: Tue, 19 Nov 2024 15:24:58 +0100 Subject: [PATCH 41/85] modules/zstd: Add Huffman literals decoder Signed-off-by: Maciej Torhan Co-authored-by: Maciej Dudek --- xls/modules/zstd/BUILD | 583 +++++++++++++++ xls/modules/zstd/fifo.v | 52 ++ xls/modules/zstd/huffman_axi_reader.x | 282 ++++++++ xls/modules/zstd/huffman_code_builder.x | 431 +++++++++++ xls/modules/zstd/huffman_common.x | 64 ++ xls/modules/zstd/huffman_ctrl.x | 284 ++++++++ xls/modules/zstd/huffman_data_preprocessor.x | 422 +++++++++++ xls/modules/zstd/huffman_decoder.x | 724 +++++++++++++++++++ xls/modules/zstd/huffman_literals_dec.x | 547 ++++++++++++++ xls/modules/zstd/huffman_prescan.x | 431 +++++++++++ 10 files changed, 3820 insertions(+) create mode 100644 xls/modules/zstd/fifo.v create mode 100644 xls/modules/zstd/huffman_axi_reader.x create mode 100644 xls/modules/zstd/huffman_code_builder.x create mode 100644 xls/modules/zstd/huffman_common.x create mode 100644 xls/modules/zstd/huffman_ctrl.x create mode 100644 xls/modules/zstd/huffman_data_preprocessor.x create mode 100644 xls/modules/zstd/huffman_decoder.x create mode 100644 xls/modules/zstd/huffman_literals_dec.x create mode 100644 xls/modules/zstd/huffman_prescan.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 9fe726bbe2..19c2595e26 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -2232,3 +2232,586 @@ place_and_route( synthesized_rtl = ":literals_decoder_synth_asap7", target_die_utilization_percentage = "10", ) + +xls_dslx_library( + name = "huffman_common_dslx", + srcs = [ + "huffman_common.x", + ], + deps = [], +) + +xls_dslx_library( + name = "huffman_prescan_dslx", + srcs = [ + "huffman_prescan.x", + ], + deps = [ + "//xls/examples:ram_dslx", + "//xls/dslx/stdlib:acm_random_dslx", + ":common_dslx", + ":huffman_common_dslx", + ], +) + +xls_dslx_test( + name = "huffman_prescan_dslx_test", + library = ":huffman_prescan_dslx", +) + +prescan_codegen_args = common_codegen_args | { + "module_name": "HuffmanPrescan", + "pipeline_stages": "16", + "clock_period_ps": "750", + "worst_case_throughput": "1", + "ram_configurations": + "InternalRam:1R1W:huffman_prescan__internal_read_req_s" + + ":huffman_prescan__internal_read_rsp_r:" + + "huffman_prescan__internal_write_req_s:" + + "huffman_prescan__internal_write_rsp_r:5", + "io_constraints" : "huffman_prescan__read_req_s:send:" + + "huffman_prescan__read_rsp_r:recv:5:5", +} + +xls_dslx_verilog( + name = "huffman_prescan_verilog", + codegen_args = prescan_codegen_args, + dslx_top = "WeightPreScan", + library = ":huffman_prescan_dslx", + verilog_file = "huffman_prescan.v", +) + +xls_benchmark_ir( + name = "huffman_prescan_opt_ir_benchmark", + src = ":huffman_prescan_verilog.opt.ir", + benchmark_ir_args = prescan_codegen_args, +) + +xls_benchmark_verilog( + name = "huffman_prescan_verilog_benchmark", + verilog_target = "huffman_prescan_verilog", +) + +verilog_library( + name = "huffman_prescan_verilog_lib", + srcs = [ + ":huffman_prescan.v", + "fifo.v" + ], +) + +synthesize_rtl( + name = "huffman_prescan_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "HuffmanPrescan", + deps = [ + ":huffman_prescan_verilog_lib", + ], +) + +benchmark_synth( + name = "huffman_prescan_benchmark_synth", + synth_target = ":huffman_prescan_synth_asap7", +) + +place_and_route( + name = "huffman_prescan_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + synthesized_rtl = ":huffman_prescan_synth_asap7", + target_die_utilization_percentage = "10", +) + +xls_dslx_library( + name = "huffman_code_builder_dslx", + srcs = [ + "huffman_code_builder.x", + ], + deps = [ + "//xls/examples:ram_dslx", + "//xls/dslx/stdlib:acm_random_dslx", + ":common_dslx", + ":huffman_common_dslx", + ], +) + +xls_dslx_test( + name = "huffman_code_builder_dslx_test", + library = ":huffman_code_builder_dslx", +) + +huffman_code_builder_codegen_args = common_codegen_args | { + "module_name": "HuffmanCodeBuilder", + "pipeline_stages": "8", + "clock_period_ps": "750", + "worst_case_throughput": "1", + "io_constraints" : "huffman_code_builder__weight_r:recv:" + + "huffman_code_builder__codes_s:send:2:2", +} + +xls_dslx_verilog( + name = "huffman_code_builder_verilog", + codegen_args = huffman_code_builder_codegen_args, + dslx_top = "WeightCodeBuilder", + library = ":huffman_code_builder_dslx", + verilog_file = "huffman_code_builder.v", +) + +xls_benchmark_ir( + name = "huffman_code_builder_opt_ir_benchmark", + src = ":huffman_code_builder_verilog.opt.ir", + benchmark_ir_args = huffman_code_builder_codegen_args, +) + +xls_benchmark_verilog( + name = "huffman_code_builder_verilog_benchmark", + verilog_target = "huffman_code_builder_verilog", +) + +verilog_library( + name = "huffman_code_builder_verilog_lib", + srcs = [ + ":huffman_code_builder.v", + "fifo.v" + ], +) + +synthesize_rtl( + name = "huffman_code_builder_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "HuffmanCodeBuilder", + deps = [ + ":huffman_code_builder_verilog_lib", + ], +) + +benchmark_synth( + name = "huffman_code_builder_benchmark_synth", + synth_target = ":huffman_code_builder_synth_asap7", +) + +place_and_route( + name = "huffman_code_builder_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + synthesized_rtl = ":huffman_code_builder_synth_asap7", + target_die_utilization_percentage = "10", +) + +xls_dslx_library( + name = "huffman_axi_reader_dslx", + srcs = [ + "huffman_axi_reader.x", + ], + deps = [ + "//xls/modules/zstd/memory:axi_dslx", + ], +) + +xls_dslx_test( + name = "huffman_axi_reader_dslx_test", + library = ":huffman_axi_reader_dslx", + tags = ["manual"], +) + +huffman_axi_reader_codegen_args = common_codegen_args | { + "module_name": "HuffmanAxiReader", + "pipeline_stages": "8", + "clock_period_ps": "750", + "worst_case_throughput": "2", +} + +xls_dslx_verilog( + name = "huffman_axi_reader_verilog", + codegen_args = huffman_axi_reader_codegen_args, + dslx_top = "HuffmanAxiReaderInst", + library = ":huffman_axi_reader_dslx", + opt_ir_args = { + "inline_procs": "true", + "top": "__huffman_axi_reader__HuffmanAxiReaderInst__HuffmanAxiReader_0__32_32_32_next", + }, + tags = ["manual"], + verilog_file = "huffman_axi_reader.v", +) + +xls_benchmark_ir( + name = "huffman_axi_reader_opt_ir_benchmark", + src = ":huffman_axi_reader_verilog.opt.ir", + benchmark_ir_args = huffman_axi_reader_codegen_args, + tags = ["manual"], +) + +verilog_library( + name = "huffman_axi_reader_verilog_lib", + srcs = [ + ":huffman_axi_reader.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "huffman_axi_reader_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "HuffmanAxiReader", + deps = [ + ":huffman_axi_reader_verilog_lib", + ], +) + +benchmark_synth( + name = "huffman_axi_reader_benchmark_synth", + synth_target = ":huffman_axi_reader_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "huffman_axi_reader_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 = ":huffman_axi_reader_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + +xls_dslx_library( + name = "huffman_data_preprocessor_dslx", + srcs = [ + "huffman_data_preprocessor.x", + ], + deps = [ + ":common_dslx", + ":huffman_axi_reader_dslx", + ":huffman_common_dslx", + ], +) + +xls_dslx_test( + name = "huffman_data_preprocessor_dslx_test", + library = ":huffman_data_preprocessor_dslx", + tags = ["manual"], +) + +huffman_data_preprocessor_codegen_args = common_codegen_args | { + "module_name": "HuffmanDataPreprocessor", + "pipeline_stages": "36", + "clock_period_ps": "810", + "clock_margin_percent": "0", + "worst_case_throughput": "1", +} + +xls_dslx_verilog( + name = "huffman_data_preprocessor_verilog", + codegen_args = huffman_data_preprocessor_codegen_args, + dslx_top = "HuffmanDataPreprocessor", + library = ":huffman_data_preprocessor_dslx", + opt_ir_args = { + "inline_procs": "true", + "top": "", + }, + tags = ["manual"], + verilog_file = "huffman_data_preprocessor.v", +) + +xls_benchmark_ir( + name = "huffman_data_preprocessor_opt_ir_benchmark", + src = ":huffman_data_preprocessor_verilog.opt.ir", + benchmark_ir_args = huffman_data_preprocessor_codegen_args, + tags = ["manual"], +) + +verilog_library( + name = "huffman_data_preprocessor_verilog_lib", + srcs = [ + ":huffman_data_preprocessor.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "huffman_data_preprocessor_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "HuffmanDataPreprocessor", + deps = [ + ":huffman_data_preprocessor_verilog_lib", + ], +) + +benchmark_synth( + name = "huffman_data_preprocessor_benchmark_synth", + synth_target = ":huffman_data_preprocessor_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "huffman_data_preprocessor_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 = ":huffman_data_preprocessor_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + +xls_dslx_library( + name = "huffman_decoder_dslx", + srcs = [ + "huffman_decoder.x", + ], + deps = [ + ":common_dslx", + ":huffman_common_dslx", + ":huffman_data_preprocessor_dslx", + ], +) + +xls_dslx_test( + name = "huffman_decoder_dslx_test", + library = ":huffman_decoder_dslx", + tags = ["manual"], +) + +huffman_decoder_codegen_args = common_codegen_args | { + "module_name": "HuffmanDecoder", + "pipeline_stages": "8", + "clock_period_ps": "0", + "worst_case_throughput": "1", +} + +xls_dslx_verilog( + name = "huffman_decoder_verilog", + codegen_args = huffman_decoder_codegen_args, + dslx_top = "HuffmanDecoder", + library = ":huffman_decoder_dslx", + opt_ir_args = { + "inline_procs": "true", + "top": "", + }, + tags = ["manual"], + verilog_file = "huffman_decoder.v", +) + +xls_benchmark_ir( + name = "huffman_decoder_opt_ir_benchmark", + src = ":huffman_decoder_verilog.opt.ir", + benchmark_ir_args = huffman_decoder_codegen_args, + tags = ["manual"], +) + +verilog_library( + name = "huffman_decoder_verilog_lib", + srcs = [ + ":huffman_decoder.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "huffman_decoder_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "HuffmanDecoder", + deps = [ + ":huffman_decoder_verilog_lib", + ], +) + +benchmark_synth( + name = "huffman_decoder_benchmark_synth", + synth_target = ":huffman_decoder_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "huffman_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 = ":huffman_decoder_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + +xls_dslx_library( + name = "huffman_ctrl_dslx", + srcs = [ + "huffman_ctrl.x", + ], + deps = [ + ":common_dslx", + ":huffman_axi_reader_dslx", + ":huffman_code_builder_dslx", + ":huffman_common_dslx", + ":huffman_data_preprocessor_dslx", + ":huffman_decoder_dslx", + ":huffman_prescan_dslx", + ], +) + +xls_dslx_test( + name = "huffman_ctrl_dslx_test", + library = ":huffman_ctrl_dslx", + tags = ["manual"], +) + +huffman_ctrl_codegen_args = common_codegen_args | { + "module_name": "HuffmanCtrl", + "pipeline_stages": "4", + "clock_period_ps": "750", + "worst_case_throughput": "1", +} + +xls_dslx_verilog( + name = "huffman_ctrl_verilog", + codegen_args = huffman_ctrl_codegen_args, + dslx_top = "HuffmanControlAndSequenceInst", + library = ":huffman_ctrl_dslx", + opt_ir_args = { + "inline_procs": "true", + "top": "__huffman_ctrl__HuffmanControlAndSequenceInst__HuffmanControlAndSequence_0__32_next", + }, + tags = ["manual"], + verilog_file = "huffman_ctrl.v", +) + +xls_benchmark_ir( + name = "huffman_ctrl_opt_ir_benchmark", + src = ":huffman_ctrl_verilog.opt.ir", + benchmark_ir_args = huffman_ctrl_codegen_args, + tags = ["manual"], +) + +verilog_library( + name = "huffman_ctrl_verilog_lib", + srcs = [ + ":huffman_ctrl.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "huffman_ctrl_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "HuffmanCtrl", + deps = [ + ":huffman_ctrl_verilog_lib", + ], +) + +benchmark_synth( + name = "huffman_ctrl_benchmark_synth", + synth_target = ":huffman_ctrl_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "huffman_ctrl_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 = ":huffman_ctrl_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + +xls_dslx_library( + name = "huffman_literals_dec_dslx", + srcs = [ + "huffman_literals_dec.x", + ], + deps = [ + ":common_dslx", + ":huffman_axi_reader_dslx", + ":huffman_code_builder_dslx", + ":huffman_common_dslx", + ":huffman_ctrl_dslx", + ":huffman_data_preprocessor_dslx", + ":huffman_decoder_dslx", + ":huffman_prescan_dslx", + ], +) + +xls_dslx_test( + name = "huffman_literals_dec_dslx_test", + library = ":huffman_literals_dec_dslx", + tags = ["manual"], +) + +huffman_literals_dec_codegen_args = common_codegen_args | { + "module_name": "HuffmanLiteralsDecoder", + "pipeline_stages": "64", + "clock_period_ps": "0", + "worst_case_throughput": "0", + "minimize_worst_case_throughput": "true", +} + +xls_dslx_verilog( + name = "huffman_literals_dec_verilog", + codegen_args = huffman_literals_dec_codegen_args, + dslx_top = "HuffmanLiteralsDecoderInst", + library = ":huffman_literals_dec_dslx", + opt_ir_args = { + "inline_procs": "true", + "top": "__xls_modules_zstd_huffman_decoder__HuffmanLiteralsDecoderInst__HuffmanLiteralsDecoder_0__HuffmanDecoder_0_next", + }, + tags = ["manual"], + verilog_file = "huffman_literals_dec.v", +) + +xls_benchmark_ir( + name = "huffman_literals_dec_opt_ir_benchmark", + src = ":huffman_literals_dec_verilog.opt.ir", + benchmark_ir_args = huffman_literals_dec_codegen_args, + tags = ["manual"], +) + +verilog_library( + name = "huffman_literals_dec_verilog_lib", + srcs = [ + ":huffman_literals_dec.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "huffman_literals_dec_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "HuffmanLiteralsDecoder", + deps = [ + ":huffman_literals_dec_verilog_lib", + ], +) + +benchmark_synth( + name = "huffman_literals_dec_benchmark_synth", + synth_target = ":huffman_literals_dec_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "huffman_literals_dec_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":huffman_literals_dec_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) diff --git a/xls/modules/zstd/fifo.v b/xls/modules/zstd/fifo.v new file mode 100644 index 0000000000..3a1633b093 --- /dev/null +++ b/xls/modules/zstd/fifo.v @@ -0,0 +1,52 @@ +module xls_fifo_wrapper ( +clk, rst, +push_ready, push_data, push_valid, +pop_ready, pop_data, pop_valid); + parameter Width = 32, + Depth = 32, + EnableBypass = 0, + RegisterPushOutputs = 1, + RegisterPopOutputs = 1; + localparam AddrWidth = $clog2(Depth) + 1; + input wire clk; + input wire rst; + output wire push_ready; + input wire [Width-1:0] push_data; + input wire push_valid; + input wire pop_ready; + output wire [Width-1:0] pop_data; + output wire pop_valid; + + // Require depth be 1 and bypass disabled. + initial begin + if (EnableBypass || Depth != 1 || !RegisterPushOutputs || RegisterPopOutputs) begin + // FIFO configuration not supported. + // $fatal(1); + end + end + + + reg [Width-1:0] mem; + reg full; + + assign push_ready = !full; + assign pop_valid = full; + assign pop_data = mem; + + always @(posedge clk) begin + if (rst == 1'b1) begin + full <= 1'b0; + end else begin + if (push_valid && push_ready) begin + mem <= push_data; + full <= 1'b1; + end else if (pop_valid && pop_ready) begin + mem <= mem; + full <= 1'b0; + end else begin + mem <= mem; + full <= full; + end + end + end +endmodule diff --git a/xls/modules/zstd/huffman_axi_reader.x b/xls/modules/zstd/huffman_axi_reader.x new file mode 100644 index 0000000000..507008cefa --- /dev/null +++ b/xls/modules/zstd/huffman_axi_reader.x @@ -0,0 +1,282 @@ +// 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 Huffmann data preprocessor. + +import xls.modules.zstd.memory.axi as axi; + +pub struct HuffmanAxiReaderCtrl { + base_addr: uN[AXI_ADDR_W], + len: uN[AXI_ADDR_W], +} + +pub struct HuffmanAxiReaderData { + data: u8, + last: bool, +} + +struct HuffmanAxiReaderState { + ctrl: HuffmanAxiReaderCtrl, + bytes_requested: uN[AXI_ADDR_W], + bytes_sent: uN[AXI_ADDR_W], +} + +pub proc HuffmanAxiReader { + // FIXME: Replace hard-coded values with proc params AXI_DATA_W, AXI_ID_W + type AxiR = axi::AxiR; + // FIXME: Replace hard-coded values with proc params AXI_ADDR_W, AXI_ID_W + type AxiAr = axi::AxiAr; + + // FIXME: Replace hard-coded values with proc params AXI_ADDR_W + type Ctrl = HuffmanAxiReaderCtrl; + type Data = HuffmanAxiReaderData; + + // FIXME: Replace hard-coded values with proc params AXI_DATA_W, AXI_ADDR_W + type State = HuffmanAxiReaderState; + + ctrl_r: chan in; + axi_r_r: chan in; + axi_ar_s: chan out; + data_s: chan out; + + config ( + ctrl_r: chan in, + axi_r_r: chan in, + axi_ar_s: chan out, + data_s: chan out, + ) { + ( + ctrl_r, + axi_r_r, + axi_ar_s, + data_s, + ) + } + + init { zero!() } + + next (state: State) { + const BYTES_PER_TRANSACTION = (AXI_ADDR_W / u32:8) as u8; + + // receive and store ctrl + let (_, ctrl, ctrl_valid) = recv_if_non_blocking(join(), ctrl_r, state.ctrl.len == state.bytes_sent, zero!()); + + let state = if ctrl_valid { + trace_fmt!("Received CTRL {:#x}", ctrl); + State { + ctrl: ctrl, + ..zero!() + } + } else { state }; + + // send AXI read request + // this could be optimized to read multiple bytes per AXI transaction + let addr = state.ctrl.base_addr + state.ctrl.len - uN[AXI_ADDR_W]:1 - state.bytes_requested; + let axi_ar = AxiAr { + id: uN[AXI_ID_W]:0, + addr: addr, + ..zero!() + }; + let do_send_axi_req = (state.bytes_requested < state.ctrl.len); + send_if(join(), axi_ar_s, do_send_axi_req, axi_ar); + if (do_send_axi_req) { + trace_fmt!("Sent AXI read request {:#x}", axi_ar); + } else {}; + + let state = if do_send_axi_req { + State { + bytes_requested: state.bytes_requested + uN[AXI_ADDR_W]:1, + ..state + } + } else { + state + }; + + // receive data from AXI + let do_read_axi_resp = (state.bytes_requested > state.bytes_sent) && (state.bytes_sent < state.bytes_requested); + let (tok, axi_r, axi_r_valid) = recv_if_non_blocking(join(), axi_r_r, do_read_axi_resp, zero!()); + + // send data + let last = axi_r_valid && ((state.bytes_sent + uN[AXI_ADDR_W]:1) == state.ctrl.len); + let tok = send_if(tok, data_s, axi_r_valid, Data { + data: axi_r.data as u8, + last: last, + }); + let state = if last { + zero!() + } else if axi_r_valid { + trace_fmt!("Received AXI read response {:#x}", axi_r); + State { + bytes_sent: state.bytes_sent + uN[AXI_ADDR_W]:1, + ..state + } + } else { state }; + + state + } +} + +const INST_AXI_DATA_W = u32:32; +const INST_AXI_ADDR_W = u32:32; +const INST_AXI_ID_W = u32:32; + +proc HuffmanAxiReaderInst { + type InstHuffmanAxiReaderCtrl = HuffmanAxiReaderCtrl; + + type InstAxiAr = axi::AxiAr; + type InstAxiR = axi::AxiR; + + config ( + ctrl_r: chan in, + axi_r_r: chan in, + axi_ar_s: chan out, + data_s: chan out, + ) { + spawn HuffmanAxiReader( + ctrl_r, + axi_r_r, + axi_ar_s, + data_s, + ); + } + + init { } + + next (state: ()) { } +} + +const TEST_AXI_DATA_W = u32:32; +const TEST_AXI_ADDR_W = u32:32; +const TEST_AXI_ID_W = u32:32; + +type TestHuffmanAxiReaderCtrl = HuffmanAxiReaderCtrl; + +type TestAxiAr = axi::AxiAr; +type TestAxiR = axi::AxiR; + +struct TestAxiData { + addr: uN[TEST_AXI_ADDR_W], + data: uN[TEST_AXI_DATA_W], + len: u8, + last: bool, +} + +const TEST_DATA_CTRL = TestHuffmanAxiReaderCtrl[3]:[ + TestHuffmanAxiReaderCtrl { + base_addr: uN[TEST_AXI_ADDR_W]:0, + len: uN[TEST_AXI_ADDR_W]:1, + }, + TestHuffmanAxiReaderCtrl { + base_addr: uN[TEST_AXI_ADDR_W]:128, + len: uN[TEST_AXI_ADDR_W]:4, + }, + TestHuffmanAxiReaderCtrl { + base_addr: uN[TEST_AXI_ADDR_W]:64, + len: uN[TEST_AXI_ADDR_W]:2, + }, +]; + +const TEST_DATA_AXI = TestAxiData[7]:[ + TestAxiData { addr: uN[TEST_AXI_ADDR_W]:0, data: uN[TEST_AXI_DATA_W]:0x12, len: u8:0, last: true, }, + TestAxiData { addr: uN[TEST_AXI_ADDR_W]:131, data: uN[TEST_AXI_DATA_W]:0xAA, len: u8:0, last: true, }, + TestAxiData { addr: uN[TEST_AXI_ADDR_W]:130, data: uN[TEST_AXI_DATA_W]:0xBB, len: u8:0, last: true, }, + TestAxiData { addr: uN[TEST_AXI_ADDR_W]:129, data: uN[TEST_AXI_DATA_W]:0xCC, len: u8:0, last: true, }, + TestAxiData { addr: uN[TEST_AXI_ADDR_W]:128, data: uN[TEST_AXI_DATA_W]:0xDD, len: u8:0, last: true, }, + TestAxiData { addr: uN[TEST_AXI_ADDR_W]:65, data: uN[TEST_AXI_DATA_W]:0x44, len: u8:0, last: false, }, + TestAxiData { addr: uN[TEST_AXI_ADDR_W]:64, data: uN[TEST_AXI_DATA_W]:0x55, len: u8:0, last: true, }, +]; + +const TEST_DATA_OUT = HuffmanAxiReaderData[7]:[ + HuffmanAxiReaderData { data: u8:0x12, last: true, }, + HuffmanAxiReaderData { data: u8:0xAA, last: false, }, + HuffmanAxiReaderData { data: u8:0xBB, last: false, }, + HuffmanAxiReaderData { data: u8:0xCC, last: false, }, + HuffmanAxiReaderData { data: u8:0xDD, last: true, }, + HuffmanAxiReaderData { data: u8:0x44, last: false, }, + HuffmanAxiReaderData { data: u8:0x55, last: true, }, +]; + +#[test_proc] +proc HuffmanAxiReader_test { + terminator: chan out; + + ctrl_s: chan out; + axi_r_s: chan out; + axi_ar_r: chan in; + data_r: chan in; + + config (terminator: chan out) { + let (ctrl_s, ctrl_r) = chan("ctrl"); + let (axi_r_s, axi_r_r) = chan("axi_r"); + let (axi_ar_s, axi_ar_r) = chan("axi_ar"); + let (data_s, data_r) = chan("data"); + + spawn HuffmanAxiReader ( + ctrl_r, + axi_r_r, + axi_ar_s, + data_s + ); + + ( + terminator, + ctrl_s, + axi_r_s, axi_ar_r, + data_r, + ) + } + + init { } + + next (state: ()) { + let tok = join(); + + let tok = for ((i, test_ctrl), tok): ((u32, TestHuffmanAxiReaderCtrl), token) in enumerate(TEST_DATA_CTRL) { + let tok = send(tok, ctrl_s, test_ctrl); + trace_fmt!("Sent #{} ctrl {:#x}", i + u32:1, test_ctrl); + tok + }(tok); + + let tok = for ((i, test_axi), tok): ((u32, TestAxiData), token) in enumerate(TEST_DATA_AXI) { + let (tok, axi_req) = recv(tok, axi_ar_r); + trace_fmt!("Received #{} AXI request {:#x}", i + u32:1, axi_req); + + assert_eq(test_axi.addr, axi_req.addr); + assert_eq(test_axi.len, axi_req.len); + + let axi_resp = TestAxiR { + id: axi_req.id, + data: test_axi.data, + resp: axi::AxiReadResp::OKAY, + last: test_axi.last, + }; + let tok = send(tok, axi_r_s, axi_resp); + trace_fmt!("Sent #{} AXI response {:#x}", i + u32:1, axi_resp); + + tok + }(tok); + + let tok = for ((i, test_data), tok): ((u32, HuffmanAxiReaderData), token) in enumerate(TEST_DATA_OUT) { + let (tok, data) = recv(tok, data_r); + trace_fmt!("Received #{} data {:#x}", i + u32:1, data); + + assert_eq(test_data.data as u8, data.data); + assert_eq(test_data.last, data.last); + + tok + }(tok); + + send(tok, terminator, true); + } +} diff --git a/xls/modules/zstd/huffman_code_builder.x b/xls/modules/zstd/huffman_code_builder.x new file mode 100644 index 0000000000..4b554779c1 --- /dev/null +++ b/xls/modules/zstd/huffman_code_builder.x @@ -0,0 +1,431 @@ +// 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 Huffman tree decoder. + +import std; +import xls.dslx.stdlib.acm_random as random; + +import xls.examples.ram; +import xls.modules.zstd.common as common; +import xls.modules.zstd.huffman_common as hcommon; + +const MAX_WEIGHT = hcommon::MAX_WEIGHT; +const WEIGHT_LOG = hcommon::WEIGHT_LOG; +const MAX_SYMBOL_COUNT = hcommon::MAX_SYMBOL_COUNT; + +const PARALLEL_ACCESS_WIDTH = hcommon::PARALLEL_ACCESS_WIDTH; +const COUNTER_WIDTH = hcommon::COUNTER_WIDTH; + +const RECV_COUNT = MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH; +const RECV_COUNT_W = std::clog2(RECV_COUNT + u32:1); +const MAX_RECV = RECV_COUNT as uN[RECV_COUNT_W]; + +type WeightPreScanMetaData = hcommon::WeightPreScanMetaData; +type WeightPreScanOutput = hcommon::WeightPreScanOutput; +type CodeBuilderToPreDecoderOutput = hcommon::CodeBuilderToPreDecoderOutput; +type CodeBuilderToDecoderOutput = hcommon::CodeBuilderToDecoderOutput; + +enum WeightCodeBuilderFSM: u2 { + IDLE = u2:0, + GATHER_WEIGHTS_RUN = u2:1, + COMPUTE_MAX_LENGTH = u2:2, + GENERATE_CODES_RUN = u2:3, +} + +struct WeightCodeBuilderState { + fsm: WeightCodeBuilderFSM, + recv_counter: uN[RECV_COUNT_W], + loopback_counter: uN[RECV_COUNT_W], + sum_of_weights_powers: uN[MAX_WEIGHT + u32:2], + huffman_codes: uN[MAX_WEIGHT][MAX_WEIGHT + u32:1], + seen_weights: u1[MAX_WEIGHT + u32:1], + max_number_of_bits: uN[WEIGHT_LOG], +} + +pub proc WeightCodeBuilder +// TODO: enable parametric expresion when they start working +//proc WeightCodeBuilder< +// PARALLEL_ACCESS_WIDTH: u32 = {u32:8}, +//> { +{ + type State = WeightCodeBuilderState; + type FSM = WeightCodeBuilderFSM; + type PreScanData = WeightPreScanOutput; + type DecoderOutput = CodeBuilderToDecoderOutput; + type PreDecoderOutput = CodeBuilderToPreDecoderOutput; + + start_r: chan in; + weight_r: chan in; + codes_s: chan out; + lookahead_config_s: chan out; + + weights_pow_sum_loopback_s: chan out; + weights_pow_sum_loopback_r: chan in; + + config ( + start_r: chan in, + weight_r: chan in, + codes_s: chan out, + lookahead_config_s: chan out, + weights_pow_sum_loopback_s: chan out, + weights_pow_sum_loopback_r: chan in, + ) { + (start_r, weight_r, codes_s, lookahead_config_s, weights_pow_sum_loopback_s, weights_pow_sum_loopback_r) + } + + init {zero!()} + + next(state: State) { + let tok = join(); + + let (recv_start, recv_prescan) = match state.fsm { + FSM::IDLE => (true, false), + FSM::GATHER_WEIGHTS_RUN => (false, true), + FSM::COMPUTE_MAX_LENGTH => (false, false), + FSM::GENERATE_CODES_RUN => (false, true), + _ => { + assert!(false, "Invalid state"); + (false, false) + } + }; + let (_, start, start_valid) = recv_if_non_blocking(tok, start_r, recv_start, false); + let (_, prescan_data, prescan_data_valid) = recv_if_non_blocking(tok, weight_r, recv_prescan, zero!()); + + if start_valid { + trace_fmt!("Received start {:#x}", start); + } else {}; + + if prescan_data_valid { + trace_fmt!("Received prescan {:#x}", prescan_data); + } else {}; + + let (advance_state, send_lookahead, send_codes) = match state.fsm { + FSM::IDLE => (start && start_valid, false, false), + FSM::GATHER_WEIGHTS_RUN => (state.recv_counter == MAX_RECV, false, false), + FSM::COMPUTE_MAX_LENGTH => (state.loopback_counter == MAX_RECV, false, false), + FSM::GENERATE_CODES_RUN => { + let advance_state = state.recv_counter == (MAX_RECV * uN[RECV_COUNT_W]:2); + (advance_state, advance_state, prescan_data_valid) + }, + _ => { + assert!(false, "Invalid state"); + (false, false, false) + } + }; + + let next_fsm_state = match(state.fsm, advance_state) { + (FSM::IDLE, true) => { + trace_fmt!("IDLE -> GATHER_WEIGHTS_RUN"); + FSM::GATHER_WEIGHTS_RUN + }, + (FSM::GATHER_WEIGHTS_RUN, true) => { + trace_fmt!("GATHER_WEIGHTS_RUN -> COMPUTE_MAX_LENGTH"); + FSM::COMPUTE_MAX_LENGTH + }, + (FSM::COMPUTE_MAX_LENGTH, true) => { + trace_fmt!("COMPUTE_MAX_LENGTH -> GENERATE_CODES_RUN"); + FSM::GENERATE_CODES_RUN + }, + (FSM::GENERATE_CODES_RUN, true) => { + trace_fmt!("GENERATE_CODES_RUN -> IDLE"); + FSM::IDLE + }, + (_, false) => state.fsm, + _ => { + assert!(false, "Invalid state"); + FSM::IDLE + } + }; + + let meta_data = prescan_data.meta_data; + + // update seen weights + let seen_weights = for (i, weights) in range(u32:0, MAX_WEIGHT + u32:1) { + update(weights, i, weights[i] | meta_data.valid_weights[i]) + }(state.seen_weights); + + // compute sum of weights powers and send it to loopback + let do_send_loopback = (state.fsm == FSM::GATHER_WEIGHTS_RUN) && prescan_data_valid; + + let sum_of_weights_powers = if do_send_loopback { + for (i, acc) in range(u32:0, PARALLEL_ACCESS_WIDTH) { + if (prescan_data.weights[i] != uN[WEIGHT_LOG]:0) { + acc + (uN[MAX_WEIGHT + u32:2]:1 << prescan_data.weights[i] as uN[MAX_WEIGHT + u32:2]) + } else { + acc + } + }(uN[MAX_WEIGHT + u32:2]:0) + } else { + uN[MAX_WEIGHT + u32:2]:0 + }; + + send_if(tok, weights_pow_sum_loopback_s, do_send_loopback, sum_of_weights_powers); + + // receive sum of weights powers from loopback + let (_, sum_of_weights_powers, sum_of_weights_powers_valid) = recv_non_blocking( + tok, weights_pow_sum_loopback_r, uN[MAX_WEIGHT + u32:2]:0 + ); + let sum_of_weights_powers = state.sum_of_weights_powers + sum_of_weights_powers; + let loopback_counter = if sum_of_weights_powers_valid { + trace_fmt!("Sum of weights powers: {}", sum_of_weights_powers); + state.loopback_counter + uN[RECV_COUNT_W]:1 + } else { + state.loopback_counter + }; + + // compute max number of bits + let max_number_of_bits = encode(sum_of_weights_powers >> u32:1) as uN[WEIGHT_LOG]; + + // intial value for huffman codes is 0 for weight 1 and 1 for the rest + // then the value is computed based on number of occurances of given weight + let huffman_codes = match(state.fsm, advance_state) { + (FSM::IDLE, _) => { + let huffman_codes = for (i, codes) in range(u32:0, MAX_WEIGHT + u32:1) { + update(codes, i, uN[MAX_WEIGHT]:1) + }(zero!()); + update(huffman_codes, u32:1, uN[MAX_WEIGHT]:0) + }, + (FSM::GENERATE_CODES_RUN, _) => { + let weights_count = meta_data.weights_count; + for(i, codes) in range(u32:0, MAX_WEIGHT + u32:1) { + update(codes, i, codes[i] + (weights_count[i] as uN[MAX_WEIGHT])) + }(state.huffman_codes) + }, + _ => state.huffman_codes, + }; + + let next_state = match(state.fsm,) { + (FSM::IDLE) => { + State { + fsm: next_fsm_state, + huffman_codes: huffman_codes, + ..zero!() + } + }, + (FSM::GATHER_WEIGHTS_RUN) => { + let recv_counter = if prescan_data_valid { + state.recv_counter + uN[RECV_COUNT_W]:1 + } else { + state.recv_counter + }; + State { + fsm: next_fsm_state, + loopback_counter: loopback_counter, + sum_of_weights_powers: sum_of_weights_powers, + recv_counter: recv_counter, + ..state + } + }, + (FSM::COMPUTE_MAX_LENGTH) => { + State { + fsm: next_fsm_state, + loopback_counter: loopback_counter, + sum_of_weights_powers: sum_of_weights_powers, + max_number_of_bits: max_number_of_bits, + ..state + } + }, + (FSM::GENERATE_CODES_RUN) => { + let recv_counter = if prescan_data_valid { + state.recv_counter + uN[RECV_COUNT_W]:1 + } else { + state.recv_counter + }; + State { + fsm: next_fsm_state, + recv_counter: recv_counter, + huffman_codes: huffman_codes, + seen_weights: seen_weights, + ..state + } + }, + _ => { + assert!(false, "Invalid state"); + zero!() + } + }; + + let lookahead_packet = PreDecoderOutput { + max_code_length: state.max_number_of_bits, + valid_weights: seen_weights, + }; + send_if(tok, lookahead_config_s, send_lookahead, lookahead_packet); + + // set symbol valid if weight is nonzero + let symbols_valid = for (i, symbol_valid) in range(u32:0, PARALLEL_ACCESS_WIDTH) { + update(symbol_valid, i, prescan_data.weights[i] != uN[WEIGHT_LOG]:0) + }(zero!()); + + // set symbol length as max_length - weight + 1 + let codes_length = for (i, code_length) in range(u32:0, PARALLEL_ACCESS_WIDTH) { + update(code_length, i, state.max_number_of_bits - prescan_data.weights[i] + uN[WEIGHT_LOG]:1) + }(zero!()); + + // set codes using weight, occurance number and Huffman codes per weight from previous iteration + let codes = for (i, codes) in range(u32:0, PARALLEL_ACCESS_WIDTH) { + let length = state.max_number_of_bits - prescan_data.weights[i] + uN[WEIGHT_LOG]:1; + let base_code = for(j, base_code) in range(u32:0, MAX_WEIGHT + u32:1) { + if (prescan_data.weights[i] == j as uN[WEIGHT_LOG]) { + state.huffman_codes[j] + } else { + base_code + } + }(uN[MAX_WEIGHT]:0); + let code = base_code + (meta_data.occurance_number[i] as uN[MAX_WEIGHT]); + let code = rev(code) >> (MAX_WEIGHT - length as u32); + update(codes, i, code) + }(zero!()); + + if send_codes { + trace_fmt!("{}\n{}\n{:#b}\n{:#b}", symbols_valid, codes_length, codes, state.huffman_codes); + } else {}; + + let code_packet = DecoderOutput { + symbol_valid: symbols_valid, + code_length: codes_length, + code: codes + }; + send_if(tok, codes_s, send_codes, code_packet); + + next_state + } +} + +//#[test_proc] +//proc WeightCodeBuilderSimpleTest{ +// type PrescanOut = WeightPreScanOutput; +// type DecoderOutput = CodeBuilderToDecoderOutput; +// type PreDecoderOutput = CodeBuilderToPreDecoderOutput; +// +// terminator: chan out; +//// external_ram_req: chan out; +//// external_ram_resp: chan in; +//// start_prescan: chan out; +//// prescan_response: chan in; +// init{()} +//// config (terminator: chan out) { +//// // Emulate external memory +//// let (RAMExternalWriteReq_s, RAMExternalWriteReq_r) = chan("Write_channel_req"); +//// let (RAMExternalWriteResp_s, RAMExternalWriteResp_r) = chan("Write_channel_resp"); +//// let (RAMExternalReadReq_s, RAMExternalReadReq_r) = chan("Read_channel_req"); +//// let (RAMExternalReadResp_s, RAMExternalReadResp_r) = chan("Read_channel_resp"); +//// spawn ram::RamModel( +//// RAMExternalReadReq_r, RAMExternalReadResp_s, RAMExternalWriteReq_r, RAMExternalWriteResp_s +//// ); +//// +//// // Emulate Internal prescan memory +//// let (RAMInternalWriteReq_s, RAMInternalWriteReq_r) = chan("Internal_write_channel_req"); +//// let (RAMInternalWriteResp_s, RAMInternalWriteResp_r) = chan("Internal_write_channel_resp"); +//// let (RAMInternalReadReq_s, RAMInternalReadReq_r) = chan("Internal_read_channel_req"); +//// let (RAMInternalReadResp_s, RAMInternalReadResp_r) = chan("Internal_read_channel_resp"); +//// spawn ram::RamModel<{WeightPreScanMetaDataSize()}, RAM_SIZE, {WeightPreScanMetaDataSize()}>( +//// RAMInternalReadReq_r, RAMInternalReadResp_s, RAMInternalWriteReq_r, RAMInternalWriteResp_s +//// ); +//// +//// let (PreScanStart_s, PreScanStart_r) = chan("Start_prescan"); +//// let (PreScanResponse_s, PreScanResponse_r) = chan("Start_prescan"); +//// spawn WeightPreScan( +//// PreScanStart_r, RAMExternalReadReq_s,RAMExternalReadResp_r, PreScanResponse_s, +//// RAMInternalReadReq_s, RAMInternalReadResp_r, RAMInternalWriteReq_s, RAMInternalWriteResp_r); +//// (terminator, RAMExternalWriteReq_s, RAMExternalWriteResp_r, PreScanStart_s, PreScanResponse_r) +//// } +//// next(state: ()) { +//// let tok = join(); +//// let rand_state = random::rng_new(random::rng_deterministic_seed()); +//// // Setup external memory with random values +//// for (i, rand_state) in range(u32:0, MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH) { +//// let (new_rand_state, data_to_send) = for (j, (rand_state, data_to_send)) in range(u32:0, PARALLEL_ACCESS_WIDTH) { +//// let (new_rand_state, data) = random::rng_next(rand_state); +//// let weight = (data - (data/u32:12) * u32:12) as u4; +//// let new_data_to_send = update(data_to_send as uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH], j, weight) as external_ram_data; +//// (new_rand_state, new_data_to_send) +//// }((rand_state, zero!())); +//// let external_w_req = WriteReq { +//// addr: i as u5, +//// data: data_to_send, +//// mask: u1:1 +//// }; +//// send(tok, external_ram_req, external_w_req); +//// recv(tok, external_ram_resp); +//// new_rand_state +//// }(rand_state); +//// send(tok, start_prescan, true); +//// // First run +//// for (_, rand_state) in range(u32:0, MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH) { +//// // Generate expected output +//// let (new_rand_state, expected_data) = for (j, (rand_state, data_to_send)) in range(u32:0, PARALLEL_ACCESS_WIDTH) { +//// let (new_rand_state, data) = random::rng_next(rand_state); +//// let weight = (data - (data/u32:12) * u32:12) as u4; +//// let new_data_to_send = update(data_to_send as uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH], j, weight) as external_ram_data; +//// (new_rand_state, new_data_to_send) +//// }((rand_state, zero!())); +//// let (_, prescan_resp) = recv(tok, prescan_response); +//// let expected_data = PrescanOut { +//// weights: expected_data as uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH], +//// meta_data: zero!() +//// }; +//// assert_eq(prescan_resp, expected_data); +//// new_rand_state +//// }(rand_state); +//// +//// // Second run +//// for (_, rand_state) in range(u32:0, MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH) { +//// // Generate expected output +//// let (new_rand_state, expected_data) = for (j, (rand_state, data_to_send)) in range(u32:0, PARALLEL_ACCESS_WIDTH) { +//// let (new_rand_state, data) = random::rng_next(rand_state); +//// let weight = (data - (data/u32:12) * u32:12) as u4; +//// let new_data_to_send = update(data_to_send as uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH], j, weight) as external_ram_data; +//// (new_rand_state, new_data_to_send) +//// }((rand_state, zero!())); +//// let expected_data = expected_data as uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH]; +//// let valid_weights = for (i, seen_weights) in range(u32:0, PARALLEL_ACCESS_WIDTH) { +//// update(seen_weights, expected_data[i], true) +//// }(zero!()); +//// let occurance_number = for (i, occurance_number) in range(u32:0, PARALLEL_ACCESS_WIDTH) { +//// let number = for (j, number) in range(u32:0, PARALLEL_ACCESS_WIDTH){ +//// if (j < i && expected_data[j] == expected_data[i]) { +//// number + u4:1 +//// } else { +//// number +//// } +//// }(zero!()); +//// update(occurance_number, i, number) +//// }(zero!()); +//// let weights_count = for (i, weights_count) in range(u32:0, MAX_WEIGHT + u32:1) { +//// let count = for (j, count) in range(u32:0, PARALLEL_ACCESS_WIDTH) { +//// if (expected_data[j] == i as uN[COUNTER_WIDTH]) { +//// count + uN[COUNTER_WIDTH]:1 +//// } else { +//// count +//// } +//// }(zero!()); +//// update(weights_count, i, count) +//// }(zero!()); +//// let (_, prescan_resp) = recv(tok, prescan_response); +//// let expected_data = PrescanOut { +//// weights: expected_data, +//// meta_data: WeightPreScanMetaData { +//// occurance_number: occurance_number, +//// valid_weights: valid_weights, +//// weights_count: weights_count, +//// } +//// }; +//// assert_eq(prescan_resp, expected_data); +//// new_rand_state +//// }(rand_state); +//// +//// send(tok, terminator, true); +//// } +//} diff --git a/xls/modules/zstd/huffman_common.x b/xls/modules/zstd/huffman_common.x new file mode 100644 index 0000000000..7661f42315 --- /dev/null +++ b/xls/modules/zstd/huffman_common.x @@ -0,0 +1,64 @@ +// 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 Huffmann tree decoder. + +import std; + +pub const MAX_WEIGHT = u32:11; +pub const WEIGHT_LOG = std::clog2(MAX_WEIGHT + u32:1); +pub const MAX_SYMBOL_COUNT = u32:256; +pub const MAX_CODE_LEN = u32:12; + +pub const PARALLEL_ACCESS_WIDTH = u32:8; +pub const COUNTER_WIDTH = std::clog2(PARALLEL_ACCESS_WIDTH + u32:1); + +pub struct WeightPreScanMetaData { + occurance_number: uN[COUNTER_WIDTH][PARALLEL_ACCESS_WIDTH], + valid_weights: u1[MAX_WEIGHT + u32:1], + weights_count: uN[COUNTER_WIDTH][MAX_WEIGHT + u32:1], +} + +// TODO: Enable once parametrics work +//pub struct WeightPreScanMetaData < +// PARALLEL_ACCESS_WIDTH: u32, +// COUNTER_WIDTH: u32 = {std::clog2(PARALLEL_ACCESS_WIDTH + u32:1)} +//> { +// occurance_number: uN[COUNTER_WIDTH][PARALLEL_ACCESS_WIDTH], +// valid_weights: u1[MAX_WEIGHT + u32:1], +// weights_count: uN[COUNTER_WIDTH][MAX_WEIGHT + u32:1], +//} + +pub struct WeightPreScanOutput { + weights: uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH], + meta_data: WeightPreScanMetaData, +} +// TODO: Use parametrics when they work +//pub struct WeightPreScanOutput< +// PARALLEL_ACCESS_WIDTH: u32, WEIGHT_LOG: u32 +//> { +// weights: uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH], +// meta_data: WeightPreScanMetaData, +//} + +pub struct CodeBuilderToPreDecoderOutput { + max_code_length: uN[WEIGHT_LOG], + valid_weights: u1[MAX_WEIGHT + u32:1], +} + +pub struct CodeBuilderToDecoderOutput { + symbol_valid: u1[PARALLEL_ACCESS_WIDTH], + code_length: uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH], + code: uN[MAX_WEIGHT][PARALLEL_ACCESS_WIDTH], +} diff --git a/xls/modules/zstd/huffman_ctrl.x b/xls/modules/zstd/huffman_ctrl.x new file mode 100644 index 0000000000..d5ac04d427 --- /dev/null +++ b/xls/modules/zstd/huffman_ctrl.x @@ -0,0 +1,284 @@ +// 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 Huffman decoder control and sequence proc implementation. + +import xls.modules.zstd.common as common; +import xls.modules.zstd.huffman_common as hcommon; +import xls.modules.zstd.huffman_axi_reader as axi_reader; +import xls.modules.zstd.huffman_code_builder as code_builder; +import xls.modules.zstd.huffman_data_preprocessor as data_preprocessor; +import xls.modules.zstd.huffman_decoder as decoder; +import xls.modules.zstd.huffman_prescan as prescan; + + +enum HuffmanControlAndSequenceFSM: u2 { + IDLE = 0, + DECODING = 1, +} + +pub struct HuffmanControlAndSequenceCtrl { + base_addr: uN[AXI_ADDR_W], + len: uN[AXI_ADDR_W], + new_config: bool, +} + +struct HuffmanControlAndSequenceState { + fsm: HuffmanControlAndSequenceFSM, +} + +pub proc HuffmanControlAndSequence { + type AxiReaderCtrl = axi_reader::HuffmanAxiReaderCtrl; + type DataPreprocessorStart = data_preprocessor::HuffmanDataPreprocessorStart; + type DecoderStart = decoder::HuffmanDecoderStart; + + type State = HuffmanControlAndSequenceState; + type FSM = HuffmanControlAndSequenceFSM; + type Ctrl = HuffmanControlAndSequenceCtrl; + + ctrl_r: chan in; + + // prescan + prescan_start_s: chan out; + + // code builder + code_builder_start_s: chan out; + + // AXI reader + axi_reader_ctrl_s: chan out; + + // data preprocess + data_preprocess_start_s: chan out; + + // decoder + decoder_start_s: chan out; + decoder_done_r: chan<()> in; + + config ( + ctrl_r: chan in, + prescan_start_s: chan out, + code_builder_start_s: chan out, + axi_reader_ctrl_s: chan out, + data_preprocess_start_s: chan out, + decoder_start_s: chan out, + decoder_done_r: chan<()> in, + ) { + ( + ctrl_r, + prescan_start_s, + code_builder_start_s, + axi_reader_ctrl_s, + data_preprocess_start_s, + decoder_start_s, + decoder_done_r, + ) + } + + init { + zero!() + } + + next (state: State) { + // receive start + let (tok, ctrl, ctrl_valid) = recv_if_non_blocking(join(), ctrl_r, state.fsm == FSM::IDLE, zero!()); + + let state = if ctrl_valid { + State { + fsm: FSM::DECODING, + } + } else { + state + }; + + // send start to prescan and code builder + let new_config = ctrl_valid & ctrl.new_config; + + if new_config { + trace_fmt!("Sending start to prescan and code builder"); + } else {}; + send_if(tok, prescan_start_s, new_config, true); + send_if(tok, code_builder_start_s, new_config, true); + + // send address and length to AXI reader + if ctrl_valid { + trace_fmt!("Sending ctrl to AXI reader"); + } else {}; + send_if(tok, axi_reader_ctrl_s, ctrl_valid, AxiReaderCtrl { + base_addr: ctrl.base_addr, + len: ctrl.len, + }); + + // send reconfigure/keep to data preprocessor and decoder + if ctrl_valid { + trace_fmt!("Sending start to data preprocessor and decoder"); + } else {}; + send_if(tok, data_preprocess_start_s, ctrl_valid, DataPreprocessorStart { + new_config: new_config, + }); + send_if(tok, decoder_start_s, ctrl_valid, DecoderStart { + new_config: new_config, + }); + + // receive done + let (_, _, decoder_done_valid) = recv_if_non_blocking(tok, decoder_done_r, state.fsm == FSM::DECODING, ()); + if decoder_done_valid { + trace_fmt!("Received decoder done"); + } else {}; + + if decoder_done_valid { + State { + fsm: FSM::IDLE + } + } else { + state + } + } +} + + +const INST_AXI_ADDR_W = u32:32; + +proc HuffmanControlAndSequenceInst { + type AxiReaderCtrl = axi_reader::HuffmanAxiReaderCtrl; + type DataPreprocessorStart = data_preprocessor::HuffmanDataPreprocessorStart; + type DecoderStart = decoder::HuffmanDecoderStart; + + config ( + ctrl_r: chan> in, + prescan_start_s: chan out, + code_builder_start_s: chan out, + axi_reader_ctrl_s: chan out, + data_preprocess_start_s: chan out, + decoder_start_s: chan out, + decoder_done_r: chan<()> in, + ) { + spawn HuffmanControlAndSequence( + ctrl_r, + prescan_start_s, + code_builder_start_s, + axi_reader_ctrl_s, + data_preprocess_start_s, + decoder_start_s, + decoder_done_r, + ); + } + + init { } + + next (state: ()) { } +} + + +const TEST_AXI_ADDR_W = u32:32; + +#[test_proc] +proc HuffmanControlAndSequence_test { + type Ctrl = HuffmanControlAndSequenceCtrl; + type AxiReaderCtrl = axi_reader::HuffmanAxiReaderCtrl; + type DataPreprocessorStart = data_preprocessor::HuffmanDataPreprocessorStart; + type DecoderStart = decoder::HuffmanDecoderStart; + + terminator: chan out; + + ctrl_s: chan> out; + prescan_start_r: chan in; + code_builder_start_r: chan in; + axi_reader_ctrl_r: chan in; + data_preprocess_start_r: chan in; + decoder_start_r: chan in; + decoder_done_s: chan<()> out; + + config (terminator: chan out) { + let (ctrl_s, ctrl_r) = chan("ctrl"); + let (prescan_start_s, prescan_start_r) = chan("prescan_start"); + let (code_builder_start_s, code_builder_start_r) = chan("code_builder_start"); + let (axi_reader_ctrl_s, axi_reader_ctrl_r) = chan("axi_reader_ctrl"); + let (data_preprocess_start_s, data_preprocess_start_r) = chan("data_preprocess_start"); + let (decoder_start_s, decoder_start_r) = chan("decoder_start"); + let (decoder_done_s, decoder_done_r) = chan<()>("decoder_done"); + + spawn HuffmanControlAndSequence( + ctrl_r, + prescan_start_s, + code_builder_start_s, + axi_reader_ctrl_s, + data_preprocess_start_s, + decoder_start_s, + decoder_done_r, + ); + + ( + terminator, + ctrl_s, + prescan_start_r, + code_builder_start_r, + axi_reader_ctrl_r, + data_preprocess_start_r, + decoder_start_r, + decoder_done_s, + ) + } + + init { } + + next (state: ()) { + let tok = join(); + + // without new config + let ctrl = Ctrl { + base_addr: uN[TEST_AXI_ADDR_W]:0x1, + len: uN[TEST_AXI_ADDR_W]:0x2, + new_config: false, + }; + let tok = send(tok, ctrl_s, ctrl); + + let (tok, axi_reader_ctrl) = recv(tok, axi_reader_ctrl_r); + assert_eq(AxiReaderCtrl {base_addr: ctrl.base_addr, len: ctrl.len}, axi_reader_ctrl); + + let (tok, data_preprocess_start) = recv(tok, data_preprocess_start_r); + assert_eq(DataPreprocessorStart {new_config: ctrl.new_config}, data_preprocess_start); + + let (tok, decoder_start) = recv(tok, decoder_start_r); + assert_eq(DecoderStart {new_config: ctrl.new_config}, decoder_start); + + let tok = send(tok, decoder_done_s, ()); + + // with new config + let ctrl = Ctrl { + base_addr: uN[TEST_AXI_ADDR_W]:0x1, + len: uN[TEST_AXI_ADDR_W]:0x2, + new_config: true, + }; + let tok = send(tok, ctrl_s, ctrl); + + let (tok, prescan_start) = recv(tok, prescan_start_r); + assert_eq(true, prescan_start); + + let (tok, code_builder_start) = recv(tok, code_builder_start_r); + assert_eq(true, code_builder_start); + + let (tok, axi_reader_ctrl) = recv(tok, axi_reader_ctrl_r); + assert_eq(AxiReaderCtrl {base_addr: ctrl.base_addr, len: ctrl.len}, axi_reader_ctrl); + + let (tok, data_preprocess_start) = recv(tok, data_preprocess_start_r); + assert_eq(DataPreprocessorStart {new_config: ctrl.new_config}, data_preprocess_start); + + let (tok, decoder_start) = recv(tok, decoder_start_r); + assert_eq(DecoderStart {new_config: ctrl.new_config}, decoder_start); + + let tok = send(tok, decoder_done_s, ()); + + send(tok, terminator, true); + } +} diff --git a/xls/modules/zstd/huffman_data_preprocessor.x b/xls/modules/zstd/huffman_data_preprocessor.x new file mode 100644 index 0000000000..4f97cb9943 --- /dev/null +++ b/xls/modules/zstd/huffman_data_preprocessor.x @@ -0,0 +1,422 @@ +// 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 Huffmann data preprocessor. + +import std; + +import xls.modules.zstd.huffman_common as hcommon; +import xls.modules.zstd.huffman_axi_reader as huffman_axi_reader; + +type Config = hcommon::CodeBuilderToPreDecoderOutput; + +pub const H_DATA_W = hcommon::MAX_CODE_LEN * u32:8; +pub const H_DATA_W_LOG2 = std::clog2(H_DATA_W + u32:1); + +pub type Data = uN[H_DATA_W]; + +pub type CodeLen = uN[H_DATA_W_LOG2]; + +const MAX_PREFIX_LEN = u4:7; + +enum HuffmanDataPreprocessorFSM: u2 { + IDLE = 0, + AWAITING_CONFIG = 1, + READ_DATA = 2, + PROCESSING = 3, +} + +pub struct HuffmanDataPreprocessorStart { + new_config: bool +} + +pub struct HuffmanDataPreprocessorData { + data: Data, + data_len: CodeLen, + code_length: CodeLen[H_DATA_W], +} + +struct HuffmanDataPreprocessorState { + fsm: HuffmanDataPreprocessorFSM, + lookahead_config: Config, + data: Data, + data_len: CodeLen, +} + +pub proc HuffmanDataPreprocessor { + type State = HuffmanDataPreprocessorState; + type FSM = HuffmanDataPreprocessorFSM; + type Start = HuffmanDataPreprocessorStart; + type DataIn = huffman_axi_reader::HuffmanAxiReaderData; + type PreprocessedData = HuffmanDataPreprocessorData; + + start_r: chan in; + lookahead_config_r: chan in; + data_r: chan in; + + preprocessed_data_s: chan out; + + config ( + start_r: chan in, + lookahead_config_r: chan in, + data_r: chan in, + preprocessed_data_s: chan out, + ) { + ( + start_r, + lookahead_config_r, + data_r, + preprocessed_data_s, + ) + } + + init { zero!() } + + next (state: State) { + let tok = join(); + + // wait for start + let (tok, start, start_valid) = recv_if_non_blocking(tok, start_r, state.fsm == FSM::IDLE, zero!()); + + let state = if start_valid { + let fsm = if start.new_config { + trace_fmt!("Waiting for new config"); + FSM::AWAITING_CONFIG + } else { + FSM::READ_DATA + }; + State { + fsm: fsm, + ..state + } + } else { state }; + + // wait for config + let (tok, config, config_valid) = recv_if_non_blocking( + tok, + lookahead_config_r, + state.fsm == FSM::AWAITING_CONFIG, + zero!() + ); + + let state = if config_valid { + State { + fsm: FSM::READ_DATA, + lookahead_config: config, + ..state + } + } else { state }; + + // receive data + let do_read_data = state.fsm == FSM::READ_DATA; + let (tok, data, data_valid) = recv_if_non_blocking(tok, data_r, do_read_data, zero!()); + + // process data + let state = if data_valid { + let fsm = if data.last { + FSM::PROCESSING + } else { + state.fsm + }; + State { + fsm: fsm, + data: state.data | ((rev(data.data) as Data) << state.data_len), + data_len: state.data_len + CodeLen:8, + ..state + } + } else { state }; + + let processed_data = if state.fsm == FSM::PROCESSING { + let data_bits = state.data; + let data_bits_len = state.data_len; + + // remove prefix + let (prefix_len, _) = for (i, (prefix_len, stop)): (u32, (u4, bool)) in range(u32:0, MAX_PREFIX_LEN as u32) { + if stop || (data_bits >> i) as u1 { + ( + prefix_len, + true, + ) + } else { + ( + prefix_len + u4:1, + stop, + ) + } + }((u4:1, false)); + + trace_fmt!("Prefix len: {}", prefix_len); + + let data_bits = data_bits >> prefix_len; + let data_bits_len = data_bits_len - prefix_len as CodeLen; + + // compute Huffman code lengths + + // compute number of zeros + let (code_lengths, _) = for (i, (code_lengths, num_zeros)): (u32, (CodeLen[H_DATA_W], CodeLen)) in range(u32:0, H_DATA_W) { + // reverse order + let n = H_DATA_W - u32:1 - i; + if n < data_bits_len as u32 { + // if non zero then reset counter, otherwise increment + let num_zeros = if (data_bits >> n) as u1 { + CodeLen:0 + } else { + num_zeros + CodeLen:1 + }; + // clip code len by max code length + let code_len = if num_zeros >= state.lookahead_config.max_code_length as CodeLen { + state.lookahead_config.max_code_length as CodeLen + } else { + num_zeros + CodeLen:1 + }; + ( + update(code_lengths, n, code_len), num_zeros + ) + } else { + (code_lengths, num_zeros) + } + }((zero!(), CodeLen:0)); + + // round up number of zeros to possible length + let code_lengths = for (i, code_lengths): (u32, CodeLen[H_DATA_W]) in range(u32:0, H_DATA_W) { + if i < data_bits_len as u32 { + let length = for (weight, length): (u32, CodeLen) in range(u32:0, hcommon::MAX_WEIGHT + u32:1) { + let weight_valid = state.lookahead_config.valid_weights[weight]; + let number_of_bits = if weight > u32:0 { + state.lookahead_config.max_code_length as u32 + u32:1 - weight + } else { + u32:0 + }; + if (code_lengths[i] <= number_of_bits as CodeLen) && weight_valid { + number_of_bits as CodeLen + } else { + length + } + }(code_lengths[i]); + update(code_lengths, i, length) + } else { + code_lengths + } + }(code_lengths); + + PreprocessedData { + data: data_bits, + data_len: data_bits_len, + code_length: code_lengths, + } + + } else { zero!() }; + + let tok = send_if(tok, preprocessed_data_s, state.fsm == FSM::PROCESSING, processed_data); + + let state = if state.fsm == FSM::PROCESSING { + State { + fsm: FSM::IDLE, + lookahead_config: state.lookahead_config, + ..zero!() + } + } else { state }; + + state + } +} + +const TEST_START = HuffmanDataPreprocessorStart[2]:[ + HuffmanDataPreprocessorStart { + new_config: true, + }, + HuffmanDataPreprocessorStart { + new_config: true, + }, +]; + +const TEST_CONFIG = Config[2]:[ + Config { + max_code_length: uN[hcommon::WEIGHT_LOG]:6, + valid_weights: [false, true, false, true, true, false, true, false, false, false, false, false] + }, + Config { + max_code_length: uN[hcommon::WEIGHT_LOG]:9, + valid_weights: [false, true, false, false, true, false, false, true, false, true, false, false] + } +]; + +const TEST_DATA = huffman_axi_reader::HuffmanAxiReaderData[12]:[ + // #1 + huffman_axi_reader::HuffmanAxiReaderData { + data: u8:0b01010000, + last: false, + }, + huffman_axi_reader::HuffmanAxiReaderData { + data: u8:0b01011011, + last: false, + }, + huffman_axi_reader::HuffmanAxiReaderData { + data: u8:0b01000001, + last: false, + }, + huffman_axi_reader::HuffmanAxiReaderData { + data: u8:0b01010011, + last: true, + }, + // #2 + huffman_axi_reader::HuffmanAxiReaderData { + data: u8:0b00110100, + last: false, + }, + huffman_axi_reader::HuffmanAxiReaderData { + data: u8:0b11110001, + last: false, + }, + huffman_axi_reader::HuffmanAxiReaderData { + data: u8:0b01010000, + last: false, + }, + huffman_axi_reader::HuffmanAxiReaderData { + data: u8:0b00101010, + last: false, + }, + huffman_axi_reader::HuffmanAxiReaderData { + data: u8:0b11010100, + last: false, + }, + huffman_axi_reader::HuffmanAxiReaderData { + data: u8:0b01000010, + last: false, + }, + huffman_axi_reader::HuffmanAxiReaderData { + data: u8:0b01010101, + last: false, + }, + huffman_axi_reader::HuffmanAxiReaderData { + data: u8:0b10010101, + last: true, + }, +]; + +const TEST_PREPROCESSED_DATA = HuffmanDataPreprocessorData[2]:[ + HuffmanDataPreprocessorData { + data: Data:0b110_010_1_010000_010_110_1_1_010000_010, + data_len: CodeLen:30, + code_length: [ + CodeLen:3, CodeLen:1, CodeLen:6, CodeLen:6, CodeLen:4, CodeLen:3, CodeLen:3, CodeLen:1, + CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:3, CodeLen:1, + CodeLen:6, CodeLen:6, CodeLen:4, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, + CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + ], + }, + HuffmanDataPreprocessorData { + data: Data:0b1_010_100_110_1_010_100_100_001000_1_010_110_1_010_100000_010_101000_1_1_110_010_1, + data_len: CodeLen:61, + code_length: [ + CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:1, + CodeLen:1, CodeLen:6, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, + CodeLen:1, CodeLen:9, CodeLen:6, CodeLen:6, CodeLen:6, CodeLen:3, CodeLen:3, CodeLen:1, + CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:3, + CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:6, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:6, + CodeLen:6, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, + CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:3, CodeLen:3, + CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + ], + } +]; + +#[test_proc] +proc HuffmanDataPreprocessor_test { + type State = HuffmanDataPreprocessorState; + type Start = HuffmanDataPreprocessorStart; + type Data = huffman_axi_reader::HuffmanAxiReaderData; + type PreprocessedData = HuffmanDataPreprocessorData; + + terminator_s: chan out; + + start_s: chan out; + lookahead_config_s: chan out; + data_s: chan out; + + preprocessed_data_r: chan in; + + config (terminator_s: chan out) { + let (start_s, start_r) = chan("start"); + let (lookahead_config_s, lookahead_config_r) = chan("lookahead_config"); + let (data_s, data_r) = chan("data"); + let (preprocessed_data_s, preprocessed_data_r) = chan("preprocessed_data"); + + spawn HuffmanDataPreprocessor( + start_r, + lookahead_config_r, + data_r, + preprocessed_data_s, + ); + + ( + terminator_s, + start_s, + lookahead_config_s, + data_s, + preprocessed_data_r, + ) + } + + init { } + + next (state: ()) { + let tok = join(); + + let (tok, _, _) = for ((i, test_start), (tok, cfg_idx, data_idx)): ((u32, Start), (token, u32, u32)) in enumerate(TEST_START) { + let tok = send(tok, start_s, test_start); + trace_fmt!("Sent #{} start {:#x}", i + u32:1, test_start); + + let (tok, cfg_idx) = if test_start.new_config { + let tok = send(tok, lookahead_config_s, TEST_CONFIG[cfg_idx]); + trace_fmt!("Sent #{} config {:#x}", cfg_idx + u32:1, TEST_CONFIG[cfg_idx]); + (tok, cfg_idx + u32:1) + } else { (tok, cfg_idx) }; + + let (tok, data_idx, _) = for (_, (tok, data_idx, do_send)) in range(u32:0, hcommon::MAX_CODE_LEN) { + if data_idx < array_size(TEST_DATA) { + let data = TEST_DATA[data_idx]; + + if do_send { + let tok = send(tok, data_s, data); + trace_fmt!("Sent #{} data {:#x}", data_idx + u32:1, data); + (tok, data_idx + u32:1, !data.last) + } else { + (tok, data_idx, false) + } + } else { (tok, data_idx, false) } + }((tok, data_idx, true)); + + let (tok, preprocessed_data) = recv(tok, preprocessed_data_r); + trace_fmt!("Received #{} preprocessed data {:#x}", i + u32:1, preprocessed_data); + assert_eq(TEST_PREPROCESSED_DATA[i], preprocessed_data); + + (tok, cfg_idx, data_idx) + }((tok, u32:0, u32:0)); + + send(tok, terminator_s, true); + } +} diff --git a/xls/modules/zstd/huffman_decoder.x b/xls/modules/zstd/huffman_decoder.x new file mode 100644 index 0000000000..e13bc484d1 --- /dev/null +++ b/xls/modules/zstd/huffman_decoder.x @@ -0,0 +1,724 @@ +// 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 Huffman decoder. + +import std; + +import xls.modules.zstd.common as common; +import xls.modules.zstd.huffman_common as hcommon; +import xls.modules.zstd.huffman_data_preprocessor as huffman_data_preprocessor; + +type Codes = hcommon::CodeBuilderToDecoderOutput; +type CodeLen = huffman_data_preprocessor::CodeLen; + +const SYMBOLS_N = u32:1 << common::SYMBOL_WIDTH; + +const H_DATA_W = hcommon::MAX_CODE_LEN * u32:8; +const H_DATA_W_LOG2 = std::clog2(H_DATA_W + u32:1); + +const BUFF_W = H_DATA_W * u32:2; +const BUFF_W_LOG2 = std::clog2(BUFF_W + u32:1); + +enum HuffmanDecoderFSM: u3 { + IDLE = 0, + AWAITING_CONFIG = 1, + READ_DATA = 2, + DECODE = 3, +} + +pub struct HuffmanDecoderStart { + new_config: bool, +} + +struct HuffmanDecoderState { + fsm: HuffmanDecoderFSM, + symbol_config_id: u5, + symbol_valid: bool[SYMBOLS_N], + symbol_code: uN[hcommon::MAX_WEIGHT][SYMBOLS_N], + symbol_code_len: uN[hcommon::WEIGHT_LOG][SYMBOLS_N], + data_len: uN[BUFF_W_LOG2], + data: uN[BUFF_W], + code_length: CodeLen[BUFF_W], + decoded_literals: uN[common::SYMBOL_WIDTH][u32:8], + decoded_literals_len: u4, +} + +fn extend_buff_array(buff: CodeLen[N], buff_len: u32, array: CodeLen[M]) -> CodeLen[N] { + const ELEM_SIZE = huffman_data_preprocessor::H_DATA_W_LOG2; + + let buff_flat = buff as uN[ELEM_SIZE * N]; + let array_flat = array as uN[ELEM_SIZE * M]; + let buff_flat = ( + buff_flat | + (array_flat as uN[ELEM_SIZE * N] << (ELEM_SIZE * (N - M - buff_len))) + ); + buff_flat as CodeLen[N] +} + +#[test] +fn extend_buff_array_test() { + assert_eq( + CodeLen[8]:[CodeLen:1, CodeLen:2, CodeLen:3, CodeLen:4, CodeLen:5, CodeLen:0, CodeLen:0, CodeLen:0], + extend_buff_array( + CodeLen[8]:[CodeLen:1, CodeLen:2, CodeLen:3, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0], + u32:3, + CodeLen[2]:[CodeLen:4, CodeLen:5], + ), + ); + assert_eq( + CodeLen[8]:[CodeLen:1, CodeLen:2, CodeLen:3, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0], + extend_buff_array( + zero!(), + u32:0, + CodeLen[3]:[CodeLen:1, CodeLen:2, CodeLen:3], + ), + ); +} + +fn shift_buff_array(buff: CodeLen[N], shift: u32) -> CodeLen[N] { + const ELEM_SIZE = huffman_data_preprocessor::H_DATA_W_LOG2; + + let buff_flat = buff as uN[ELEM_SIZE * N]; + let buff_flat = buff_flat << (ELEM_SIZE * shift); + buff_flat as CodeLen[N] +} + +#[test] +fn shift_buff_array_test() { + assert_eq( + CodeLen[8]:[CodeLen:4, CodeLen:5, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0], + shift_buff_array( + CodeLen[8]:[CodeLen:1, CodeLen:2, CodeLen:3, CodeLen:4, CodeLen:5, CodeLen:0, CodeLen:0, CodeLen:0], + u32:3, + ), + ); + assert_eq( + CodeLen[8]:[CodeLen:1, CodeLen:2, CodeLen:3, CodeLen:4, CodeLen:5, CodeLen:0, CodeLen:0, CodeLen:0], + shift_buff_array( + CodeLen[8]:[CodeLen:1, CodeLen:2, CodeLen:3, CodeLen:4, CodeLen:5, CodeLen:0, CodeLen:0, CodeLen:0], + u32:0, + ), + ); +} + +pub proc HuffmanDecoder { + type State = HuffmanDecoderState; + type FSM = HuffmanDecoderFSM; + type Start = HuffmanDecoderStart; + type Data = huffman_data_preprocessor::HuffmanDataPreprocessorData; + + start_r: chan in; + codes_r: chan in; + data_r: chan in; + + done_s: chan<()> out; + decoded_literals_s: chan out; + + config ( + start_r: chan in, + codes_r: chan in, + data_r: chan in, + done_s: chan<()> out, + decoded_literals_s: chan out, + ) { + ( + start_r, + codes_r, + data_r, + done_s, + decoded_literals_s, + ) + } + + init { zero!() } + + next (state: State) { + let tok = join(); + + // wait for start + let (tok, start, start_valid) = recv_if_non_blocking( + tok, start_r, state.fsm == FSM::IDLE, zero!() + ); + + let state = if start_valid { + if start.new_config { + trace_fmt!("IDLE -> AWAITING_CONFIG"); + State { + fsm: FSM::AWAITING_CONFIG, + symbol_config_id: u5:0, + ..state + } + } else { + trace_fmt!("IDLE -> READ_DATA"); + State { + fsm: FSM::READ_DATA, + ..state + } + } + } else { state }; + + // wait for config + let (tok, config, config_valid) = recv_if_non_blocking( + tok, + codes_r, + state.fsm == FSM::AWAITING_CONFIG, + zero!() + ); + + let state = if config_valid { + let (symbol_valid, symbol_code, symbol_code_len) = + for (i, (symbol_valid, symbol_code, symbol_code_len)): + ( + u32, + ( + bool[SYMBOLS_N], + uN[hcommon::MAX_WEIGHT][SYMBOLS_N], + uN[hcommon::WEIGHT_LOG][SYMBOLS_N], + ) + ) in range(u32:0, hcommon::PARALLEL_ACCESS_WIDTH) { + ( + update(symbol_valid, (state.symbol_config_id as u32 * u32:8) + i, config.symbol_valid[i]), + update(symbol_code, (state.symbol_config_id as u32 * u32:8) + i, config.code[i]), + update(symbol_code_len, (state.symbol_config_id as u32 * u32:8) + i, config.code_length[i]), + ) + }((state.symbol_valid, state.symbol_code, state.symbol_code_len)); + let fsm = if (state.symbol_config_id as u32 + u32:1) == (SYMBOLS_N / hcommon::PARALLEL_ACCESS_WIDTH) { + trace_fmt!("AWAITING_CONFIG -> READ_DATA"); + trace_fmt!("Received codes:"); + for (i, ()) in range(u32:0, SYMBOLS_N) { + if symbol_valid[i] { + trace_fmt!(" {:#b} (len {}) -> {:#x}", symbol_code[i], symbol_code_len[i], i); + } else {}; + }(()); + FSM::READ_DATA + } else { + state.fsm + }; + State { + fsm: fsm, + symbol_config_id: state.symbol_config_id + u5:1, + symbol_valid: symbol_valid, + symbol_code: symbol_code, + symbol_code_len: symbol_code_len, + ..state + } + } else { state }; + + // receive data + let (tok, data, data_valid) = recv_if_non_blocking( + tok, data_r, (state.fsm == FSM::READ_DATA) && (state.data_len as u32 < H_DATA_W), zero!() + ); + + let state = if data_valid { + trace_fmt!("READ_DATA -> DECODE"); + trace_fmt!("Received data: {:#b} (len: {})", data.data, data.data_len); + State { + fsm: FSM::DECODE, + data_len: state.data_len + data.data_len as uN[BUFF_W_LOG2], + data: state.data | (data.data as uN[BUFF_W] << state.data_len), + code_length: extend_buff_array(state.code_length, state.data_len as u32, data.code_length), + ..state + } + } else { + state + }; + + // decode data + let state = if ( + state.fsm == FSM::DECODE && + state.data_len > uN[BUFF_W_LOG2]:0 && + state.data_len >= state.code_length[0] as uN[BUFF_W_LOG2] + ) { + let data_mask = (!uN[hcommon::MAX_WEIGHT]:0) >> (hcommon::MAX_WEIGHT - state.code_length[0] as u32); + let data_masked = state.data as uN[hcommon::MAX_WEIGHT] & data_mask; + + trace_fmt!("Data to be decoded: {:#b} (len: {})", data_masked, state.code_length[0]); + + let literals = for (i, literals):(u32, uN[common::SYMBOL_WIDTH][SYMBOLS_N]) in range(u32:0, SYMBOLS_N){ + if ( + state.symbol_valid[i] && + (data_masked == state.symbol_code[i]) + ) { + update(literals, i, i as uN[common::SYMBOL_WIDTH]) + } else { + literals + } + }(zero!()); + + // assuming only one code was valid, we can compute 'or' of all array elements + let literal = for (i, literal):(u32, uN[common::SYMBOL_WIDTH]) in range(u32:0, SYMBOLS_N) { + literal | literals[i] + }(uN[common::SYMBOL_WIDTH]:0); + + // shift buffer + State { + decoded_literals: update(state.decoded_literals, state.decoded_literals_len, literal), + decoded_literals_len: state.decoded_literals_len + u4:1, + data_len: state.data_len - state.code_length[0] as uN[BUFF_W_LOG2], + data: state.data >> state.code_length[0], + code_length: shift_buff_array(state.code_length, state.code_length[0] as u32), + ..state + } + } else { + state + }; + + // send literals + let do_send_literals = ( + state.decoded_literals_len == u4:8 || + (state.decoded_literals_len > u4:0 && state.data_len == uN[BUFF_W_LOG2]:0) + ); + + let data = if do_send_literals { + for (i, data): (u32, common::LitData) in range(u32:0, u32:8) { + data | (state.decoded_literals[i] as common::LitData << (common::SYMBOL_WIDTH * i)) + }(zero!()) + } else { + zero!() + }; + + let done = state.data_len == uN[BUFF_W_LOG2]:0; + send_if(tok, decoded_literals_s, do_send_literals, common::LiteralsData{ + data: data, + length: state.decoded_literals_len as common::LitLength, + last: done, + }); + + let state = if do_send_literals { + let fsm = if state.data_len == uN[BUFF_W_LOG2]:0 { + trace_fmt!("DECODE -> IDLE"); + FSM::IDLE + } else { + FSM::DECODE + }; + State { + fsm: fsm, + decoded_literals_len: u4:0, + decoded_literals: zero!(), + ..state + } + } else { + state + }; + + send_if(tok, done_s, done, ()); + + state + } +} + +type TestCodeLen = uN[hcommon::WEIGHT_LOG]; +type TestCode = uN[hcommon::MAX_WEIGHT]; + +struct SymbolData { + symbol_valid: bool, + code_length: TestCodeLen, + code: TestCode, +} + +// helper function to improve readability of test data +fn generate_codes(data: SymbolData[8]) -> Codes { + Codes { + symbol_valid: [ + data[0].symbol_valid, data[1].symbol_valid, data[2].symbol_valid, data[3].symbol_valid, + data[4].symbol_valid, data[5].symbol_valid, data[6].symbol_valid, data[7].symbol_valid, + ], + code_length: [ + data[0].code_length, data[1].code_length, data[2].code_length, data[3].code_length, + data[4].code_length, data[5].code_length, data[6].code_length, data[7].code_length, + ], + code: [ + data[0].code, data[1].code, data[2].code, data[3].code, + data[4].code, data[5].code, data[6].code, data[7].code, + ], + } +} + +const TEST_START = HuffmanDecoderStart[3]:[ + HuffmanDecoderStart { new_config: true }, + HuffmanDecoderStart { new_config: false }, + HuffmanDecoderStart { new_config: true }, +]; + +// config #1 +// 0b1 -> 0x06 +// 0b100 -> 0x03 +// 0b010 -> 0x00 +// 0b110 -> 0x02 +// 0b1000 -> 0x1B +// 0b000000 -> 0xB6 +// 0b010000 -> 0xB5 +// 0b100000 -> 0x0D +// 0b110000 -> 0xB2 +// +// config #2 +// 0b1 -> 0x47 +// 0b001 -> 0x41 +// 0b010 -> 0xD2 +// 0b011 -> 0x8A +// 0b000001 -> 0x7A +// 0b000010 -> 0xDA +// 0b000011 -> 0x45 +// 0b000100 -> 0xD3 +// 0b000101 -> 0x89 +// 0b000110 -> 0x8D +// 0b000111 -> 0xD1 +// 0b000000001 -> 0xAC +// 0b000000010 -> 0x8F +// 0b000000011 -> 0xDB +// 0b000000100 -> 0xD4 +// 0b000000101 -> 0xFE +// 0b000000110 -> 0xDE +// 0b000000111 -> 0xD7 + +const TEST_CODES = Codes[64]:[ + // config #1 + generate_codes([ // 0x00 - 0x07 + SymbolData { symbol_valid: true, code_length: TestCodeLen:3, code: TestCode:0b010 }, + zero!(), + SymbolData { symbol_valid: true, code_length: TestCodeLen:3, code: TestCode:0b110 }, + SymbolData { symbol_valid: true, code_length: TestCodeLen:3, code: TestCode:0b100 }, + zero!(), + zero!(), + SymbolData { symbol_valid: true, code_length: TestCodeLen:1, code: TestCode:0b1 }, + zero!(), + ]), + zero!(), // 0x08 - 0x0F + zero!(), // 0x10 - 0x17 + generate_codes([ // 0x18 - 0x1F + zero!(), + zero!(), + zero!(), + SymbolData { symbol_valid: true, code_length: TestCodeLen:4, code: TestCode:0b1000 }, + zero!(), + zero!(), + zero!(), + zero!(), + ]), + zero!(), // 0x20 - 0x27 + zero!(), // 0x28 - 0x2F + zero!(), // 0x30 - 0x37 + zero!(), // 0x38 - 0x3F + zero!(), // 0x40 - 0x47 + zero!(), // 0x48 - 0x4F + zero!(), // 0x50 - 0x67 + zero!(), // 0x58 - 0x5F + zero!(), // 0x60 - 0x67 + zero!(), // 0x68 - 0x6F + zero!(), // 0x70 - 0x77 + zero!(), // 0x78 - 0x7F + zero!(), // 0x80 - 0x87 + zero!(), // 0x88 - 0x8F + zero!(), // 0x90 - 0x97 + zero!(), // 0x98 - 0x9F + zero!(), // 0xA0 - 0xA7 + zero!(), // 0xA8 - 0xAF + generate_codes([ // 0xB0 - 0xB7 + zero!(), + zero!(), + SymbolData { symbol_valid: true, code_length: TestCodeLen:6, code: TestCode:0b110000 }, + zero!(), + zero!(), + SymbolData { symbol_valid: true, code_length: TestCodeLen:6, code: TestCode:0b010000 }, + SymbolData { symbol_valid: true, code_length: TestCodeLen:6, code: TestCode:0b000000 }, + zero!(), + ]), + zero!(), // 0xB8 - 0xBF + zero!(), // 0xC0 - 0xC7 + zero!(), // 0xC8 - 0xCF + zero!(), // 0xD0 - 0xD7 + zero!(), // 0xD8 - 0xDF + zero!(), // 0xE0 - 0xE7 + zero!(), // 0xE8 - 0xEF + zero!(), // 0xF0 - 0xF7 + zero!(), // 0xF8 - 0xFF + // config #2 + zero!(), // 0x00 - 0x07 + zero!(), // 0x08 - 0x0F + zero!(), // 0x10 - 0x17 + zero!(), // 0x18 - 0x1F + zero!(), // 0x20 - 0x27 + zero!(), // 0x28 - 0x2F + zero!(), // 0x30 - 0x37 + zero!(), // 0x38 - 0x3F + generate_codes([ // 0x40 - 0x47 + zero!(), + SymbolData { symbol_valid: true, code_length: TestCodeLen:3, code: TestCode:0b100 }, + zero!(), + zero!(), + zero!(), + SymbolData { symbol_valid: true, code_length: TestCodeLen:6, code: TestCode:0b110000 }, + zero!(), + SymbolData { symbol_valid: true, code_length: TestCodeLen:1, code: TestCode:0b1 }, + ]), + zero!(), // 0x48 - 0x4F + zero!(), // 0x50 - 0x67 + zero!(), // 0x58 - 0x5F + zero!(), // 0x60 - 0x67 + zero!(), // 0x68 - 0x6F + zero!(), // 0x70 - 0x77 + generate_codes([ // 0x78 - 0x7F + zero!(), + zero!(), + SymbolData { symbol_valid: true, code_length: TestCodeLen:6, code: TestCode:0b100000 }, + zero!(), + zero!(), + zero!(), + zero!(), + zero!(), + ]), + zero!(), // 0x80 - 0x87 + generate_codes([ // 0x88 - 0x8F + zero!(), + SymbolData { symbol_valid: true, code_length: TestCodeLen:6, code: TestCode:0b101000 }, + SymbolData { symbol_valid: true, code_length: TestCodeLen:3, code: TestCode:0b110 }, + zero!(), + zero!(), + SymbolData { symbol_valid: true, code_length: TestCodeLen:6, code: TestCode:0b110000 }, + zero!(), + SymbolData { symbol_valid: true, code_length: TestCodeLen:9, code: TestCode:0b0100000000 }, + ]), + zero!(), // 0x90 - 0x97 + zero!(), // 0x98 - 0x9F + zero!(), // 0xA0 - 0xA7 + generate_codes([ // 0xA8 - 0xAF + zero!(), + zero!(), + zero!(), + zero!(), + SymbolData { symbol_valid: true, code_length: TestCodeLen:9, code: TestCode:0b100000000 }, + zero!(), + zero!(), + zero!(), + ]), + zero!(), // 0xB0 - 0xB7 + zero!(), // 0xB8 - 0xBF + zero!(), // 0xC0 - 0xC7 + zero!(), // 0xC8 - 0xCF + generate_codes([ // 0xD0 - 0xD7 + zero!(), + SymbolData { symbol_valid: true, code_length: TestCodeLen:6, code: TestCode:0b111000 }, + SymbolData { symbol_valid: true, code_length: TestCodeLen:3, code: TestCode:0b010 }, + SymbolData { symbol_valid: true, code_length: TestCodeLen:6, code: TestCode:0b001000 }, + SymbolData { symbol_valid: true, code_length: TestCodeLen:9, code: TestCode:0b001000000 }, + zero!(), + zero!(), + SymbolData { symbol_valid: true, code_length: TestCodeLen:9, code: TestCode:0b111000000 }, + ]), + generate_codes([ // 0xD8 - 0xDF + zero!(), + zero!(), + SymbolData { symbol_valid: true, code_length: TestCodeLen:6, code: TestCode:0b010000 }, + SymbolData { symbol_valid: true, code_length: TestCodeLen:9, code: TestCode:0b110000000 }, + zero!(), + zero!(), + SymbolData { symbol_valid: true, code_length: TestCodeLen:9, code: TestCode:0b011000000 }, + zero!(), + ]), + zero!(), // 0xE0 - 0xE7 + zero!(), // 0xE8 - 0xEF + zero!(), // 0xF0 - 0xF7 + generate_codes([ // 0xF8 - 0xFF + zero!(), + zero!(), + zero!(), + zero!(), + zero!(), + zero!(), + SymbolData { symbol_valid: true, code_length: TestCodeLen:9, code: TestCode:0b101000000 }, + zero!(), + ]), +]; + +const TEST_DATA = huffman_data_preprocessor::HuffmanDataPreprocessorData[3]:[ + huffman_data_preprocessor::HuffmanDataPreprocessorData { + data: huffman_data_preprocessor::Data:0x32a0b682, + data_len: CodeLen:30, + code_length: [ + CodeLen:3, CodeLen:1, CodeLen:6, CodeLen:6, CodeLen:4, CodeLen:3, CodeLen:3, CodeLen:1, + CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:3, CodeLen:1, + CodeLen:6, CodeLen:6, CodeLen:4, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, + CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + ], + }, + huffman_data_preprocessor::HuffmanDataPreprocessorData { + data: huffman_data_preprocessor::Data:0x32a0b682, + data_len: CodeLen:30, + code_length: [ + CodeLen:3, CodeLen:1, CodeLen:6, CodeLen:6, CodeLen:4, CodeLen:3, CodeLen:3, CodeLen:1, + CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:3, CodeLen:1, + CodeLen:6, CodeLen:6, CodeLen:4, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, + CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + ], + }, + huffman_data_preprocessor::HuffmanDataPreprocessorData { + data: huffman_data_preprocessor::Data:0b1_010_100_110_1_010_100_100_001000_1_010_110_1_010_100000_010_101000_1_1_110_010_1, + data_len: CodeLen:61, + code_length: [ + CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:1, + CodeLen:1, CodeLen:6, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, + CodeLen:1, CodeLen:9, CodeLen:6, CodeLen:6, CodeLen:6, CodeLen:3, CodeLen:3, CodeLen:1, + CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:3, + CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:6, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:6, + CodeLen:6, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, + CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:3, CodeLen:3, + CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, + ], + }, +]; + +const TEST_LITERALS = common::LiteralsData[7]:[ + common::LiteralsData { + data: common::LitData:0x06B5_0002_0606_B500, + length: common::LitLength:8, + last: false, + }, + common::LiteralsData { + data: common::LitData:0x0200, + length: common::LitLength:2, + last: true, + }, + common::LiteralsData { + data: common::LitData:0x06B5_0002_0606_B500, + length: common::LitLength:8, + last: false, + }, + common::LiteralsData { + data: common::LitData:0x0200, + length: common::LitLength:2, + last: true, + }, + common::LiteralsData { + data: common::LitData:0x7AD2_8947_478A_D247, + length: common::LitLength:8, + last: false, + }, + common::LiteralsData { + data: common::LitData:0x4141_D347_D28A_47D2, + length: common::LitLength:8, + last: false, + }, + common::LiteralsData { + data: common::LitData:0x47D2_418A_47D2, + length: common::LitLength:6, + last: true, + }, +]; + +#[test_proc] +proc HuffmanDecoder_test { + type Start = HuffmanDecoderStart; + type Data = huffman_data_preprocessor::HuffmanDataPreprocessorData; + + terminator_s: chan out; + + start_s: chan out; + codes_s: chan out; + data_s: chan out; + + done_r: chan<()> in; + decoded_literals_r: chan in; + + config (terminator_s: chan out) { + let (start_s, start_r) = chan("start"); + let (codes_s, codes_r) = chan("codes"); + let (data_s, data_r) = chan("data"); + let (done_s, done_r) = chan<()>("done"); + let (decoded_literals_s, decoded_literals_r) = chan("decoded_literals"); + + spawn HuffmanDecoder( + start_r, codes_r, data_r, + done_s, decoded_literals_s, + ); + ( + terminator_s, + start_s, + codes_s, + data_s, + done_r, + decoded_literals_r, + ) + } + + init { } + + next (state: ()) { + let tok = join(); + + let (tok, _) = for ((i, start), (tok, codes_idx)): ((u32, Start), (token, u32)) in enumerate(TEST_START) { + // send start + let tok = send(tok, start_s, start); + trace_fmt!("Sent #{} start {:#x}", i + u32:1, start); + + // send codes if required + let (tok, codes_idx) = if start.new_config { + for (_, (tok, codes_idx)): (u32, (token, u32)) in range(u32:0, SYMBOLS_N / hcommon::PARALLEL_ACCESS_WIDTH) { + let tok = send(tok, codes_s, TEST_CODES[codes_idx]); + trace_fmt!("Send #{} codes {:#x}", codes_idx + u32:1, TEST_CODES[codes_idx]); + (tok, codes_idx + u32:1) + }((tok, codes_idx)) + } else { + (tok, codes_idx) + }; + + // send data + let tok = send(tok, data_s, TEST_DATA[i]); + trace_fmt!("Sent #{} data {:#x}", i + u32:1, TEST_DATA[i]); + + (tok, codes_idx) + }((tok, u32:0)); + + let tok = for ((i, expected_literals), tok): ((u32, common::LiteralsData), token) in enumerate(TEST_LITERALS) { + // receive literals + let (tok, literals) = recv(tok, decoded_literals_r); + trace_fmt!("Received #{} literals {:#x}", i + u32:1, literals); + + assert_eq(expected_literals, literals); + + // receive done + let tok = if expected_literals.last { + let (tok, _) = recv(tok, done_r); + tok + } else { + tok + }; + + tok + }(tok); + + send(tok, terminator_s, true); + } + +} diff --git a/xls/modules/zstd/huffman_literals_dec.x b/xls/modules/zstd/huffman_literals_dec.x new file mode 100644 index 0000000000..c93a736404 --- /dev/null +++ b/xls/modules/zstd/huffman_literals_dec.x @@ -0,0 +1,547 @@ +// 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 Huffman literals decoder proc implementation. + +import xls.modules.zstd.common as common; +import xls.modules.zstd.huffman_common as hcommon; +import xls.modules.zstd.huffman_axi_reader as axi_reader; +import xls.modules.zstd.huffman_code_builder as code_builder; +import xls.modules.zstd.huffman_data_preprocessor as data_preprocessor; +import xls.modules.zstd.huffman_decoder as decoder; +import xls.modules.zstd.huffman_prescan as prescan; +import xls.modules.zstd.huffman_ctrl as ctrl; +import xls.modules.zstd.memory.axi as axi; +import xls.examples.ram; + +pub proc HuffmanLiteralsDecoder { + type AxiR = axi::AxiR; + type AxiAr = axi::AxiAr; + + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + + type HuffmanAxiReaderCtrl = axi_reader::HuffmanAxiReaderCtrl; + + type PrescanInternalReadReq = ram::ReadReq; + type PrescanInternalReadResp = ram::ReadResp<{prescan::WeightPreScanMetaDataSize()}>; + type PrescanInternalWriteReq = ram::WriteReq; + type PrescanInternalWriteResp = ram::WriteResp; + + config ( + // ctrl + ctrl_r: chan in, + // output literals + decoded_literals_s: chan out, + // AXI interface + axi_ar_s: chan out, + axi_r_r: chan in, + // weight memory + ram_read_req_s: chan out, + ram_read_resp_r: chan in, + // code builder loopback + weights_pow_sum_loopback_s: chan out, + weights_pow_sum_loopback_r: chan in, + ) { + let (prescan_start_s, prescan_start_r) = chan("prescan_start"); + let (code_builder_start_s, code_builder_start_r) = chan("code_buider"); + let (axi_reader_ctrl_s, axi_reader_ctrl_r) = chan("axi_reader_ctrl"); + let (data_preprocess_start_s, data_preprocess_start_r) = chan("data_preprocess_start"); + let (decoder_start_s, decoder_start_r) = chan("decoder_start"); + let (decoder_done_s, decoder_done_r) = chan<(), u32:1>("decoder_done"); + let (prescan_response_s, prescan_response_r) = chan("prescan_response"); + let (code_builder_codes_s, code_builder_codes_r) = chan("code_builder_codes"); + let (lookahead_config_s, lookahead_config_r) = chan("lookahead_config"); + let (axi_data_s, axi_data_r) = chan("axi_data"); + let (preprocessed_data_s, preprocessed_data_r) = chan("preprocessed_data"); + + // prescan internal memory + let (prescan_internal_ram_write_req_s, prescan_internal_ram_write_req_r) = chan("prescan_internal_ram_write_req"); + let (prescan_internal_ram_write_resp_s, prescan_internal_ram_write_resp_r) = chan("prescan_internal_ram_write_resp"); + let (prescan_internal_ram_read_req_s, prescan_internal_ram_read_req_r) = chan("prescan_internal_ram_read_req"); + let (prescan_internal_ram_read_resp_s, prescan_internal_ram_read_resp_r) = chan("prescan_internal_ram_read_resp"); + + spawn ram::RamModel<{prescan::WeightPreScanMetaDataSize()}, prescan::RAM_SIZE, {prescan::WeightPreScanMetaDataSize()}>( + prescan_internal_ram_read_req_r, prescan_internal_ram_read_resp_s, + prescan_internal_ram_write_req_r, prescan_internal_ram_write_resp_s, + ); + + spawn ctrl::HuffmanControlAndSequence( + ctrl_r, + prescan_start_s, + code_builder_start_s, + axi_reader_ctrl_s, + data_preprocess_start_s, + decoder_start_s, + decoder_done_r, + ); + + spawn prescan::WeightPreScan( + prescan_start_r, + ram_read_req_s, + ram_read_resp_r, + prescan_response_s, + prescan_internal_ram_read_req_s, + prescan_internal_ram_read_resp_r, + prescan_internal_ram_write_req_s, + prescan_internal_ram_write_resp_r, + ); + + spawn code_builder::WeightCodeBuilder( + code_builder_start_r, + prescan_response_r, + code_builder_codes_s, + lookahead_config_s, + weights_pow_sum_loopback_s, + weights_pow_sum_loopback_r, + ); + + spawn axi_reader::HuffmanAxiReader( + axi_reader_ctrl_r, + axi_r_r, + axi_ar_s, + axi_data_s, + ); + + spawn data_preprocessor::HuffmanDataPreprocessor( + data_preprocess_start_r, + lookahead_config_r, + axi_data_r, + preprocessed_data_s, + ); + + spawn decoder::HuffmanDecoder( + decoder_start_r, + code_builder_codes_r, + preprocessed_data_r, + decoder_done_s, + decoded_literals_s, + ); + + () + } + + init { } + + next (state: ()) { } +} + +const INST_AXI_DATA_W = u32:32; +const INST_AXI_ADDR_W = u32:32; +const INST_AXI_ID_W = u32:32; + +const INST_RAM_ADDR_WIDTH = prescan::RAM_ADDR_WIDTH; +const INST_RAM_ACCESS_WIDTH = prescan::RAM_ACCESS_WIDTH; + +proc HuffmanLiteralsDecoderInst { + type Ctrl = ctrl::HuffmanControlAndSequenceCtrl; + type AxiR = axi::AxiR; + type AxiAr = axi::AxiAr; + + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + + config ( + ctrl_r: chan in, + decoded_literals_s: chan out, + axi_ar_s: chan out, + axi_r_r: chan in, + ram_read_req_s: chan out, + ram_read_resp_r: chan in, + weights_pow_sum_loopback_s: chan out, + weights_pow_sum_loopback_r: chan in, + ) { + spawn HuffmanLiteralsDecoder( + ctrl_r, + decoded_literals_s, + axi_ar_s, + axi_r_r, + ram_read_req_s, + ram_read_resp_r, + weights_pow_sum_loopback_s, + weights_pow_sum_loopback_r, + ); + } + + init { } + + next (state: ()) { } +} + +const TEST_AXI_DATA_W = u32:32; +const TEST_AXI_ADDR_W = u32:32; +const TEST_AXI_ID_W = u32:32; + +const TEST_RAM_ADDR_WIDTH = prescan::RAM_ADDR_WIDTH; +const TEST_RAM_ACCESS_WIDTH = prescan::RAM_ACCESS_WIDTH; + +type TestCtrl = ctrl::HuffmanControlAndSequenceCtrl; +type TestAxiR = axi::AxiR; +type TestAxiAr = axi::AxiAr; + +type TestReadReq = ram::ReadReq; +type TestReadResp = ram::ReadResp; + +type TestRamEntry = uN[TEST_RAM_ACCESS_WIDTH]; + +// data for test case #0 +const TEST_CTRL_0 = TestCtrl { + base_addr: uN[TEST_AXI_ADDR_W]:0x0, + len: uN[TEST_AXI_ADDR_W]:0x8, + new_config: true +}; + +const TEST_DATA_LEN_0 = u32:64; +const TEST_DATA_0 = ( + u8:0b1_001_010_1 ++ + u8:0b01_010_1_01 ++ + u8:0b0100_001_0 ++ + u8:0b11_010_1_00 ++ + u8:0b001_010_1_0 ++ + u8:0b01_010_000 ++ + u8:0b11_1_1_0001 ++ + u8:0b001_1_010_0 +); + +// code symbol length weight +// 0b1 0x47 1 9 +// 0b001 0x41 3 7 +// 0b010 0x8A 3 7 +// 0b011 0xD2 3 7 +// 0b000001 0x45 6 4 +// 0b000010 0x7A 6 4 +// 0b000011 0x89 6 4 +// 0b000100 0x8D 6 4 +// 0b000101 0xD1 6 4 +// 0b000110 0xD3 6 4 +// 0b000111 0xDA 6 4 +// 0b000000000 0x12 9 1 +// 0b000000001 0x8F 9 1 +// 0b000000010 0xAC 9 1 +// 0b000000011 0xD4 9 1 +// 0b000000100 0xD7 9 1 +// 0b000000101 0xDB 9 1 +// 0b000000110 0xDE 9 1 +// 0b000000111 0xFE 9 1 + +const TEST_WEIGHT_MEMORY_0 = TestRamEntry[32]:[ + // x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x0x + TestRamEntry:0x_0__0__1__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x1x + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x2x + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x3x + TestRamEntry:0x_0__7__0__0__0__4__0__9, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x4x + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x5x + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x6x + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__4__0__0__0__0__0, // 0x7x + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__4__7__0__0__4__0__1, // 0x8x + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x9x + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__1__0__0__0, // 0xAx + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xBx + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xCx + TestRamEntry:0x_0__4__7__4__1__0__0__1, TestRamEntry:0x_0__0__4__1__0__0__1__0, // 0xDx + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xEx + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__1__0, // 0xFx +]; + +const TEST_DECODED_LITERALS_0 = common::LiteralsData[3]:[ + common::LiteralsData { + data: common::LitData:0x458A_D147_47D2_8A47, + length: common::LitLength:8, + last: false, + }, + common::LiteralsData { + data: common::LitData:0x4141_8D47_8AD2_478A, + length: common::LitLength:8, + last: false, + }, + common::LiteralsData { + data: common::LitData:0x478A_41D2_478A, + length: common::LitLength:6, + last: true, + }, +]; + +// data for test case #1 (same config) +const TEST_CTRL_1 = TestCtrl { + base_addr: uN[TEST_AXI_ADDR_W]:0x20, + len: uN[TEST_AXI_ADDR_W]:0x4, + new_config: false +}; + +const TEST_DATA_LEN_1 = u32:32; +const TEST_DATA_1 = ( + u8:0b0010_1_010 ++ + u8:0b000_0_000 ++ + u8:0b1_1_000000 ++ + u8:0b001_011_1_1 +); + +const TEST_DECODED_LITERALS_1 = common::LiteralsData[2]:[ + common::LiteralsData { + data: common::LitData:0x47AC_1247_4747_47D2, + length: common::LitLength:8, + last: false, + }, + common::LiteralsData { + data: common::LitData:0x8A, + length: common::LitLength:1, + last: true, + }, +]; + +// Data for test case #2 +// Source: Example from RFC 8878, 4.2.2. Huffman-Coded Streams +// https://datatracker.ietf.org/doc/html/rfc8878#huffman_coded_streams +// Weights taken from Table 25 +// Bitstream fixed to encode literal sequence "0145" +// See https://www.rfc-editor.org/errata/eid8195 + +const TEST_CTRL_2 = TestCtrl { + base_addr: uN[TEST_AXI_ADDR_W]:0x0, + len: uN[TEST_AXI_ADDR_W]:0x2, + new_config: true +}; + +const TEST_DATA_LEN_2 = u32:16; +const TEST_DATA_2 = u64:0b00000001_00001101; + +// code symbol length weight +// N/A 0x03 0 0 +// 0b0000 0x04 4 1 +// 0b0001 0x05 4 1 +// 0b001 0x02 3 2 +// 0b01 0x01 2 3 +// 0b1 0x00 1 4 + +const TEST_WEIGHT_MEMORY_2 = TestRamEntry[32]:[ + // x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF + TestRamEntry:0x_4__3__2__0__1__1__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x0x + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x1x + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x2x + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x3x + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x4x + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x5x + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x6x + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x7x + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x8x + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x9x + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xAx + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xBx + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xCx + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xDx + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xEx + TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xFx +]; + +const TEST_DECODED_LITERALS_2 = common::LiteralsData[1]:[ + common::LiteralsData { + data: common::LitData:0x0504_0100, + length: common::LitLength:4, + last: true, + }, +]; +#[test_proc] +proc HuffmanLiteralsDecoder_test { + terminator: chan out; + + ctrl_s: chan out; + decoded_literals_r: chan in; + axi_ar_r: chan in; + axi_r_s: chan out; + ram_read_req_r: chan in; + ram_read_resp_s: chan out; + + config (terminator: chan out) { + let (ctrl_s, ctrl_r) = chan("ctrl"); + let (decoded_literals_s, decoded_literals_r) = chan("decoded_literals"); + let (axi_ar_s, axi_ar_r) = chan("axi_ar"); + let (axi_r_s, axi_r_r) = chan("axi_r"); + let (ram_read_req_s, ram_read_req_r) = chan("ram_read_req"); + let (ram_read_resp_s, ram_read_resp_r) = chan("ram_read_resp"); + let (weights_pow_sum_loopback_s, weights_pow_sum_loopback_r) = chan("weights_pow_sum_loopback"); + + spawn HuffmanLiteralsDecoder( + ctrl_r, decoded_literals_s, + axi_ar_s, axi_r_r, + ram_read_req_s, ram_read_resp_r, + weights_pow_sum_loopback_s, weights_pow_sum_loopback_r, + ); + + ( + terminator, + ctrl_s, decoded_literals_r, + axi_ar_r, axi_r_s, + ram_read_req_r, ram_read_resp_s, + ) + } + + init { } + + next (state: ()) { + let tok = join(); + + trace_fmt!("Test Case #1"); + // send ctrl + let tok = send(tok, ctrl_s, TEST_CTRL_0); + trace_fmt!("Sent #1 ctrl {:#x}", TEST_CTRL_0); + + // receive RAM read requests and send responses + trace_fmt!("Sending weight memory content"); + let tok = for (_, tok): (u32, token) in range(u32:0, u32:2) { + for (i, tok):(u32, token) in range(u32:0, array_size(TEST_WEIGHT_MEMORY_0)) { + let (tok, ram_read_req) = recv(tok, ram_read_req_r); + trace_fmt!("Received #{} ReadReq {:#x}", i + u32:1, ram_read_req); + + let read_resp = TestReadResp { + data: TEST_WEIGHT_MEMORY_0[ram_read_req.addr] as u32, + }; + + let tok = send(tok, ram_read_resp_s, read_resp); + trace_fmt!("Sent #{} ReadResp {:#x}", i + u32:1, read_resp); + + tok + }(tok) + }(tok); + + // receive Axi requests and send responses + trace_fmt!("Sending data from AXI"); + const AXI_READS_NUM = (TEST_DATA_LEN_0 + u32:7) / u32:8; + let tok = for (i, tok):(u32, token) in range(u32:0, AXI_READS_NUM) { + let expected_axi_ar = TestAxiAr { + addr: TEST_CTRL_0.base_addr + (AXI_READS_NUM - u32:1 - i) as uN[TEST_AXI_ADDR_W], + ..zero!() + }; + let (tok, axi_ar) = recv(tok, axi_ar_r); + trace_fmt!("Received #{} AxiAr {:#x}", i + u32:1, axi_ar); + assert_eq(expected_axi_ar, axi_ar); + + let axi_r = TestAxiR { + id: axi_ar.id, + data: (TEST_DATA_0 >> (u32:8 * i)) as u32, + resp: axi::AxiReadResp::OKAY, + last: i == (AXI_READS_NUM - u32:1), + }; + + let tok = send(tok, axi_r_s, axi_r); + trace_fmt!("Sent #{} AxiR {:#x}", i + u32:1, axi_r); + + tok + }(tok); + + // receive decoded literals + let tok = for ((i, test_decoded_literals), tok):((u32, common::LiteralsData), token) in enumerate(TEST_DECODED_LITERALS_0) { + let (tok, decoded_literals) = recv(tok, decoded_literals_r); + trace_fmt!("Received #{} decoded literals {:#x}", i + u32:1, decoded_literals); + assert_eq(test_decoded_literals, decoded_literals); + tok + }(tok); + + trace_fmt!("Test Case #2"); + // send ctrl + let tok = send(tok, ctrl_s, TEST_CTRL_1); + trace_fmt!("Sent #2 ctrl {:#x}", TEST_CTRL_1); + + // receive Axi requests and send responses + trace_fmt!("Sending data from AXI"); + const AXI_READS_NUM = (TEST_DATA_LEN_1 + u32:7) / u32:8; + let tok = for (i, tok):(u32, token) in range(u32:0, AXI_READS_NUM) { + let expected_axi_ar = TestAxiAr { + addr: TEST_CTRL_1.base_addr + (AXI_READS_NUM - u32:1 - i) as uN[TEST_AXI_ADDR_W], + ..zero!() + }; + let (tok, axi_ar) = recv(tok, axi_ar_r); + trace_fmt!("Received #{} AxiAr {:#x}", i + u32:1, axi_ar); + assert_eq(expected_axi_ar, axi_ar); + + let axi_r = TestAxiR { + id: axi_ar.id, + data: (TEST_DATA_1 >> (u32:8 * i)) as u32, + resp: axi::AxiReadResp::OKAY, + last: i == (AXI_READS_NUM - u32:1), + }; + + let tok = send(tok, axi_r_s, axi_r); + trace_fmt!("Sent #{} AxiR {:#x}", i + u32:1, axi_r); + + tok + }(tok); + + // receive decoded literals + let tok = for ((i, test_decoded_literals), tok):((u32, common::LiteralsData), token) in enumerate(TEST_DECODED_LITERALS_1) { + let (tok, decoded_literals) = recv(tok, decoded_literals_r); + trace_fmt!("Received #{} decoded literals {:#x}", i + u32:1, decoded_literals); + assert_eq(test_decoded_literals, decoded_literals); + tok + }(tok); + + trace_fmt!("Test Case #3"); + // send ctrl + let tok = send(tok, ctrl_s, TEST_CTRL_2); + trace_fmt!("Sent #3 ctrl {:#x}", TEST_CTRL_2); + + // receive RAM read requests and send responses + trace_fmt!("Sending weight memory content"); + let tok = for (_, tok): (u32, token) in range(u32:0, u32:2) { + for (i, tok):(u32, token) in range(u32:0, array_size(TEST_WEIGHT_MEMORY_2)) { + let (tok, ram_read_req) = recv(tok, ram_read_req_r); + trace_fmt!("Received #{} ReadReq {:#x}", i + u32:1, ram_read_req); + + let read_resp = TestReadResp { + data: TEST_WEIGHT_MEMORY_2[ram_read_req.addr] as u32, + }; + + let tok = send(tok, ram_read_resp_s, read_resp); + trace_fmt!("Sent #{} ReadResp {:#x}", i + u32:1, read_resp); + + tok + }(tok) + }(tok); + + // receive Axi requests and send responses + trace_fmt!("Sending data from AXI"); + const AXI_READS_NUM = (TEST_DATA_LEN_2 + u32:7) / u32:8; + let tok = for (i, tok):(u32, token) in range(u32:0, AXI_READS_NUM) { + let expected_axi_ar = TestAxiAr { + addr: TEST_CTRL_2.base_addr + (AXI_READS_NUM - u32:1 - i) as uN[TEST_AXI_ADDR_W], + ..zero!() + }; + let (tok, axi_ar) = recv(tok, axi_ar_r); + trace_fmt!("Received #{} AxiAr {:#x}", i + u32:1, axi_ar); + assert_eq(expected_axi_ar, axi_ar); + + let axi_r = TestAxiR { + id: axi_ar.id, + data: (TEST_DATA_2 >> (u32:8 * i)) as u32, + resp: axi::AxiReadResp::OKAY, + last: i == (AXI_READS_NUM - u32:1), + }; + + let tok = send(tok, axi_r_s, axi_r); + trace_fmt!("Sent #{} AxiR {:#x}", i + u32:1, axi_r); + + tok + }(tok); + + // receive decoded literals + let tok = for ((i, test_decoded_literals), tok):((u32, common::LiteralsData), token) in enumerate(TEST_DECODED_LITERALS_2) { + let (tok, decoded_literals) = recv(tok, decoded_literals_r); + trace_fmt!("Received #{} decoded literals {:#x}", i + u32:1, decoded_literals); + assert_eq(test_decoded_literals, decoded_literals); + tok + }(tok); + + send(tok, terminator, true); + } +} diff --git a/xls/modules/zstd/huffman_prescan.x b/xls/modules/zstd/huffman_prescan.x new file mode 100644 index 0000000000..4cc49c9cd5 --- /dev/null +++ b/xls/modules/zstd/huffman_prescan.x @@ -0,0 +1,431 @@ +// 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 Huffmann tree decoder. + +import std; +import xls.dslx.stdlib.acm_random as random; + +import xls.examples.ram; +import xls.modules.zstd.common as common; +import xls.modules.zstd.huffman_common as hcommon; + +// TODO: Enable once parametrics work +//fn WeightPreScanMetaDataSize(PARALLEL_ACCESS_WIDTH: u32) -> u32 { +// let COUNTER_WIDTH = {std::clog2(PARALLEL_ACCESS_WIDTH + u32:1)}; +// (COUNTER_WIDTH as u32) * (PARALLEL_ACCESS_WIDTH as u32) + +// (MAX_WEIGHT as u32) + u32:1 + +// (COUNTER_WIDTH as u32) * (MAX_WEIGHT as u32 + u32:1) +//} +// +//fn InternalStructToBits< +// PARALLEL_ACCESS_WIDTH: u32, +// BITS: u32 = {WeightPreScanMetaDataSize(PARALLEL_ACCESS_WIDTH)} +//> (internalStruct: WeightPreScanMetaData) -> bits[BITS] { +// internalStruct as bits[BITS] +//} +// +//fn BitsToInternalStruct< +// PARALLEL_ACCESS_WIDTH: u32, +// BITS: u32 = {WeightPreScanMetaDataSize(PARALLEL_ACCESS_WIDTH)} +//> (rawBits: bits[BITS]) -> WeightPreScanMetaData { +// rawBits as WeightPreScanMetaData +//} + +const MAX_WEIGHT = hcommon::MAX_WEIGHT; +const WEIGHT_LOG = hcommon::WEIGHT_LOG; +const MAX_SYMBOL_COUNT = hcommon::MAX_SYMBOL_COUNT; + +const PARALLEL_ACCESS_WIDTH = hcommon::PARALLEL_ACCESS_WIDTH; +const COUNTER_WIDTH = hcommon::COUNTER_WIDTH; + +type WeightPreScanMetaData = hcommon::WeightPreScanMetaData; +type WeightPreScanOutput = hcommon::WeightPreScanOutput; + +pub fn WeightPreScanMetaDataSize() -> u32 { + (COUNTER_WIDTH as u32) * (PARALLEL_ACCESS_WIDTH as u32) + + (MAX_WEIGHT as u32) + u32:1 + + (COUNTER_WIDTH as u32) * (MAX_WEIGHT as u32 + u32:1) +} + +fn InternalStructToBits< + BITS: u32 = {WeightPreScanMetaDataSize()}, + OCCURANCE_WIDTH: u32 ={COUNTER_WIDTH * PARALLEL_ACCESS_WIDTH}, +> (internalStruct: WeightPreScanMetaData) -> bits[BITS] { + (internalStruct.weights_count as bits[COUNTER_WIDTH * (MAX_WEIGHT + u32:1)] ++ + internalStruct.valid_weights as bits[MAX_WEIGHT + u32:1] ++ + internalStruct.occurance_number as bits[OCCURANCE_WIDTH]) as bits[BITS] +} + +fn BitsToInternalStruct< + BITS: u32 = {WeightPreScanMetaDataSize()}, + OCCURANCE_WIDTH: u32 ={COUNTER_WIDTH * PARALLEL_ACCESS_WIDTH}, +> (rawBits: bits[BITS]) -> WeightPreScanMetaData { + WeightPreScanMetaData { + occurance_number: rawBits[0:OCCURANCE_WIDTH as s32] as uN[COUNTER_WIDTH][PARALLEL_ACCESS_WIDTH], + valid_weights: rawBits[OCCURANCE_WIDTH as s32:(OCCURANCE_WIDTH + MAX_WEIGHT + u32:1) as s32] as u1[MAX_WEIGHT + u32:1], + weights_count: rawBits[(OCCURANCE_WIDTH + MAX_WEIGHT + u32:1) as s32:BITS as s32] as uN[COUNTER_WIDTH][MAX_WEIGHT + u32:1] + } +} + +#[quickcheck(test_count=50000)] +fn bits_to_struct_to_bits_qtest(x: bits[WeightPreScanMetaDataSize()]) -> bool { + x == InternalStructToBits(BitsToInternalStruct(x)) +} + +#[quickcheck(test_count=50000)] +fn struct_to_bots_to_struct_qtest(x: WeightPreScanMetaData) -> bool { + x == BitsToInternalStruct(InternalStructToBits(x)) +} + +pub const RAM_SIZE = MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH * u32:8 / WEIGHT_LOG; +pub const RAM_ADDR_WIDTH = {std::clog2(RAM_SIZE)}; +pub const RAM_ACCESS_WIDTH = PARALLEL_ACCESS_WIDTH * WEIGHT_LOG; +const MAX_RAM_ADDR = MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH; + +enum WeightPreScanFSM: u2 { + IDLE = u2:0, + FIRST_RUN = u2:1, + SECOND_RUN = u2:2, +} + +struct WeightPreScanState { + fsm: WeightPreScanFSM, + addr: u9, + internal_addr: u9, +} + +pub proc WeightPreScan +// TODO: enable parametric expresion when they start working +//proc WeightPreScan< +// PARALLEL_ACCESS_WIDTH: u32 = {u32:8}, +// RAM_SIZE: u32 = {MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH}, +// RAM_ADDR_WIDTH: u32 = {std::clog2(RAM_SIZE)}, +// RAM_ACCESS_WIDTH: u32 = {PARALLEL_ACCESS_WIDTH * WEIGHT_LOG}, +// MAX_RAM_ADDR: u32 = {(u32:1< { +{ + type State = WeightPreScanState; + type FSM = WeightPreScanFSM; + + type ExternalRamAddr = uN[RAM_ADDR_WIDTH]; + type ExternalRamData = uN[RAM_ACCESS_WIDTH]; + + type OutData = WeightPreScanOutput; + + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + + type InternalRamAddr = uN[RAM_ADDR_WIDTH]; + type InternalData = WeightPreScanMetaData; + type InternalRamData = bits[WeightPreScanMetaDataSize()]; + + type InternalReadReq = ram::ReadReq; + type InternalReadResp = ram::ReadResp<{WeightPreScanMetaDataSize()}>; + type InternalWriteReq = ram::WriteReq; + type InternalWriteResp = ram::WriteResp; + + start_r: chan in; + read_req_s: chan out; + read_rsp_r: chan in; + weight_s: chan out; + + internal_read_req_s: chan out; + internal_read_rsp_r: chan in; + internal_write_req_s: chan out; + internal_write_rsp_r: chan in; + + internal_memory_written_s: chan out; + internal_memory_written_r: chan in; + + config ( + start_r: chan in, + read_req_s: chan out, + read_rsp_r: chan in, + weight_s: chan out, + internal_read_req_s: chan out, + internal_read_rsp_r: chan in, + internal_write_req_s: chan out, + internal_write_rsp_r: chan in + ) { + let (internal_memory_written_s, internal_memory_written_r) = + chan("internal_loopback"); + (start_r, read_req_s, read_rsp_r, weight_s, + internal_read_req_s, internal_read_rsp_r, + internal_write_req_s, internal_write_rsp_r, + internal_memory_written_s, internal_memory_written_r) + } + + init {zero!()} + + next(state: State) { + let tok = join(); + let (recv_start, send_addr, write_internal, read_internal, addr) = match state.fsm { + FSM::IDLE => (true, false, false, false, u32:0 as ExternalRamAddr), + FSM::FIRST_RUN => (false, true, true, false, state.addr as ExternalRamAddr), + FSM::SECOND_RUN => { + let valid_data = state.addr < state.internal_addr || state.internal_addr as u32 == MAX_RAM_ADDR - u32:1; + (false, valid_data, false, valid_data, state.addr as ExternalRamAddr) + }, + _ => { + assert!(false, "Invalid state"); + (false, false, false, false, u9:0 as ExternalRamAddr) + } + }; + let (tok1, start) = recv_if(tok, start_r, recv_start, false); + + let (tok2, internal_addr, _) = recv_non_blocking(tok, internal_memory_written_r, state.internal_addr as InternalRamAddr); + let next_state = match (state.fsm, start, send_addr, state.addr as u32 == MAX_RAM_ADDR - u32:1) { + (FSM::IDLE, true, _, _) => State { + fsm: FSM::FIRST_RUN, + addr: u9:0, + internal_addr: u9:0 + }, + (FSM::FIRST_RUN, _, false, _) => State { + fsm: FSM::FIRST_RUN, + addr: state.addr, + internal_addr: internal_addr as u9 + }, + (FSM::FIRST_RUN, _, true, false) => State { + fsm: FSM::FIRST_RUN, + addr: state.addr + u9:1, + internal_addr: internal_addr as u9 + }, + (FSM::FIRST_RUN, _, true, true) => State { + fsm: FSM::SECOND_RUN, + addr: u9:0, + internal_addr: internal_addr as u9 + }, + (FSM::SECOND_RUN, _, false, _) => State { + fsm: FSM::SECOND_RUN, + addr: state.addr, + internal_addr: internal_addr as u9 + }, + (FSM::SECOND_RUN, _, true, false) => State { + fsm: FSM::SECOND_RUN, + addr: state.addr + u9:1, + internal_addr: internal_addr as u9 + }, + (FSM::SECOND_RUN, _, true, true) => State { + fsm: FSM::IDLE, + addr: u9:0, + internal_addr: internal_addr as u9 + }, + _ => { + assert!(false, "Invalid state"); + State { + fsm: FSM::IDLE, + addr: u9:0, + internal_addr: u9:0 + } + } + }; + + let external_ram_req = ReadReq { + addr: addr, + mask: u1:1, + }; + let tok3 = send_if(tok, read_req_s, send_addr, external_ram_req); + let (tok3, ram_data) = recv_if(tok3, read_rsp_r, send_addr, zero!()); + let ram_data = ram_data.data as uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH]; + + let internal_ram_r_req = InternalReadReq { + addr: addr, + mask: u1:1, + }; + let tok4 = send_if(tok, internal_read_req_s, read_internal, internal_ram_r_req); + let (tok4, meta_data_flat) = recv_if(tok4, internal_read_rsp_r, read_internal, zero!()); + let meta_data = BitsToInternalStruct(meta_data_flat.data); + let tok34 = join(tok3, tok4); + + let prescan_output = OutData { + weights: ram_data, + meta_data: meta_data + }; + let tok34 = send_if(tok34, weight_s, send_addr, prescan_output); + + let occurance_matrix = for (i, occurance_matrix) in range(u32:0, PARALLEL_ACCESS_WIDTH) { + let row = for (j, row) in range(u32:0, MAX_WEIGHT + u32:1) { + if (ram_data[i] == j as uN[COUNTER_WIDTH]) { + update(row, j, row[j] + uN[COUNTER_WIDTH]:1) + } else { row } + } (occurance_matrix[i]); + update(occurance_matrix, i + u32:1, row) + }(zero!()); + + let valid_weights = for(i, valid_weights) in range(u32:0, MAX_WEIGHT + u32:1) { + if (occurance_matrix[PARALLEL_ACCESS_WIDTH][i] != uN[COUNTER_WIDTH]:0) { + update(valid_weights, i, true) + } else { valid_weights } + }(zero!()); + + let occurance_number = for (i, occurance_number) in range(u32:0, PARALLEL_ACCESS_WIDTH) { + let number = occurance_matrix[i][ram_data[i]]; + update(occurance_number, i, number) + }(zero!()); + let _meta_data = WeightPreScanMetaData { + occurance_number: occurance_number, + valid_weights: valid_weights, + weights_count: occurance_matrix[PARALLEL_ACCESS_WIDTH], + }; + + let internal_ram_w_req = InternalWriteReq { + addr: addr, + data: InternalStructToBits(_meta_data), + mask: u1:1 + }; + let tok5 = send_if(tok, internal_write_req_s, write_internal, internal_ram_w_req); + let (tok5, _) = recv_if(tok5, internal_write_rsp_r, write_internal, zero!()); + send_if(tok5, internal_memory_written_s, state.fsm == FSM::FIRST_RUN, addr as InternalRamAddr); + next_state + } +} + +#[test_proc] +proc Prescan_test{ + type external_ram_addr = uN[RAM_ADDR_WIDTH]; + type external_ram_data = uN[RAM_ACCESS_WIDTH]; + + type PrescanOut = WeightPreScanOutput; + + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp<>; + + type InternalReadReq = ram::ReadReq; + type InternalReadResp = ram::ReadResp<{WeightPreScanMetaDataSize()}>; + type InternalWriteReq = ram::WriteReq; + type InternalWriteResp = ram::WriteResp<>; + + terminator: chan out; + external_ram_req: chan out; + external_ram_resp: chan in; + start_prescan: chan out; + prescan_response: chan in; + init{()} + config (terminator: chan out) { + // Emulate external memory + let (RAMExternalWriteReq_s, RAMExternalWriteReq_r) = chan("Write_channel_req"); + let (RAMExternalWriteResp_s, RAMExternalWriteResp_r) = chan("Write_channel_resp"); + let (RAMExternalReadReq_s, RAMExternalReadReq_r) = chan("Read_channel_req"); + let (RAMExternalReadResp_s, RAMExternalReadResp_r) = chan("Read_channel_resp"); + spawn ram::RamModel( + RAMExternalReadReq_r, RAMExternalReadResp_s, RAMExternalWriteReq_r, RAMExternalWriteResp_s + ); + + // Emulate Internal prescan memory + let (RAMInternalWriteReq_s, RAMInternalWriteReq_r) = chan("Internal_write_channel_req"); + let (RAMInternalWriteResp_s, RAMInternalWriteResp_r) = chan("Internal_write_channel_resp"); + let (RAMInternalReadReq_s, RAMInternalReadReq_r) = chan("Internal_read_channel_req"); + let (RAMInternalReadResp_s, RAMInternalReadResp_r) = chan("Internal_read_channel_resp"); + spawn ram::RamModel<{WeightPreScanMetaDataSize()}, RAM_SIZE, {WeightPreScanMetaDataSize()}>( + RAMInternalReadReq_r, RAMInternalReadResp_s, RAMInternalWriteReq_r, RAMInternalWriteResp_s + ); + + let (PreScanStart_s, PreScanStart_r) = chan("Start_prescan"); + let (PreScanResponse_s, PreScanResponse_r) = chan("Start_prescan"); + spawn WeightPreScan( + PreScanStart_r, RAMExternalReadReq_s,RAMExternalReadResp_r, PreScanResponse_s, + RAMInternalReadReq_s, RAMInternalReadResp_r, RAMInternalWriteReq_s, RAMInternalWriteResp_r); + (terminator, RAMExternalWriteReq_s, RAMExternalWriteResp_r, PreScanStart_s, PreScanResponse_r) + } + next(state: ()) { + let tok = join(); + let rand_state = random::rng_new(random::rng_deterministic_seed()); + // Setup external memory with random values + for (i, rand_state) in range(u32:0, MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH) { + let (new_rand_state, data_to_send) = for (j, (rand_state, data_to_send)) in range(u32:0, PARALLEL_ACCESS_WIDTH) { + let (new_rand_state, data) = random::rng_next(rand_state); + let weight = (data - (data/u32:12) * u32:12) as u4; + let new_data_to_send = update(data_to_send as uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH], j, weight) as external_ram_data; + (new_rand_state, new_data_to_send) + }((rand_state, zero!())); + let external_w_req = WriteReq { + addr: i as uN[RAM_ADDR_WIDTH], + data: data_to_send, + mask: u1:1 + }; + send(tok, external_ram_req, external_w_req); + recv(tok, external_ram_resp); + new_rand_state + }(rand_state); + send(tok, start_prescan, true); + // First run + for (_, rand_state) in range(u32:0, MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH) { + // Generate expected output + let (new_rand_state, expected_data) = for (j, (rand_state, data_to_send)) in range(u32:0, PARALLEL_ACCESS_WIDTH) { + let (new_rand_state, data) = random::rng_next(rand_state); + let weight = (data - (data/u32:12) * u32:12) as u4; + let new_data_to_send = update(data_to_send as uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH], j, weight) as external_ram_data; + (new_rand_state, new_data_to_send) + }((rand_state, zero!())); + let (_, prescan_resp) = recv(tok, prescan_response); + let expected_data = PrescanOut { + weights: expected_data as uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH], + meta_data: zero!() + }; + assert_eq(prescan_resp, expected_data); + new_rand_state + }(rand_state); + + // Second run + for (_, rand_state) in range(u32:0, MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH) { + // Generate expected output + let (new_rand_state, expected_data) = for (j, (rand_state, data_to_send)) in range(u32:0, PARALLEL_ACCESS_WIDTH) { + let (new_rand_state, data) = random::rng_next(rand_state); + let weight = (data - (data/u32:12) * u32:12) as u4; + let new_data_to_send = update(data_to_send as uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH], j, weight) as external_ram_data; + (new_rand_state, new_data_to_send) + }((rand_state, zero!())); + let expected_data = expected_data as uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH]; + let valid_weights = for (i, seen_weights) in range(u32:0, PARALLEL_ACCESS_WIDTH) { + update(seen_weights, expected_data[i], true) + }(zero!()); + let occurance_number = for (i, occurance_number) in range(u32:0, PARALLEL_ACCESS_WIDTH) { + let number = for (j, number) in range(u32:0, PARALLEL_ACCESS_WIDTH){ + if (j < i && expected_data[j] == expected_data[i]) { + number + u4:1 + } else { + number + } + }(zero!()); + update(occurance_number, i, number) + }(zero!()); + let weights_count = for (i, weights_count) in range(u32:0, MAX_WEIGHT + u32:1) { + let count = for (j, count) in range(u32:0, PARALLEL_ACCESS_WIDTH) { + if (expected_data[j] == i as uN[COUNTER_WIDTH]) { + count + uN[COUNTER_WIDTH]:1 + } else { + count + } + }(zero!()); + update(weights_count, i, count) + }(zero!()); + let (_, prescan_resp) = recv(tok, prescan_response); + let expected_data = PrescanOut { + weights: expected_data, + meta_data: WeightPreScanMetaData { + occurance_number: occurance_number, + valid_weights: valid_weights, + weights_count: weights_count, + } + }; + assert_eq(prescan_resp, expected_data); + new_rand_state + }(rand_state); + + send(tok, terminator, true); + } +} From 78eee27d8159c0aa19280c780827c0f70b61ac6d Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Thu, 12 Dec 2024 17:14:20 +0100 Subject: [PATCH 42/85] modules/zstd: Add triple output RamDemux Add 3:1 demultiplexer for RamModel Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 87 +++++++++ xls/modules/zstd/ram_demux3.x | 337 ++++++++++++++++++++++++++++++++++ 2 files changed, 424 insertions(+) create mode 100644 xls/modules/zstd/ram_demux3.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 19c2595e26..67027a48ac 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1809,6 +1809,93 @@ place_and_route( target_die_utilization_percentage = "10", ) +xls_dslx_library( + name = "ram_demux3_dslx", + srcs = ["ram_demux3.x"], + deps = [":ram_demux_dslx"], +) + +xls_dslx_test( + name = "ram_demux3_dslx_test", + library = ":ram_demux3_dslx", +) + +xls_dslx_verilog( + name = "ram_demux3_verilog", + codegen_args = { + "module_name": "RamDemux3", + "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 = "ram_demux3__rd_req{}_s".format(num), + rd_resp = "ram_demux3__rd_resp{}_r".format(num), + wr_req = "ram_demux3__wr_req{}_s".format(num), + wr_resp = "ram_demux3__wr_resp{}_r".format(num), + ) + for num in range(3) + ]), + "pipeline_stages": "6", + "reset": "rst", + "use_system_verilog": "false", + "multi_proc": "true", + }, + dslx_top = "RamDemux3Inst", + library = ":ram_demux3_dslx", + verilog_file = "ram_demux3.v", +) + +xls_benchmark_ir( + name = "ram_demux3_opt_ir_benchmark", + src = ":ram_demux3_verilog.opt.ir", + benchmark_ir_args = { + "delay_model": "asap7", + "pipeline_stages": "3", + "inline_procs": "false" + }, + tags = ["manual"], +) + +verilog_library( + name = "ram_demux3_verilog_lib", + srcs = [ + ":ram_demux3.v", + ":xls_fifo_wrapper.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "ram_demux3_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "RamDemux3", + deps = [ + ":ram_demux3_verilog_lib", + ], +) + +benchmark_synth( + name = "ram_demux3_benchmark_synth", + synth_target = ":ram_demux3_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "ram_demux3_place_and_route", + clock_period = "750", + core_padding_microns = 2, + die_height_microns = 100, + die_width_microns = 100, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":ram_demux3_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) xls_dslx_library( name = "literals_dispatcher_dslx", srcs = [ diff --git a/xls/modules/zstd/ram_demux3.x b/xls/modules/zstd/ram_demux3.x new file mode 100644 index 0000000000..1548c4aa03 --- /dev/null +++ b/xls/modules/zstd/ram_demux3.x @@ -0,0 +1,337 @@ +// 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 a RamDemux implementation that can be used to connect +// a single proc with two RAM instances, by using a single RAM interface and +// switching between the RAMs, when requested. The switching occurs only after +// each request has received the corresponding response. +// Additionally, a "naive" implementation is provided that does not ensure +// any synchronization when switching RAMs. + +import std; +import xls.examples.ram; +import xls.modules.zstd.ram_demux; + +pub proc RamDemux3< + ADDR_WIDTH: u32, + DATA_WIDTH: u32, + NUM_PARTITIONS: u32, + INIT_SEL: u2 = {u2:0}, + QUEUE_LEN: u32 = {u32:5}, + D1_INIT_SEL: u1 = {INIT_SEL == u2:1 || INIT_SEL == u2:2}, + D2_INIT_SEL: u1 = {INIT_SEL == u2:2 || INIT_SEL == u2:3}, +> { + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp; + + sel_req_r: chan in; + sel_resp_s: chan<()> out; + d1_sel_req_s: chan out; + d1_sel_resp_r: chan<()> in; + d2_sel_req_s: chan out; + d2_sel_resp_r: chan<()> in; + + config( + sel_req_r: chan in, + sel_resp_s: chan<()> out, + + rd_req_r: chan in, + rd_resp_s: chan out, + wr_req_r: chan in, + wr_resp_s: chan out, + + rd_req0_s: chan out, + rd_resp0_r: chan in, + wr_req0_s: chan out, + wr_resp0_r: chan in, + + rd_req1_s: chan out, + rd_resp1_r: chan in, + wr_req1_s: chan out, + wr_resp1_r: chan in, + + rd_req2_s: chan out, + rd_resp2_r: chan in, + wr_req2_s: chan out, + wr_resp2_r: chan in + + ) { + const CHANNEL_DEPTH = u32:1; + + let (d1_sel_req_s, d1_sel_req_r) = chan("d1_sel_req"); + let (d1_sel_resp_s, d1_sel_resp_r) = chan<(), CHANNEL_DEPTH>("d1_sel_resp"); + + let (tmp_rd_req_s, tmp_rd_req_r) = chan("tmp_rd_req"); + let (tmp_rd_resp_s, tmp_rd_resp_r) = chan("tmp_rd_resp"); + let (tmp_wr_req_s, tmp_wr_req_r) = chan("tmp_wr_req"); + let (tmp_wr_resp_s, tmp_wr_resp_r) = chan("tmp_wr_resp"); + + spawn ram_demux::RamDemux( + d1_sel_req_r, d1_sel_resp_s, + rd_req_r, rd_resp_s, wr_req_r, wr_resp_s, + rd_req0_s, rd_resp0_r, wr_req0_s, wr_resp0_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + ); + + let (d2_sel_req_s, d2_sel_req_r) = chan("d2_sel_req"); + let (d2_sel_resp_s, d2_sel_resp_r) = chan<(), CHANNEL_DEPTH>("d2_sel_resp"); + + spawn ram_demux::RamDemux( + d2_sel_req_r, d2_sel_resp_s, + tmp_rd_req_r, tmp_rd_resp_s, tmp_wr_req_r, tmp_wr_resp_s, + rd_req1_s, rd_resp1_r, wr_req1_s, wr_resp1_r, + rd_req2_s, rd_resp2_r, wr_req2_s, wr_resp2_r + ); + + ( + sel_req_r, sel_resp_s, + d1_sel_req_s, d1_sel_resp_r, + d2_sel_req_s, d2_sel_resp_r, + ) + } + + init { } + + next(state: ()) { + let tok = join(); + let (tok, sel) = recv(tok, sel_req_r); + + let (sel1, sel2) = match sel { + u2:0 => (u1:0, u1:0), + u2:1 => (u1:1, u1:0), + u2:2 => (u1:1, u1:1), + _ => (u1:0, u1:1), + }; + + let tok1_0 = send(tok, d1_sel_req_s, sel1); + let (tok2_0, ()) = recv(tok1_0, d1_sel_resp_r); + + let tok1_1 = send(tok, d2_sel_req_s, sel2); + let (tok2_1, ()) = recv(tok, d2_sel_resp_r); + + let tok2 = join(tok2_0, tok2_1); + send(tok2, sel_resp_s, ()); + } +} + +const RAM_SIZE = u32:32; +const RAM_DATA_WIDTH = u32:8; +const RAM_ADDR_WIDTH = std::clog2(RAM_SIZE); +const RAM_WORD_PARTITION_SIZE = u32:1; +const RAM_NUM_PARTITIONS = ram::num_partitions(RAM_WORD_PARTITION_SIZE, RAM_DATA_WIDTH); + +pub proc RamDemux3Inst { + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp; + + config( + sel_req_r: chan in, + sel_resp_s: chan<()> out, + + rd_req_r: chan in, + rd_resp_s: chan out, + wr_req_r: chan in, + wr_resp_s: chan out, + + rd_req0_s: chan out, + rd_resp0_r: chan in, + wr_req0_s: chan out, + wr_resp0_r: chan in, + + rd_req1_s: chan out, + rd_resp1_r: chan in, + wr_req1_s: chan out, + wr_resp1_r: chan in, + + rd_req2_s: chan out, + rd_resp2_r: chan in, + wr_req2_s: chan out, + wr_resp2_r: chan in + + ) { + spawn RamDemux3( + sel_req_r, sel_resp_s, + rd_req_r, rd_resp_s, wr_req_r, wr_resp_s, + rd_req0_s, rd_resp0_r, wr_req0_s, wr_resp0_r, + rd_req1_s, rd_resp1_r, wr_req1_s, wr_resp1_r, + rd_req2_s, rd_resp2_r, wr_req2_s, wr_resp2_r + ); + } + + init { } + + next(state: ()) { } +} + +const TEST_RAM_SIZE = u32:32; +const TEST_RAM_DATA_WIDTH = u32:8; +const TEST_RAM_ADDR_WIDTH = std::clog2(TEST_RAM_SIZE); +const TEST_RAM_WORD_PARTITION_SIZE = u32:1; +const TEST_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_RAM_WORD_PARTITION_SIZE, TEST_RAM_DATA_WIDTH); +const TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_RAM_INITIALIZED = true; +const TEST_DEMUX_INIT_SEL = u2:0; +const TEST_DEMUX_QUEUE_LEN = u32:5; + +#[test_proc] +proc RamDemux3Test { + + type WriteReq = ram::WriteReq; + type ReadResp = ram::ReadResp; + type ReadReq = ram::ReadReq; + type WriteResp = ram::WriteResp; + + type Addr = uN[TEST_RAM_ADDR_WIDTH]; + type Data = uN[TEST_RAM_DATA_WIDTH]; + type Mask = uN[TEST_RAM_NUM_PARTITIONS]; + + terminator: chan out; + + sel_req_s: chan out; + sel_resp_r: chan<()> in; + + rd_req_s: chan out; + rd_resp_r: chan in; + wr_req_s: chan out; + wr_resp_r: chan in; + + rd_req0_s: chan out; + rd_resp0_r: chan in; + wr_req0_s: chan out; + wr_resp0_r: chan in; + + rd_req1_s: chan out; + rd_resp1_r: chan in; + wr_req1_s: chan out; + wr_resp1_r: chan in; + + rd_req2_s: chan out; + rd_resp2_r: chan in; + wr_req2_s: chan out; + wr_resp2_r: chan in; + + config(terminator: chan out) { + let (sel_req_s, sel_req_r) = chan("sel_req"); + let (sel_resp_s, sel_resp_r) = chan<()>("sel_resp"); + + let (rd_req_s, rd_req_r) = chan("rd_req"); + let (rd_resp_s, rd_resp_r) = chan("rd_resp"); + let (wr_req_s, wr_req_r) = chan("wr_req"); + let (wr_resp_s, wr_resp_r) = chan("wr_resp"); + + let (rd_req0_s, rd_req0_r) = chan("rd_req0"); + let (rd_resp0_s, rd_resp0_r) = chan("rd_resp0"); + let (wr_req0_s, wr_req0_r) = chan("wr_req0"); + let (wr_resp0_s, wr_resp0_r) = chan("wr_resp0"); + + let (rd_req1_s, rd_req1_r) = chan("rd_req1"); + let (rd_resp1_s, rd_resp1_r) = chan("rd_resp1"); + let (wr_req1_s, wr_req1_r) = chan("wr_req1"); + let (wr_resp1_s, wr_resp1_r) = chan("wr_resp1"); + + let (rd_req2_s, rd_req2_r) = chan("rd_req2"); + let (rd_resp2_s, rd_resp2_r) = chan("rd_resp2"); + let (wr_req2_s, wr_req2_r) = chan("wr_req2"); + let (wr_resp2_s, wr_resp2_r) = chan("wr_resp2"); + + spawn RamDemux3< + TEST_RAM_ADDR_WIDTH, TEST_RAM_DATA_WIDTH, TEST_RAM_NUM_PARTITIONS, + TEST_DEMUX_INIT_SEL, TEST_DEMUX_QUEUE_LEN + >( + sel_req_r, sel_resp_s, + rd_req_r, rd_resp_s, wr_req_r, wr_resp_s, + rd_req0_s, rd_resp0_r, wr_req0_s, wr_resp0_r, + rd_req1_s, rd_resp1_r, wr_req1_s, wr_resp1_r, + rd_req2_s, rd_resp2_r, wr_req2_s, wr_resp2_r, + ); + + spawn ram::RamModel< + TEST_RAM_DATA_WIDTH, TEST_RAM_SIZE, TEST_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED + >(rd_req0_r, rd_resp0_s, wr_req0_r, wr_resp0_s); + + spawn ram::RamModel< + TEST_RAM_DATA_WIDTH, TEST_RAM_SIZE, TEST_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED + >(rd_req1_r, rd_resp1_s, wr_req1_r, wr_resp1_s); + + spawn ram::RamModel< + TEST_RAM_DATA_WIDTH, TEST_RAM_SIZE, TEST_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED + >(rd_req2_r, rd_resp2_s, wr_req2_r, wr_resp2_s); + + ( + terminator, sel_req_s, sel_resp_r, + rd_req_s, rd_resp_r, wr_req_s, wr_resp_r, + rd_req0_s, rd_resp0_r, wr_req0_s, wr_resp0_r, + rd_req1_s, rd_resp1_r, wr_req1_s, wr_resp1_r, + rd_req2_s, rd_resp2_r, wr_req2_s, wr_resp2_r, + ) + } + + init { } + + next(state: ()) { + let tok = join(); + + // Writes + + let tok = send(tok, sel_req_s, u2:0); + let (tok, _) = recv(tok, sel_resp_r); + + let tok = send(tok, wr_req_s, WriteReq { addr: Addr:0, data: Data:0xA, mask: !Mask:0 }); + let (tok, _) = recv(tok, wr_resp_r); + + let tok = send(tok, sel_req_s, u2:1); + let (tok, _) = recv(tok, sel_resp_r); + + let tok = send(tok, wr_req_s, WriteReq { addr: Addr:0, data: Data:0xB, mask: !Mask:0 }); + let (tok, _) = recv(tok, wr_resp_r); + + let tok = send(tok, sel_req_s, u2:2); + let (tok, _) = recv(tok, sel_resp_r); + + let tok = send(tok, wr_req_s, WriteReq { addr: Addr:0, data: Data:0xC, mask: !Mask:0 }); + let (tok, _) = recv(tok, wr_resp_r); + + // Reads + + let tok = send(tok, sel_req_s, u2:0); + let (tok, _) = recv(tok, sel_resp_r); + + let tok = send(tok, rd_req_s, ReadReq { addr: Addr:0, mask: !Mask:0 }); + let (tok, resp) = recv(tok, rd_resp_r); + trace_fmt!("Value read from the first RAM: {:#x}", resp); + + let tok = send(tok, sel_req_s, u2:1); + let (tok, _) = recv(tok, sel_resp_r); + + let tok = send(tok, rd_req_s, ReadReq { addr: Addr:0, mask: !Mask:0 }); + let (tok, resp) = recv(tok, rd_resp_r); + trace_fmt!("Value read from the second RAM: {:#x}", resp); + + let tok = send(tok, sel_req_s, u2:2); + let (tok, _) = recv(tok, sel_resp_r); + + let tok = send(tok, rd_req_s, ReadReq { addr: Addr:0, mask: !Mask:0 }); + let (tok, resp) = recv(tok, rd_resp_r); + trace_fmt!("Value read from the third RAM: {:#x}", resp); + + let tok = send(tok, terminator, true); + } +} From 9e76223b4668d71c185dcacca1463c21a0856690 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Thu, 12 Dec 2024 17:16:45 +0100 Subject: [PATCH 43/85] Add skeleton of SequenceDecoder Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 57 + xls/modules/zstd/fse_dec.x | 3 +- xls/modules/zstd/sequence_dec.x | 1945 +++++++++++++++++++++++++++++++ 3 files changed, 2004 insertions(+), 1 deletion(-) create mode 100644 xls/modules/zstd/sequence_dec.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 67027a48ac..bf69578959 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1896,6 +1896,63 @@ place_and_route( tags = ["manual"], target_die_utilization_percentage = "10", ) + +xls_dslx_library( + name = "sequence_dec_dslx", + srcs = [ + "sequence_dec.x" + ], + deps = [ + ":sequence_conf_dec_dslx", + ":fse_lookup_dec_dslx", + ":ram_demux3_dslx", + ":common_dslx", + ":fse_dec_dslx", + ":ram_mux_dslx", + ":fse_table_creator_dslx", + "//xls/modules/zstd/memory:mem_reader_dslx", + "//xls/modules/zstd/memory:axi_ram_dslx", + "//xls/modules/zstd:refilling_shift_buffer_dslx", + "//xls/modules/shift_buffer:shift_buffer_dslx", + ], +) + +xls_dslx_test( + name = "sequence_dec_dslx_test", + library = ":sequence_dec_dslx", +) + +xls_dslx_verilog( + name = "fse_lookup_ctrl_verilog", + codegen_args = { + "module_name": "FseLookupCtrl", + "generator": "pipeline", + "delay_model": "asap7", + "pipeline_stages": "6", + "reset": "rst", + "use_system_verilog": "false", + "multi_proc": "true", + }, + opt_ir_args = { + "top": "__sequence_dec__FseLookupCtrlInst__FseLookupCtrl_0__32_next" + }, + dslx_top = "FseLookupCtrlInst", + library = ":sequence_dec_dslx", + verilog_file = "fse_lookup_ctrl.v", +) + +xls_benchmark_ir( + name = "fse_lookup_ctrl_opt_ir_benchmark", + src = ":fse_lookup_ctrl_verilog.opt.ir", + benchmark_ir_args = { + "delay_model": "asap7", + "pipeline_stages": "6", + "inline_procs": "false", + "multi_proc": "true", + }, + tags = ["manual"], +) + xls_dslx_library( name = "literals_dispatcher_dslx", srcs = [ diff --git a/xls/modules/zstd/fse_dec.x b/xls/modules/zstd/fse_dec.x index 01a1416862..143cd45b7f 100644 --- a/xls/modules/zstd/fse_dec.x +++ b/xls/modules/zstd/fse_dec.x @@ -606,7 +606,8 @@ pub proc FseDecoder< ..state } } else { - fail!("too_many_literals", zero!()) + trace_fmt!("Fails state: {:#x}", state); + fail!("too_many_literals", state) } } else { FseDecoderState { diff --git a/xls/modules/zstd/sequence_dec.x b/xls/modules/zstd/sequence_dec.x new file mode 100644 index 0000000000..6f1823df49 --- /dev/null +++ b/xls/modules/zstd/sequence_dec.x @@ -0,0 +1,1945 @@ +// 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; +import xls.modules.zstd.common; +import xls.modules.zstd.memory.axi; +import xls.modules.zstd.memory.axi_ram; +import xls.modules.zstd.memory.mem_reader; +import xls.modules.zstd.sequence_conf_dec; +import xls.modules.zstd.fse_lookup_dec; +import xls.modules.zstd.ram_demux3; +import xls.modules.zstd.ram_demux; +import xls.modules.zstd.ram_mux; +import xls.modules.zstd.refilling_shift_buffer; +import xls.modules.zstd.fse_dec; +import xls.modules.shift_buffer.shift_buffer; +import xls.modules.zstd.fse_table_creator; + + +type SequenceExecutorPacket = common::SequenceExecutorPacket; +type SequenceExecutorMessageType = common::SequenceExecutorMessageType; + +type BlockSyncData = common::BlockSyncData; +type CommandConstructorData = common::CommandConstructorData; + +enum SequenceDecoderStatus: u3 { + OK = 0, + ERROR = 1, +} + +pub struct SequenceDecoderReq { + start_addr: uN[ADDR_W], + end_addr: uN[ADDR_W], + sync: BlockSyncData, + literals_count: u20, +} + +pub struct SequenceDecoderResp { + status: SequenceDecoderStatus, +} + +enum SequenceDecoderFSM: u3 { + IDLE = 0, + DECODE_SEQUENCE_HEADER = 1, + PREPARE_LL_TABLE = 2, + PREPARE_OF_TABLE = 3, + PREPARE_ML_TABLE = 4, + + ERROR = 7, +} + +struct SequenceDecoderState { + fsm: SequenceDecoderFSM, + req: SequenceDecoderReq, + conf_resp: sequence_conf_dec::SequenceConfDecoderResp, +} + +struct FseLookupCtrlReq { + ll: bool, + ml: bool, + of: bool, + addr: uN[AXI_ADDR_W], +} + +type AccuracyLog = common::FseAccuracyLog; +struct FseLookupCtrlResp { + ll_accuracy_log: AccuracyLog, + ml_accuracy_log: AccuracyLog, + of_accuracy_log: AccuracyLog, +} + +struct FseLookupCtrlState { + decode: bool[3], + decode_valid: bool, + addr: uN[AXI_ADDR_W], + resp: FseLookupCtrlResp, + cnt: u2, +} + +pub proc FseLookupCtrl { + type Req = FseLookupCtrlReq; + type Resp = FseLookupCtrlResp; + type State = FseLookupCtrlState; + + type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; + type FseLookupDecoderResp = fse_lookup_dec::FseLookupDecoderResp; + + req_r: chan in; + resp_s: chan out; + + fld_req_s: chan out; + fld_resp_r: chan in; + + fse_demux_req_s: chan out; + fse_demux_resp_r: chan<()> in; + + init { zero!() } + + config( + req_r: chan in, + resp_s: chan out, + + fld_req_s: chan out, + fld_resp_r: chan in, + + fse_demux_req_s: chan out, + fse_demux_resp_r: chan<()> in, + ) { + ( + req_r, resp_s, + fld_req_s, fld_resp_r, + fse_demux_req_s, fse_demux_resp_r, + ) + } + + next(state: State) { + let tok0 = join(); + + if !state.decode_valid { + let (tok1_0, req) = recv(tok0, req_r); + State { + decode: bool[3]:[req.ll, req.of, req.ml], + decode_valid: true, + cnt: u2:0, + addr: req.addr, + ..zero!() + } + } else { + let do_set = state.decode[state.cnt]; + match(state.cnt) { + u2:0 => trace_fmt!("Handling LL"), + u2:1 => trace_fmt!("Handling OF"), + u2:2 => trace_fmt!("Handling ML"), + _ => trace_fmt!("Impossible case"), + }; + + // trace_fmt!("Sending request to demux {:#x}", state.cnt); + let tok1 = send_if(tok0, fse_demux_req_s, do_set, state.cnt); + if do_set { + trace_fmt!("[SequenceDecoderCtrl/FseLookupCtrl]: Sent fse_demux req {:#x}", state.cnt); + } else {}; + + let (tok2, demux_resp) = recv_if(tok1, fse_demux_resp_r, do_set, ()); + if do_set { + trace_fmt!("[SequenceDecoderCtrl/FseLookupCtrl]: Received demux resp {:#x}", demux_resp); + } else {}; + // trace_fmt!("Received response from demux"); + + let fld_req = FseLookupDecoderReq { addr: state.addr }; + let tok3 = send_if(tok2, fld_req_s, do_set, fld_req); + if do_set { + trace_fmt!("[SequenceDecoderCtrl/FseLookupCtrl]: Sent FseLookupDecoder req {:#x}", fld_req); + } else {}; + + // FIXME: Extract RefillingShiftBuffer from the FseLookupDecoder + // to support decoding multiple FSE Tables + let (tok4, fld_resp) = recv_if(tok3, fld_resp_r, do_set, zero!()); + if do_set { + trace_fmt!("[SequenceDecoderCtrl/FseLookupCtrl]: Received FseLookupDecoder resp {:#x}", fld_resp); + } else {}; + + let resp = match(state.cnt) { + u2:0 => FseLookupCtrlResp { ll_accuracy_log: fld_resp.accuracy_log, ..state.resp}, + u2:1 => FseLookupCtrlResp { of_accuracy_log: fld_resp.accuracy_log, ..state.resp}, + u2:2 => FseLookupCtrlResp { ml_accuracy_log: fld_resp.accuracy_log, ..state.resp}, + _ => fail!("impossible_cnt", zero!()), + }; + + // trace_fmt!("Received response from from FseLookupDecoder {:#x}", fld_resp); + + if state.cnt >= u2:2 { + let tok5 = send(tok4, resp_s, resp); + zero!() + } else { + State { cnt: state.cnt + u2:1, resp, ..state} + } + } + } +} + +const INST_AXI_ADDR_W = u32:32; + +pub proc FseLookupCtrlInst { + type Req = FseLookupCtrlReq; + type Resp = FseLookupCtrlResp; + + type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; + type FseLookupDecoderResp = fse_lookup_dec::FseLookupDecoderResp; + + init { } + + config( + req_r: chan in, + resp_s: chan out, + + fld_req_s: chan out, + fld_resp_r: chan in, + + demux_req_s: chan out, + demux_resp_r: chan<()> in, + ) { + spawn FseLookupCtrl( + req_r, resp_s, + fld_req_s, fld_resp_r, + demux_req_s, demux_resp_r, + ); + } + + next(state: ()) {} +} + +const TEST_FLC_AXI_ADDR_W = u32:32; + +//#[test_proc] +//proc FseLookupCtrlTest { +// +// type Req = FseLookupCtrlReq; +// type Resp = FseLookupCtrlResp; +// +// type Addr = uN[TEST_FLC_AXI_ADDR_W]; +// +// type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; +// type FseLookupDecoderResp = fse_lookup_dec::FseLookupDecoderResp; +// type FseLookupDecoderStatus = fse_lookup_dec::FseLookupDecoderStatus; +// +// terminator: chan out; +// +// req_s: chan out; +// resp_r: chan in; +// fld_req_r: chan in; +// fld_resp_s: chan out; +// demux_req_r: chan in; +// demux_resp_s: chan<()> out; +// +// init {} +// +// config( +// terminator: chan out, +// ) { +// let (req_s, req_r) = chan("req"); +// let (resp_s, resp_r) = chan("resp"); +// let (fld_req_s, fld_req_r) = chan("fld_req"); +// let (fld_resp_s, fld_resp_r) = chan("fld_resp"); +// let (demux_req_s, demux_req_r) = chan("demux_req"); +// let (demux_resp_s, demux_resp_r) = chan<()>("demux_resp"); +// +// spawn FseLookupCtrl( +// req_r, resp_s, +// fld_req_s, fld_resp_r, +// demux_req_s, demux_resp_r, +// ); +// +// ( +// terminator, +// req_s, resp_r, +// fld_req_r, fld_resp_s, +// demux_req_r, demux_resp_s, +// ) +// } +// +// next(state: ()) { +// +// // Decode all the tables +// // --------------------- +// +// // Start +// let tok = join(); +// let tok = send(tok, req_s, Req { ll: true, of: true, ml: true, addr: Addr:0 }); +// +// // Select LL ( u2:0 ) +// let (tok, demux_req) = recv(tok, demux_req_r); +// assert_eq(demux_req, u2:0); +// +// let tok = send(tok, demux_resp_s, ()); +// let (tok, fld_req) = recv(tok, fld_req_r); +// +// assert_eq(fld_req, zero!()); +// let tok = send(tok, fld_resp_s, FseLookupDecoderResp {status: FseLookupDecoderStatus::OK}); +// +// // Select OF ( u2:1 ) +// let (tok, demux_req) = recv(tok, demux_req_r); +// assert_eq(demux_req, u2:1); +// +// let tok = send(tok, demux_resp_s, ()); +// let (tok, fld_req) = recv(tok, fld_req_r); +// +// assert_eq(fld_req, zero!()); +// let tok = send(tok, fld_resp_s, FseLookupDecoderResp {status: FseLookupDecoderStatus::OK}); +// +// // Select ML ( u2:2 ) +// let (tok, demux_req) = recv(tok, demux_req_r); +// assert_eq(demux_req, u2:2); +// +// let tok = send(tok, demux_resp_s, ()); +// let (tok, _fld_req) = recv(tok, fld_req_r); +// +// assert_eq(fld_req, zero!()); +// let tok = send(tok, fld_resp_s, FseLookupDecoderResp {status: FseLookupDecoderStatus::OK}); +// +// // Stop +// let (tok, resp) = recv(tok, resp_r); +// assert_eq(resp, FseLookupCtrlResp {}); +// +// // Decode only LL and ML +// // --------------------- +// +// // Start +// let tok = join(); +// let tok = send(tok, req_s, Req { ll: true, of: false, ml: true, addr: Addr:0 }); +// +// // Select LL ( u2:0 ) +// let (tok, demux_req) = recv(tok, demux_req_r); +// assert_eq(demux_req, u2:0); +// +// let tok = send(tok, demux_resp_s, ()); +// let (tok, fld_req) = recv(tok, fld_req_r); +// +// assert_eq(fld_req, zero!()); +// let tok = send(tok, fld_resp_s, FseLookupDecoderResp {status: FseLookupDecoderStatus::OK}); +// +// // Select ML ( u2:2 ) +// let (tok, demux_req) = recv(tok, demux_req_r); +// assert_eq(demux_req, u2:2); +// +// let tok = send(tok, demux_resp_s, ()); +// let (tok, _fld_req) = recv(tok, fld_req_r); +// +// assert_eq(fld_req, zero!()); +// let tok = send(tok, fld_resp_s, FseLookupDecoderResp {status: FseLookupDecoderStatus::OK}); +// +// // Stop +// let (tok, resp) = recv(tok, resp_r); +// assert_eq(resp, FseLookupCtrlResp {}); +// +// +// // Decode only OF +// // --------------------- +// +// // Start +// let tok = join(); +// let tok = send(tok, req_s, Req { ll: false, of: true, ml: false, addr: Addr:0 }); +// +// // Select OF ( u2:1 ) +// let (tok, demux_req) = recv(tok, demux_req_r); +// assert_eq(demux_req, u2:1); +// +// let tok = send(tok, demux_resp_s, ()); +// let (tok, fld_req) = recv(tok, fld_req_r); +// +// assert_eq(fld_req, zero!()); +// let tok = send(tok, fld_resp_s, FseLookupDecoderResp {status: FseLookupDecoderStatus::OK}); +// +// // Stop +// let (tok, resp) = recv(tok, resp_r); +// assert_eq(resp, FseLookupCtrlResp {}); +// +// let tok = send(tok, terminator, true); +// } +//} + +pub proc SequenceDecoderCtrl< + AXI_ADDR_W: u32, AXI_DATA_W: u32, + REFILLING_SB_DATA_W: u32 = {AXI_DATA_W}, + REFILLING_SB_LENGTH_W: u32 = {refilling_shift_buffer::length_width(AXI_DATA_W)}, +> { + type Req = SequenceDecoderReq; + type Resp = SequenceDecoderResp; + type State = SequenceDecoderState; + type FSM = SequenceDecoderFSM; + type Status = SequenceDecoderStatus; + + type Addr = uN[AXI_ADDR_W]; + + type CompressionMode = common::CompressionMode; + type SequenceConfDecoderStatus = sequence_conf_dec::SequenceConfDecoderStatus; + + type SequenceConfDecoderReq = sequence_conf_dec::SequenceConfDecoderReq; + type SequenceConfDecoderResp = sequence_conf_dec::SequenceConfDecoderResp; + + type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; + type FseLookupDecoderResp = fse_lookup_dec::FseLookupDecoderResp; + + type RefillingShiftBufferStart = refilling_shift_buffer::RefillStart; + type RefillingShiftBufferError = refilling_shift_buffer::RefillingShiftBufferInput; + type RefillingShiftBufferOutput = refilling_shift_buffer::RefillingShiftBufferOutput; + type RefillingShiftBufferCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + + type FseDecoderCtrl = fse_dec::FseDecoderCtrl; + type FseDecoderFinish = fse_dec::FseDecoderFinish; + + sd_req_r: chan in; + sd_resp_s: chan out; + + scd_req_s: chan out; + scd_resp_r: chan in; + + flc_req_s: chan out; + flc_resp_r: chan in; + + ll_demux_req_s: chan out; + ll_demux_resp_r: chan<()> in; + + of_demux_req_s: chan out; + of_demux_resp_r: chan<()> in; + + ml_demux_req_s: chan out; + ml_demux_resp_r: chan<()> in; + + fd_rsb_start_req_s: chan out; + fd_rsb_stop_flush_req_s: chan<()> out; + fd_rsb_flushing_done_r: chan<()> in; + + fd_ctrl_s: chan out; + fd_finish_r: chan in; + + init { } + + config( + sd_req_r: chan in, + sd_resp_s: chan out, + + scd_req_s: chan out, + scd_resp_r: chan in, + + fld_req_s: chan out, + fld_resp_r: chan in, + + fld_demux_req_s: chan out, + fld_demux_resp_r: chan<()> in, + + ll_demux_req_s: chan out, + ll_demux_resp_r: chan<()> in, + + of_demux_req_s: chan out, + of_demux_resp_r: chan<()> in, + + ml_demux_req_s: chan out, + ml_demux_resp_r: chan<()> in, + + fd_rsb_start_req_s: chan out, + fd_rsb_stop_flush_req_s: chan<()> out, + fd_rsb_flushing_done_r: chan<()> in, + + fd_ctrl_s: chan out, + fd_finish_r: chan in, + ) { + const CHANNEL_DEPTH = u32:1; + + let (flc_req_s, flc_req_r) = chan("flc_req"); + let (flc_resp_s, flc_resp_r) = chan("flc_resp"); + + spawn FseLookupCtrl( + flc_req_r, flc_resp_s, + fld_req_s, fld_resp_r, + fld_demux_req_s, fld_demux_resp_r, + ); + + ( + sd_req_r, sd_resp_s, + scd_req_s, scd_resp_r, + flc_req_s, flc_resp_r, + ll_demux_req_s, ll_demux_resp_r, + of_demux_req_s, of_demux_resp_r, + ml_demux_req_s, ml_demux_resp_r, + fd_rsb_start_req_s, fd_rsb_stop_flush_req_s, fd_rsb_flushing_done_r, + fd_ctrl_s, fd_finish_r, + ) + } + + next(state: ()) { + + // Receive Sequence Decoder request + let (tok_req_sd, req) = recv(join(), sd_req_r); + trace_fmt!("[SequenceDecoderCtrl]: Received Sequence Decoder request: {:#x}", req); + + // Request decoding Sequence Header + let scd_req = SequenceConfDecoderReq { addr: req.start_addr }; + let tok_send_scd = send(tok_req_sd, scd_req_s, scd_req); + trace_fmt!("[SequenceDecoderCtrl]: Sent Sequence Decoder request: {:#x}", scd_req); + + // Receive decoded Seqence Header + let (tok_recv_scd, conf_resp) = recv(tok_send_scd, scd_resp_r); + trace_fmt!("[SequenceDecoderCtrl]: Received decoded Sequence header: {:#x}", conf_resp); + + // Request decoding lookups + let flc_req = FseLookupCtrlReq { + addr: req.start_addr + conf_resp.length as Addr, + ll: (conf_resp.header.literals_mode == CompressionMode::COMPRESSED), + ml: (conf_resp.header.match_mode == CompressionMode::COMPRESSED), + of: (conf_resp.header.offset_mode == CompressionMode::COMPRESSED), + }; + + let zero_sequences = (conf_resp.header.sequence_count == u17:0); + let tok_send_ctrl = send_if(tok_recv_scd, flc_req_s, !zero_sequences, flc_req); + if !zero_sequences { + trace_fmt!("[SequenceDecoderCtrl]: Sent FseLookupCtrl request: {:#x}", flc_req); + } else {}; + + // Receive response about deoded lookups + let (tok_recv_ctrl, flc_resp) = recv_if(tok_send_ctrl, flc_resp_r, !zero_sequences, zero!()); + + // Set proper LL lookup through demux + let ll_demux_sel = (conf_resp.header.literals_mode != CompressionMode::PREDEFINED); + let tok_ll_demux = send_if(tok_recv_scd, ll_demux_req_s, !zero_sequences, ll_demux_sel); + // Receive response from LL lookup demux + let (tok_ll_demux, _) = recv_if(tok_ll_demux, ll_demux_resp_r, !zero_sequences, ()); + + // Set proper ML lookup through demux + let ml_demux_sel = (conf_resp.header.match_mode != CompressionMode::PREDEFINED); + let tok_ml_demux = send_if(tok_recv_scd, ml_demux_req_s, !zero_sequences, ml_demux_sel); + // Receive response from ML lookup demux + let (tok_ml_demux, _) = recv_if(tok_ml_demux, ml_demux_resp_r, !zero_sequences, ()); + + // Set proper OF lookup through demux + let of_demux_sel = (conf_resp.header.match_mode != CompressionMode::PREDEFINED); + let tok_of_demux = send_if(tok_recv_scd, of_demux_req_s, !zero_sequences, of_demux_sel); + // Receive response from OF lookup demux + let (tok_of_demux, _) = recv_if(tok_of_demux, of_demux_resp_r, !zero_sequences, ()); + + let tok_demux = join(tok_ll_demux, tok_ml_demux, tok_of_demux); + + let fd_rsb_start_req = RefillingShiftBufferStart { start_addr: req.end_addr }; + let tok_rsb_start = send_if(tok_demux, fd_rsb_start_req_s, !zero_sequences, fd_rsb_start_req); + if !zero_sequences { + trace_fmt!("[SequenceDecoderCtrl]: Sent RefillingShiftBufferStart request: {:#x}", fd_rsb_start_req); + } else {}; + + let fd_ctrl = FseDecoderCtrl { + sync: req.sync, + sequences_count: conf_resp.header.sequence_count as u24, + literals_count: req.literals_count, + ll_acc_log: if (conf_resp.header.literals_mode == CompressionMode::PREDEFINED) { u7:6 } else { flc_resp.ll_accuracy_log as u7 }, + of_acc_log: if (conf_resp.header.offset_mode == CompressionMode::PREDEFINED) { u7:5 } else { flc_resp.of_accuracy_log as u7 }, + ml_acc_log: if (conf_resp.header.match_mode == CompressionMode::PREDEFINED) { u7:6 } else { flc_resp.ml_accuracy_log as u7 }, + }; + + let tok_fse_dec = send(tok_demux, fd_ctrl_s, fd_ctrl); + let (tok_fse_dec, _) = recv(tok_fse_dec, fd_finish_r); + trace_fmt!("[SequenceDecoderCtrl]: Fse finished!"); + + let tok_rsb_flush = send_if(tok_fse_dec, fd_rsb_stop_flush_req_s, !zero_sequences, ()); + trace_fmt!("[SequenceDecoderCtrl]: Send flush request"); + let (tok_rsb_flush_done, _) = recv_if(tok_rsb_flush, fd_rsb_flushing_done_r, !zero_sequences, ()); + trace_fmt!("[SequenceDecoderCtrl]: Flush done"); + + let resp = SequenceDecoderResp { status: Status::OK }; + send(tok_rsb_flush_done, sd_resp_s, resp); + } +} + +const SDC_TEST_AXI_ADDR_W = u32:32; +const SDC_TEST_AXI_DATA_W = u32:64; +const SDC_TEST_REFILLING_SB_DATA_W = {SDC_TEST_AXI_DATA_W}; +const SDC_TEST_REFILLING_SB_LENGTH_W = refilling_shift_buffer::length_width(SDC_TEST_AXI_DATA_W); + +//#[test_proc] +//proc SequenceDecoderCtrlTest { +// +// type Req = SequenceDecoderReq; +// type Resp = SequenceDecoderResp; +// type Status = SequenceDecoderStatus; +// +// type CompressionMode = common::CompressionMode; +// type Addr = uN[SDC_TEST_AXI_ADDR_W]; +// +// type SequenceConf = common::SequenceConf; +// type SequenceConfDecoderReq = sequence_conf_dec::SequenceConfDecoderReq; +// type SequenceConfDecoderResp = sequence_conf_dec::SequenceConfDecoderResp; +// type SequenceConfDecoderStatus = sequence_conf_dec::SequenceConfDecoderStatus; +// +// type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; +// type FseLookupDecoderResp = fse_lookup_dec::FseLookupDecoderResp; +// type FseLookupDecoderStatus = fse_lookup_dec::FseLookupDecoderStatus; +// +// type RefillingShiftBufferStart = refilling_shift_buffer::RefillStart; +// type RefillingShiftBufferError = refilling_shift_buffer::RefillingShiftBufferInput; +// type RefillingShiftBufferOutput = refilling_shift_buffer::RefillingShiftBufferOutput; +// type RefillingShiftBufferCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; +// +// type FseDecoderCtrl = fse_dec::FseDecoderCtrl; +// type FseDecoderFinish = fse_dec::FseDecoderFinish; +// +// terminator: chan out; +// +// sd_req_s: chan out; +// sd_resp_r: chan in; +// +// scd_req_r: chan in; +// scd_resp_s: chan out; +// +// fld_req_r: chan in; +// fld_resp_s: chan out; +// +// fse_demux_req_r: chan in; +// fse_demux_resp_s: chan<()> out; +// +// ll_demux_req_r: chan in; +// ll_demux_resp_s: chan<()> out; +// +// of_demux_req_r: chan in; +// of_demux_resp_s: chan<()> out; +// +// ml_demux_req_r: chan in; +// ml_demux_resp_s: chan<()> out; +// +// fd_rsb_start_req_r: chan in; +// fd_rsb_stop_flush_req_r: chan<()> in; +// fd_rsb_flushing_done_s: chan<()> out; +// +// fd_ctrl_r: chan in; +// fd_finish_s: chan out; +// +// init { } +// +// config(terminator: chan out) { +// let (sd_req_s, sd_req_r) = chan("sd_req"); +// let (sd_resp_s, sd_resp_r) = chan("sd_resp"); +// +// let (scd_req_s, scd_req_r) = chan("scd_req"); +// let (scd_resp_s, scd_resp_r) = chan("scd_resp"); +// +// let (fld_req_s, fld_req_r) = chan("fld_req"); +// let (fld_resp_s, fld_resp_r) = chan("fld_resp"); +// +// let (fse_demux_req_s, fse_demux_req_r) = chan("fse_demux_req"); +// let (fse_demux_resp_s, fse_demux_resp_r) = chan<()>("fse_demux_resp"); +// +// let (ll_demux_req_s, ll_demux_req_r) = chan("ll_demux_req"); +// let (ll_demux_resp_s, ll_demux_resp_r) = chan<()>("ll_demux_resp"); +// +// let (of_demux_req_s, of_demux_req_r) = chan("of_demux_req"); +// let (of_demux_resp_s, of_demux_resp_r) = chan<()>("of_demux_resp"); +// +// let (ml_demux_req_s, ml_demux_req_r) = chan("ml_demux_req"); +// let (ml_demux_resp_s, ml_demux_resp_r) = chan<()>("ml_demux_resp"); +// +// let (fd_rsb_start_req_s, fd_rsb_start_req_r) = chan("fd_rsb_start_req"); +// let (fd_rsb_stop_flush_req_s, fd_rsb_stop_flush_req_r) = chan<()>("fd_rsb_stop_flush_req"); +// let (fd_rsb_flushing_done_s, fd_rsb_flushing_done_r) = chan<()>("fd_rsb_flushing_done"); +// +// let (fd_ctrl_s, fd_ctrl_r) = chan("fd_ctrl"); +// let (fd_finish_s, fd_finish_r) = chan("fd_finish"); +// +// spawn SequenceDecoderCtrl< +// SDC_TEST_AXI_ADDR_W, SDC_TEST_AXI_DATA_W +// >( +// sd_req_r, sd_resp_s, +// scd_req_s, scd_resp_r, +// fld_req_s, fld_resp_r, +// fse_demux_req_s, fse_demux_resp_r, +// ll_demux_req_s, ll_demux_resp_r, +// of_demux_req_s, of_demux_resp_r, +// ml_demux_req_s, ml_demux_resp_r, +// fd_rsb_start_req_s, fd_rsb_stop_flush_req_s, fd_rsb_flushing_done_r, +// fd_ctrl_s, fd_finish_r, +// ); +// +// ( +// terminator, +// sd_req_s, sd_resp_r, +// scd_req_r, scd_resp_s, +// fld_req_r, fld_resp_s, +// fse_demux_req_r, fse_demux_resp_s, +// ll_demux_req_r, ll_demux_resp_s, +// of_demux_req_r, of_demux_resp_s, +// ml_demux_req_r, ml_demux_resp_s, +// fd_rsb_start_req_r, fd_rsb_stop_flush_req_r, fd_rsb_flushing_done_s, +// fd_ctrl_r, fd_finish_s, +// ) +// } +// +// next(state: ()) { +// let tok = join(); +// +// let tok = send(tok, sd_req_s, Req { +// start_addr: Addr:0x1000, +// end_addr: Addr:0x1012, +// }); +// +// let (tok, scd_req) = recv(tok, scd_req_r); +// assert_eq(scd_req, SequenceConfDecoderReq { addr: Addr: 0x1000 }); +// +// let scd_resp = SequenceConfDecoderResp { +// header: SequenceConf { +// sequence_count: u17:1, +// literals_mode: CompressionMode::PREDEFINED, +// offset_mode: CompressionMode::RLE, +// match_mode: CompressionMode::COMPRESSED, +// }, +// length: u3:5, +// status: SequenceConfDecoderStatus::OKAY +// }; +// let tok = send(tok, scd_resp_s, scd_resp); +// +// let (tok, demux_req) = recv(tok, fse_demux_req_r); +// assert_eq(demux_req, u2:2); +// let tok = send(tok, fse_demux_resp_s, ()); +// +// let (tok, fld_req) = recv(tok, fld_req_r); +// assert_eq(fld_req, FseLookupDecoderReq { +// addr: Addr:0x1005, +// }); +// +// let tok = send(tok, fld_resp_s, FseLookupDecoderResp {status: FseLookupDecoderStatus::OK}); +// +// let (tok, ll_demux) = recv(tok, ll_demux_req_r); +// assert_eq(ll_demux, u1:0); +// let tok = send(tok, ll_demux_resp_s, ()); +// +// let (tok, ml_demux) = recv(tok, ml_demux_req_r); +// assert_eq(ml_demux, u1:1); +// let tok = send(tok, ml_demux_resp_s, ()); +// +// let (tok, of_demux) = recv(tok, of_demux_req_r); +// assert_eq(of_demux, u1:1); +// let tok = send(tok, of_demux_resp_s, ()); +// +// let (tok, fd_ctrl) = recv(tok, fd_ctrl_r); +// assert_eq(fd_ctrl, zero!()); +// +// send(tok, terminator, true); +// } +//} + +pub proc SequenceDecoder< + AXI_ADDR_W: u32, AXI_DATA_W: u32, AXI_DEST_W: u32, AXI_ID_W: u32, + DPD_RAM_ADDR_W: u32, DPD_RAM_DATA_W: u32, DPD_RAM_NUM_PARTITIONS: u32, + TMP_RAM_ADDR_W: u32, TMP_RAM_DATA_W: u32, TMP_RAM_NUM_PARTITIONS: u32, + FSE_RAM_ADDR_W: u32, FSE_RAM_DATA_W: u32, FSE_RAM_NUM_PARTITIONS: u32, + + AXI_DATA_W_DIV8: u32 = {AXI_DATA_W / u32:8}, + REFILLING_SB_DATA_W: u32 = {AXI_DATA_W}, + REFILLING_SB_LENGTH_W: u32 = {refilling_shift_buffer::length_width(AXI_DATA_W)}, +> { + type Req = SequenceDecoderReq; + type Resp = SequenceDecoderResp; + + type MemAxiAr = axi::AxiAr; + type MemAxiR = axi::AxiR; + type MemAxiAw = axi::AxiAw; + type MemAxiW = axi::AxiW; + type MemAxiB = axi::AxiB; + + type MemReaderStatus = mem_reader::MemReaderStatus; + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + + type SequenceConfDecoderReq = sequence_conf_dec::SequenceConfDecoderReq; + type SequenceConfDecoderResp = sequence_conf_dec::SequenceConfDecoderResp; + + type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; + type FseLookupDecoderResp = fse_lookup_dec::FseLookupDecoderResp; + + type FseDecoderCtrl = fse_dec::FseDecoderCtrl; + type FseDecoderFinish = fse_dec::FseDecoderFinish; + + type RefillingShiftBufferStart = refilling_shift_buffer::RefillStart; + type RefillingShiftBufferError = refilling_shift_buffer::RefillingShiftBufferInput; + type RefillingShiftBufferOutput = refilling_shift_buffer::RefillingShiftBufferOutput; + type RefillingShiftBufferCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + + type DpdRamRdReq = ram::ReadReq; + type DpdRamRdResp = ram::ReadResp; + type DpdRamWrReq = ram::WriteReq; + type DpdRamWrResp = ram::WriteResp; + + type TmpRamRdReq = ram::ReadReq; + type TmpRamRdResp = ram::ReadResp; + type TmpRamWrReq = ram::WriteReq; + type TmpRamWrResp = ram::WriteResp; + + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; + type FseRamWrReq = ram::WriteReq; + type FseRamWrResp = ram::WriteResp; + + init { } + + fd_ctrl_s: chan out; + fd_finish_r: chan in; + + fd_rsb_ctrl_r: chan in; + fd_rsb_data_s: chan out; + + ll_wr_req_s: chan out; + ll_wr_resp_s: chan in; + ml_wr_req_s: chan out; + ml_wr_resp_s: chan in; + of_wr_req_s: chan out; + of_wr_resp_s: chan in; + + dummy_ll_fse_rd_req_r: chan in; + dummy_ll_fse_rd_resp_s: chan out; + dummy_ll_fse_wr_req_r: chan in; + dummy_ll_fse_wr_resp_s: chan out; + dummy_ml_fse_rd_req_r: chan in; + dummy_ml_fse_rd_resp_s: chan out; + dummy_ml_fse_wr_req_r: chan in; + dummy_ml_fse_wr_resp_s: chan out; + dummy_of_fse_rd_req_r: chan in; + dummy_of_fse_rd_resp_s: chan out; + dummy_of_fse_wr_req_r: chan in; + dummy_of_fse_wr_resp_s: chan out; + + config ( + // Sequence Conf Decoder (manager) + scd_axi_ar_s: chan out, + scd_axi_r_r: chan in, + + // Fse Lookup Decoder (manager) + fld_axi_ar_s: chan out, + fld_axi_r_r: chan in, + + // FSE decoder (manager) + fd_axi_ar_s: chan out, + fd_axi_r_r: chan in, + + req_r: chan in, + resp_s: chan out, + + // Command constructor + fd_command_s: chan out, + + // RAMs + + dpd_rd_req_s: chan out, + dpd_rd_resp_r: chan in, + dpd_wr_req_s: chan out, + dpd_wr_resp_r: chan in, + + tmp_rd_req_s: chan out, + tmp_rd_resp_r: chan in, + tmp_wr_req_s: chan out, + tmp_wr_resp_r: chan in, + + ll_def_fse_rd_req_s: chan out, + ll_def_fse_rd_resp_r: chan in, + ll_def_fse_wr_req_s: chan out, + ll_def_fse_wr_resp_r: chan in, + + ll_fse_rd_req_s: chan out, + ll_fse_rd_resp_r: chan in, + ll_fse_wr_req_s: chan out, + ll_fse_wr_resp_r: chan in, + + ml_def_fse_rd_req_s: chan out, + ml_def_fse_rd_resp_r: chan in, + ml_def_fse_wr_req_s: chan out, + ml_def_fse_wr_resp_r: chan in, + + ml_fse_rd_req_s: chan out, + ml_fse_rd_resp_r: chan in, + ml_fse_wr_req_s: chan out, + ml_fse_wr_resp_r: chan in, + + of_def_fse_rd_req_s: chan out, + of_def_fse_rd_resp_r: chan in, + of_def_fse_wr_req_s: chan out, + of_def_fse_wr_resp_r: chan in, + + of_fse_rd_req_s: chan out, + of_fse_rd_resp_r: chan in, + of_fse_wr_req_s: chan out, + of_fse_wr_resp_r: chan in, + ) { + const CHANNEL_DEPTH = u32:1; + const READ_BACKWORD = true; + + // Sequence Section Decoder + + let (scd_mem_rd_req_s, scd_mem_rd_req_r) = chan("scd_mem_rd_req"); + let (scd_mem_rd_resp_s, scd_mem_rd_resp_r) = chan("scd_mem_rd_resp"); + + spawn mem_reader::MemReader( + scd_mem_rd_req_r, scd_mem_rd_resp_s, + scd_axi_ar_s, scd_axi_r_r, + ); + + let (scd_req_s, scd_req_r) = chan("scd_req"); + let (scd_resp_s, scd_resp_r) = chan("scd_resp"); + + spawn sequence_conf_dec::SequenceConfDecoder( + scd_mem_rd_req_s, scd_mem_rd_resp_r, + scd_req_r, scd_resp_s, + ); + + // FseLookupDecoder + + let (fld_mem_rd_req_s, fld_mem_rd_req_r) = chan("fld_mem_rd_req"); + let (fld_mem_rd_resp_s, fld_mem_rd_resp_r) = chan("fld_mem_rd_resp"); + + spawn mem_reader::MemReader( + fld_mem_rd_req_r, fld_mem_rd_resp_s, + fld_axi_ar_s, fld_axi_r_r, + ); + + let (fld_req_s, fld_req_r) = chan("fse_req"); + let (fld_resp_s, fld_resp_r) = chan("fse_resp"); + + let (fse_rd_req_s, fse_rd_req_r) = chan("fse_rd_req"); + let (fse_rd_resp_s, fse_rd_resp_r) = chan("fse_rd_resp"); + let (fse_wr_req_s, fse_wr_req_r) = chan("fse_wr_req"); + let (fse_wr_resp_s, fse_wr_resp_r) = chan("fse_wr_resp"); + + spawn fse_lookup_dec::FseLookupDecoder< + AXI_DATA_W, AXI_ADDR_W, + DPD_RAM_DATA_W, DPD_RAM_ADDR_W, DPD_RAM_NUM_PARTITIONS, + TMP_RAM_DATA_W, TMP_RAM_ADDR_W, TMP_RAM_NUM_PARTITIONS, + FSE_RAM_DATA_W, FSE_RAM_ADDR_W, FSE_RAM_NUM_PARTITIONS, + >( + fld_req_r, fld_resp_s, + fld_mem_rd_req_s, fld_mem_rd_resp_r, + dpd_rd_req_s, dpd_rd_resp_r, dpd_wr_req_s, dpd_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + fse_rd_req_s, fse_rd_resp_r, fse_wr_req_s, fse_wr_resp_r, + ); + + // RamDemux3 + let (fse_demux_req_s, fse_demux_req_r) = chan("fse_demux_req"); + let (fse_demux_resp_s, fse_demux_resp_r) = chan<(), CHANNEL_DEPTH>("fse_demux_resp"); + + spawn ram_demux3::RamDemux3( + fse_demux_req_r, fse_demux_resp_s, + fse_rd_req_r, fse_rd_resp_s, fse_wr_req_r, fse_wr_resp_s, + ll_fse_rd_req_s, ll_fse_rd_resp_r, ll_fse_wr_req_s, ll_fse_wr_resp_r, + of_fse_rd_req_s, of_fse_rd_resp_r, of_fse_wr_req_s, of_fse_wr_resp_r, + ml_fse_rd_req_s, ml_fse_rd_resp_r, ml_fse_wr_req_s, ml_fse_wr_resp_r, + ); + + let (ll_demux_req_s, ll_demux_req_r) = chan("ll_demux_req"); + let (ll_demux_resp_s, ll_demux_resp_r) = chan<(), CHANNEL_DEPTH>("ll_demux_resp"); + + let (ll_rd_req_s, ll_rd_req_r) = chan("ll_rd_req"); + let (ll_rd_resp_s, ll_rd_resp_r) = chan("ll_rd_resp"); + let (ll_wr_req_s, ll_wr_req_r) = chan("ll_wr_req"); + let (ll_wr_resp_s, ll_wr_resp_r) = chan("ll_wr_resp"); + + let (dummy_ll_fse_rd_req_s, dummy_ll_fse_rd_req_r) = chan("dummy_ll_fse_rd_req"); + let (dummy_ll_fse_rd_resp_s, dummy_ll_fse_rd_resp_r) = chan("dummy_ll_fse_rd_resp"); + let (dummy_ll_fse_wr_req_s, dummy_ll_fse_wr_req_r) = chan("dummy_ll_fse_wr_req"); + let (dummy_ll_fse_wr_resp_s, dummy_ll_fse_wr_resp_r) = chan("dummy_ll_fse_wr_resp"); + + spawn ram_demux::RamDemux< + FSE_RAM_ADDR_W, FSE_RAM_DATA_W, FSE_RAM_NUM_PARTITIONS + > ( + ll_demux_req_r, ll_demux_resp_s, + ll_rd_req_r, ll_rd_resp_s, ll_wr_req_r, ll_wr_resp_s, + ll_def_fse_rd_req_s, ll_def_fse_rd_resp_r, ll_def_fse_wr_req_s, ll_def_fse_wr_resp_r, + dummy_ll_fse_rd_req_s, dummy_ll_fse_rd_resp_r, dummy_ll_fse_wr_req_s, dummy_ll_fse_wr_resp_r, + ); + + let (ml_demux_req_s, ml_demux_req_r) = chan("ml_demux_req"); + let (ml_demux_resp_s, ml_demux_resp_r) = chan<(), CHANNEL_DEPTH>("ml_demux_resp"); + + let (ml_rd_req_s, ml_rd_req_r) = chan("ml_rd_req"); + let (ml_rd_resp_s, ml_rd_resp_r) = chan("ml_rd_resp"); + let (ml_wr_req_s, ml_wr_req_r) = chan("ml_wr_req"); + let (ml_wr_resp_s, ml_wr_resp_r) = chan("ml_wr_resp"); + + let (dummy_ml_fse_rd_req_s, dummy_ml_fse_rd_req_r) = chan("dummy_ml_fse_rd_req"); + let (dummy_ml_fse_rd_resp_s, dummy_ml_fse_rd_resp_r) = chan("dummy_ml_fse_rd_resp"); + let (dummy_ml_fse_wr_req_s, dummy_ml_fse_wr_req_r) = chan("dummy_ml_fse_wr_req"); + let (dummy_ml_fse_wr_resp_s, dummy_ml_fse_wr_resp_r) = chan("dummy_ml_fse_wr_resp"); + + spawn ram_demux::RamDemux< + FSE_RAM_ADDR_W, FSE_RAM_DATA_W, FSE_RAM_NUM_PARTITIONS + > ( + ml_demux_req_r, ml_demux_resp_s, + ml_rd_req_r, ml_rd_resp_s, ml_wr_req_r, ml_wr_resp_s, + ml_def_fse_rd_req_s, ml_def_fse_rd_resp_r, ml_def_fse_wr_req_s, ml_def_fse_wr_resp_r, + dummy_ml_fse_rd_req_s, dummy_ml_fse_rd_resp_r, dummy_ml_fse_wr_req_s, dummy_ml_fse_wr_resp_r, + ); + + let (of_demux_req_s, of_demux_req_r) = chan("of_demux_req"); + let (of_demux_resp_s, of_demux_resp_r) = chan<(), CHANNEL_DEPTH>("of_demux_resp"); + + let (of_rd_req_s, of_rd_req_r) = chan("of_rd_req"); + let (of_rd_resp_s, of_rd_resp_r) = chan("of_rd_resp"); + let (of_wr_req_s, of_wr_req_r) = chan("of_wr_req"); + let (of_wr_resp_s, of_wr_resp_r) = chan("of_wr_resp"); + + let (dummy_of_fse_rd_req_s, dummy_of_fse_rd_req_r) = chan("dummy_of_fse_rd_req"); + let (dummy_of_fse_rd_resp_s, dummy_of_fse_rd_resp_r) = chan("dummy_of_fse_rd_resp"); + let (dummy_of_fse_wr_req_s, dummy_of_fse_wr_req_r) = chan("dummy_of_fse_wr_req"); + let (dummy_of_fse_wr_resp_s, dummy_of_fse_wr_resp_r) = chan("dummy_of_fse_wr_resp"); + + spawn ram_demux::RamDemux< + FSE_RAM_ADDR_W, FSE_RAM_DATA_W, FSE_RAM_NUM_PARTITIONS + > ( + of_demux_req_r, of_demux_resp_s, + of_rd_req_r, of_rd_resp_s, of_wr_req_r, of_wr_resp_s, + of_def_fse_rd_req_s, of_def_fse_rd_resp_r, of_def_fse_wr_req_s, of_def_fse_wr_resp_r, + dummy_of_fse_rd_req_s, dummy_of_fse_rd_resp_r, dummy_of_fse_wr_req_s, dummy_of_fse_wr_resp_r, + ); + + let (fd_mem_rd_req_s, fd_mem_rd_req_r) = chan("fd_mem_rd_req"); + let (fd_mem_rd_resp_s, fd_mem_rd_resp_r) = chan("fd_mem_rd_resp"); + + spawn mem_reader::MemReader( + fd_mem_rd_req_r, fd_mem_rd_resp_s, + fd_axi_ar_s, fd_axi_r_r, + ); + + let (fd_rsb_start_req_s, fd_rsb_start_req_r) = chan("fd_rsb_start_req"); + let (fd_rsb_stop_flush_req_s, fd_rsb_stop_flush_req_r) = chan<(), CHANNEL_DEPTH>("fd_rsb_stop_flush_req"); + let (fd_rsb_ctrl_s, fd_rsb_ctrl_r) = chan("fd_rsb_ctrl"); + let (fd_rsb_data_s, fd_rsb_data_r) = chan("fd_rsb_data"); + let (fd_rsb_flushing_done_s, fd_rsb_flushing_done_r) = chan<(), CHANNEL_DEPTH>("fd_rsb_flushing_done"); + + spawn refilling_shift_buffer::RefillingShiftBuffer ( + fd_mem_rd_req_s, fd_mem_rd_resp_r, + fd_rsb_start_req_r, fd_rsb_stop_flush_req_r, + fd_rsb_ctrl_r, fd_rsb_data_s, + fd_rsb_flushing_done_s, + ); + + let (fd_ctrl_s, fd_ctrl_r) = chan("fd_ctrl"); + let (fd_finish_s, fd_finish_r) = chan("fd_finish"); + + spawn fse_dec::FseDecoder< + FSE_RAM_DATA_W, FSE_RAM_ADDR_W, FSE_RAM_NUM_PARTITIONS, AXI_DATA_W, + >( + fd_ctrl_r, fd_finish_s, + fd_rsb_ctrl_s, fd_rsb_data_r, + fd_command_s, + ll_rd_req_s, ll_rd_resp_r, + ml_rd_req_s, ml_rd_resp_r, + of_rd_req_s, of_rd_resp_r, + ); + + spawn SequenceDecoderCtrl( + req_r, resp_s, + scd_req_s, scd_resp_r, + fld_req_s, fld_resp_r, + fse_demux_req_s, fse_demux_resp_r, + ll_demux_req_s, ll_demux_resp_r, + of_demux_req_s, of_demux_resp_r, + ml_demux_req_s, ml_demux_resp_r, + fd_rsb_start_req_s, fd_rsb_stop_flush_req_s, fd_rsb_flushing_done_r, + fd_ctrl_s, fd_finish_r, + ); + + ( + fd_ctrl_s, fd_finish_r, + fd_rsb_ctrl_r, fd_rsb_data_s, + ll_wr_req_s, ll_wr_resp_r, + ml_wr_req_s, ml_wr_resp_r, + of_wr_req_s, of_wr_resp_r, + dummy_ll_fse_rd_req_r, + dummy_ll_fse_rd_resp_s, + dummy_ll_fse_wr_req_r, + dummy_ll_fse_wr_resp_s, + dummy_ml_fse_rd_req_r, + dummy_ml_fse_rd_resp_s, + dummy_ml_fse_wr_req_r, + dummy_ml_fse_wr_resp_s, + dummy_of_fse_rd_req_r, + dummy_of_fse_rd_resp_s, + dummy_of_fse_wr_req_r, + dummy_of_fse_wr_resp_s, + + ) + } + + next(state: ()) { + send_if(join(), ll_wr_req_s, false, zero!()); + recv_if(join(), ll_wr_resp_s, false, zero!()); + send_if(join(), ml_wr_req_s, false, zero!()); + recv_if(join(), ml_wr_resp_s, false, zero!()); + send_if(join(), of_wr_req_s, false, zero!()); + recv_if(join(), of_wr_resp_s, false, zero!()); + + recv_if(join(), dummy_ll_fse_rd_req_r, false, zero!()); + send_if(join(), dummy_ll_fse_rd_resp_s, false, zero!()); + recv_if(join(), dummy_ll_fse_wr_req_r, false, zero!()); + send_if(join(), dummy_ll_fse_wr_resp_s, false, zero!()); + recv_if(join(), dummy_ml_fse_rd_req_r, false, zero!()); + send_if(join(), dummy_ml_fse_rd_resp_s, false, zero!()); + recv_if(join(), dummy_ml_fse_wr_req_r, false, zero!()); + send_if(join(), dummy_ml_fse_wr_resp_s, false, zero!()); + recv_if(join(), dummy_of_fse_rd_req_r, false, zero!()); + send_if(join(), dummy_of_fse_rd_resp_s, false, zero!()); + recv_if(join(), dummy_of_fse_wr_req_r, false, zero!()); + send_if(join(), dummy_of_fse_wr_resp_s, false, zero!()); + } +} + +const TEST_AXI_ADDR_W = u32:32; +const TEST_AXI_DATA_W = u32:64; +const TEST_AXI_DEST_W = u32:8; +const TEST_AXI_ID_W = u32:8; + +const TEST_INPUT_RAM_DATA_W = TEST_AXI_DATA_W; +const TEST_INPUT_RAM_SIZE = u32:1024; +const TEST_INPUT_RAM_ADDR_W = TEST_AXI_ADDR_W; +const TEST_INPUT_RAM_WORD_PARTITION_SIZE = TEST_INPUT_RAM_DATA_W / u32:8; +const TEST_INPUT_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_INPUT_RAM_WORD_PARTITION_SIZE, TEST_INPUT_RAM_DATA_W); +const TEST_INPUT_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_INPUT_RAM_INITIALIZED = true; +const TEST_INPUT_RAM_ASSERT_VALID_READ = true; + +const TEST_DPD_RAM_DATA_W = u32:16; +const TEST_DPD_RAM_SIZE = u32:256; +const TEST_DPD_RAM_ADDR_W = std::clog2(TEST_DPD_RAM_SIZE); +const TEST_DPD_RAM_WORD_PARTITION_SIZE = TEST_DPD_RAM_DATA_W; +const TEST_DPD_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_DPD_RAM_WORD_PARTITION_SIZE, TEST_DPD_RAM_DATA_W); + +const TEST_FSE_RAM_DATA_W = u32:32; +const TEST_FSE_RAM_SIZE = u32:256; +const TEST_FSE_RAM_ADDR_W = std::clog2(TEST_FSE_RAM_SIZE); +const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_W / u32:3; +const TEST_FSE_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_FSE_RAM_WORD_PARTITION_SIZE, TEST_FSE_RAM_DATA_W); +const TEST_FSE_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; + +const TEST_TMP_RAM_DATA_W = u32:16; +const TEST_TMP_RAM_SIZE = u32:256; +const TEST_TMP_RAM_ADDR_W = std::clog2(TEST_TMP_RAM_SIZE); +const TEST_TMP_RAM_WORD_PARTITION_SIZE = TEST_TMP_RAM_DATA_W; +const TEST_TMP_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_TMP_RAM_WORD_PARTITION_SIZE, TEST_TMP_RAM_DATA_W); + +const TEST_RAM_DATA = u64[40]:[ + u64:0x0, u64:0x0, // 0x000 + u64:0x0, u64:0x0, // 0x010 + u64:0x0, u64:0x0, // 0x020 + u64:0x0, u64:0x0, // 0x030 + u64:0x0, u64:0x0, // 0x040 + u64:0x0, u64:0x0, // 0x050 + u64:0x0, u64:0x0, // 0x060 + u64:0x0, u64:0x0, // 0x070 + u64:0x0, u64:0x0, // 0x080 + u64:0x0, u64:0x0, // 0x090 + u64:0x0, u64:0x0, // 0x0A0 + u64:0x0, u64:0x0, // 0x0B0 + u64:0x0, u64:0x0, // 0x0C0 + u64:0x0, u64:0x0, // 0x0D0 + u64:0x0, u64:0x0, // 0x0E0 + u64:0x0, u64:0x0, // 0x0F0 + u64:0x0016A400E6C3000A, // 0x100 + u64:0x225100295B0012D6, // 0x138 + u64:0x1E00123CB813DB80, // 0x140 + u64:0x1026064002C4800A, // 0x148 + u64:0x0, ... +]; + +const EXPECTED_OUTPUT = SequenceExecutorPacket[16]:[ + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x001c, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x000a, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x0389, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x013c, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x0479, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0009, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x001d, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0001, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x024a, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0001, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x032b, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0001, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x02b5, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0002, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x03a9, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0004, + content: u64:0x06c3, + last: true, + }, +]; + +type Base = u16; +type Symbol = u8; +type NumOfBits = u8; + +type FseTableRecord = common::FseTableRecord; + +pub const DEFAULT_LL_TABLE = FseTableRecord[64]: [ + FseTableRecord { symbol: Symbol:0, num_of_bits: NumOfBits:4, base: Base:0 }, + FseTableRecord { symbol: Symbol:0, num_of_bits: NumOfBits:4, base: Base:16 }, + FseTableRecord { symbol: Symbol:1, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:3, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:4, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:6, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:7, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:9, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:10, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:12, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:14, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:16, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:18, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:19, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:21, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:22, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:24, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:25, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:26, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:27, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:29, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:31, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:0, num_of_bits: NumOfBits:4, base: Base:32 }, + FseTableRecord { symbol: Symbol:1, num_of_bits: NumOfBits:4, base: Base:0 }, + FseTableRecord { symbol: Symbol:2, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:4, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:5, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:7, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:8, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:10, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:11, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:13, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:16, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:17, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:19, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:20, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:22, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:23, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:25, num_of_bits: NumOfBits:4, base: Base:0 }, + FseTableRecord { symbol: Symbol:25, num_of_bits: NumOfBits:4, base: Base:16 }, + FseTableRecord { symbol: Symbol:26, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:28, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:30, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:0, num_of_bits: NumOfBits:4, base: Base:48 }, + FseTableRecord { symbol: Symbol:1, num_of_bits: NumOfBits:4, base: Base:16 }, + FseTableRecord { symbol: Symbol:2, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:3, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:5, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:6, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:8, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:9, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:11, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:12, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:15, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:17, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:18, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:20, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:21, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:23, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:24, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:35, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:34, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:33, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:32, num_of_bits: NumOfBits:6, base: Base:0 }, +]; + +pub const DEFAULT_ML_TABLE = FseTableRecord[64]: [ + FseTableRecord { symbol: Symbol:0, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:1, num_of_bits: NumOfBits:4, base: Base:0 }, + FseTableRecord { symbol: Symbol:2, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:3, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:5, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:6, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:8, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:10, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:13, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:16, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:19, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:22, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:25, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:28, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:31, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:33, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:35, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:37, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:39, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:41, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:43, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:45, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:1, num_of_bits: NumOfBits:4, base: Base:16 }, + FseTableRecord { symbol: Symbol:2, num_of_bits: NumOfBits:4, base: Base:0 }, + FseTableRecord { symbol: Symbol:3, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:4, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:6, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:7, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:9, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:12, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:15, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:18, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:21, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:24, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:27, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:30, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:32, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:34, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:36, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:38, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:40, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:42, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:44, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:1, num_of_bits: NumOfBits:4, base: Base:32 }, + FseTableRecord { symbol: Symbol:1, num_of_bits: NumOfBits:4, base: Base:48 }, + FseTableRecord { symbol: Symbol:2, num_of_bits: NumOfBits:4, base: Base:16 }, + FseTableRecord { symbol: Symbol:4, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:5, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:7, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:8, num_of_bits: NumOfBits:5, base: Base:32 }, + FseTableRecord { symbol: Symbol:11, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:14, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:17, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:20, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:23, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:26, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:29, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:52, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:51, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:50, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:49, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:48, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:47, num_of_bits: NumOfBits:6, base: Base:0 }, + FseTableRecord { symbol: Symbol:46, num_of_bits: NumOfBits:6, base: Base:0 }, +]; + +pub const DEFAULT_OF_TABLE = FseTableRecord[32]:[ + FseTableRecord { symbol: Symbol:0, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:6, num_of_bits: NumOfBits:4, base: Base:0 }, + FseTableRecord { symbol: Symbol:9, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:15, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:21, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:3, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:7, num_of_bits: NumOfBits:4, base: Base:0 }, + FseTableRecord { symbol: Symbol:12, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:18, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:23, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:5, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:8, num_of_bits: NumOfBits:4, base: Base:0 }, + FseTableRecord { symbol: Symbol:14, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:20, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:2, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:7, num_of_bits: NumOfBits:4, base: Base:16 }, + FseTableRecord { symbol: Symbol:11, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:17, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:22, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:4, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:8, num_of_bits: NumOfBits:4, base: Base:16 }, + FseTableRecord { symbol: Symbol:13, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:19, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:1, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:6, num_of_bits: NumOfBits:4, base: Base:16 }, + FseTableRecord { symbol: Symbol:10, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:16, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:28, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:27, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:26, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:25, num_of_bits: NumOfBits:5, base: Base:0 }, + FseTableRecord { symbol: Symbol:24, num_of_bits: NumOfBits:5, base: Base:0 }, +]; + +#[test_proc] +proc SequenceDecoderTest { + type Req = SequenceDecoderReq; + type Resp = SequenceDecoderResp; + + type InputAddr = uN[TEST_INPUT_RAM_ADDR_W]; + type InputData = uN[TEST_INPUT_RAM_DATA_W]; + type InputMask = uN[TEST_INPUT_RAM_NUM_PARTITIONS]; + + type InputRamRdReq = ram::ReadReq; + type InputRamRdResp = ram::ReadResp; + type InputRamWrReq = ram::WriteReq; + type InputRamWrResp = ram::WriteResp; + + type DpdRamRdReq = ram::ReadReq; + type DpdRamRdResp = ram::ReadResp; + type DpdRamWrReq = ram::WriteReq; + type DpdRamWrResp = ram::WriteResp; + + type TmpRamRdReq = ram::ReadReq; + type TmpRamRdResp = ram::ReadResp; + type TmpRamWrReq = ram::WriteReq; + type TmpRamWrResp = ram::WriteResp; + + type FseAddr = uN[TEST_FSE_RAM_ADDR_W]; + type FseData = uN[TEST_FSE_RAM_DATA_W]; + type FseMask = uN[TEST_FSE_RAM_NUM_PARTITIONS]; + + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; + type FseRamWrReq = ram::WriteReq; + type FseRamWrResp = ram::WriteResp; + + type MemAxiAr = axi::AxiAr; + type MemAxiR = axi::AxiR; + + terminator: chan out; + + req_s: chan out; + resp_r: chan in; + + fd_command_r: chan in; + + input0_rd_req_s: chan out; + input0_rd_resp_r: chan in; + input0_wr_req_s: chan out; + input0_wr_resp_r: chan in; + + input1_rd_req_s: chan out; + input1_rd_resp_r: chan in; + input1_wr_req_s: chan out; + input1_wr_resp_r: chan in; + + input2_rd_req_s: chan out; + input2_rd_resp_r: chan in; + input2_wr_req_s: chan out; + input2_wr_resp_r: chan in; + + ll_sel_test_s: chan out; + ll_def_test_rd_req_s: chan out; + ll_def_test_rd_resp_r: chan in; + ll_def_test_wr_req_s: chan out; + ll_def_test_wr_resp_r: chan in; + + ml_sel_test_s: chan out; + ml_def_test_rd_req_s: chan out; + ml_def_test_rd_resp_r: chan in; + ml_def_test_wr_req_s: chan out; + ml_def_test_wr_resp_r: chan in; + + of_sel_test_s: chan out; + of_def_test_rd_req_s: chan out; + of_def_test_rd_resp_r: chan in; + of_def_test_wr_req_s: chan out; + of_def_test_wr_resp_r: chan in; + + init { } + + config( + terminator: chan out + ) { + // RAM for probability distribution + let (dpd_rd_req_s, dpd_rd_req_r) = chan("dpd_rd_req"); + let (dpd_rd_resp_s, dpd_rd_resp_r) = chan("dpd_rd_resp"); + let (dpd_wr_req_s, dpd_wr_req_r) = chan("dpd_wr_req"); + let (dpd_wr_resp_s, dpd_wr_resp_r) = chan("dpd_wr_resp"); + + spawn ram::RamModel< + TEST_DPD_RAM_DATA_W, + TEST_DPD_RAM_SIZE, + TEST_DPD_RAM_WORD_PARTITION_SIZE + >(dpd_rd_req_r, dpd_rd_resp_s, dpd_wr_req_r, dpd_wr_resp_s); + + // RAMs for temporary values when decoding probability distribution + let (tmp_rd_req_s, tmp_rd_req_r) = chan("tmp_rd_req"); + let (tmp_rd_resp_s, tmp_rd_resp_r) = chan("tmp_rd_resp"); + let (tmp_wr_req_s, tmp_wr_req_r) = chan("tmp_wr_req"); + let (tmp_wr_resp_s, tmp_wr_resp_r) = chan("tmp_wr_resp"); + + spawn ram::RamModel< + TEST_TMP_RAM_DATA_W, + TEST_TMP_RAM_SIZE, + TEST_TMP_RAM_WORD_PARTITION_SIZE + >(tmp_rd_req_r, tmp_rd_resp_s, tmp_wr_req_r, tmp_wr_resp_s); + + // RAM with default FSE lookup for Literal Lengths + + let (ll_sel_test_s, ll_sel_test_r) = chan("ll_test_sel"); + + let (ll_def_test_rd_req_s, ll_def_test_rd_req_r) = chan("ll_def_test_rd_req"); + let (ll_def_test_rd_resp_s, ll_def_test_rd_resp_r) = chan("ll_def_test_rd_resp"); + let (ll_def_test_wr_req_s, ll_def_test_wr_req_r) = chan("ll_def_test_wr_req"); + let (ll_def_test_wr_resp_s, ll_def_test_wr_resp_r) = chan("ll_def_test_wr_resp"); + + let (ll_def_fse_rd_req_s, ll_def_fse_rd_req_r) = chan("ll_def_fse_rd_req"); + let (ll_def_fse_rd_resp_s, ll_def_fse_rd_resp_r) = chan("ll_def_fse_rd_resp"); + let (ll_def_fse_wr_req_s, ll_def_fse_wr_req_r) = chan("ll_def_fse_wr_req"); + let (ll_def_fse_wr_resp_s, ll_def_fse_wr_resp_r) = chan("ll_def_fse_wr_resp"); + + let (ll_def_rd_req_s, ll_def_rd_req_r) = chan("ll_def_rd_req"); + let (ll_def_rd_resp_s, ll_def_rd_resp_r) = chan("ll_def_rd_resp"); + let (ll_def_wr_req_s, ll_def_wr_req_r) = chan("ll_def_wr_req"); + let (ll_def_wr_resp_s, ll_def_wr_resp_r) = chan("ll_def_wr_resp"); + + spawn ram_mux::RamMux< + TEST_FSE_RAM_ADDR_W, + TEST_FSE_RAM_DATA_W, + TEST_FSE_RAM_NUM_PARTITIONS, + >( + ll_sel_test_r, + ll_def_test_rd_req_r, ll_def_test_rd_resp_s, ll_def_test_wr_req_r, ll_def_test_wr_resp_s, + ll_def_fse_rd_req_r, ll_def_fse_rd_resp_s, ll_def_fse_wr_req_r, ll_def_fse_wr_resp_s, + ll_def_rd_req_s, ll_def_rd_resp_r, ll_def_wr_req_s, ll_def_wr_resp_r, + ); + + spawn ram::RamModel< + TEST_FSE_RAM_DATA_W, + TEST_FSE_RAM_SIZE, + TEST_FSE_RAM_WORD_PARTITION_SIZE + >(ll_def_rd_req_r, ll_def_rd_resp_s, ll_def_wr_req_r, ll_def_wr_resp_s); + + // RAM for FSE lookup for Literal Lengths + let (ll_fse_rd_req_s, ll_fse_rd_req_r) = chan("ll_fse_rd_req"); + let (ll_fse_rd_resp_s, ll_fse_rd_resp_r) = chan("ll_fse_rd_resp"); + let (ll_fse_wr_req_s, ll_fse_wr_req_r) = chan("ll_fse_wr_req"); + let (ll_fse_wr_resp_s, ll_fse_wr_resp_r) = chan("ll_fse_wr_resp"); + + spawn ram::RamModel< + TEST_FSE_RAM_DATA_W, + TEST_FSE_RAM_SIZE, + TEST_FSE_RAM_WORD_PARTITION_SIZE + >(ll_fse_rd_req_r, ll_fse_rd_resp_s, ll_fse_wr_req_r, ll_fse_wr_resp_s); + + // RAM with default FSE lookup for Match Lengths + + let (ml_sel_test_s, ml_sel_test_r) = chan("ml_sel_test"); + + let (ml_def_test_rd_req_s, ml_def_test_rd_req_r) = chan("ml_def_test_rd_req"); + let (ml_def_test_rd_resp_s, ml_def_test_rd_resp_r) = chan("ml_def_test_rd_resp"); + let (ml_def_test_wr_req_s, ml_def_test_wr_req_r) = chan("ml_def_test_wr_req"); + let (ml_def_test_wr_resp_s, ml_def_test_wr_resp_r) = chan("ml_def_test_wr_resp"); + + let (ml_def_fse_rd_req_s, ml_def_fse_rd_req_r) = chan("ml_def_fse_rd_req"); + let (ml_def_fse_rd_resp_s, ml_def_fse_rd_resp_r) = chan("ml_def_fse_rd_resp"); + let (ml_def_fse_wr_req_s, ml_def_fse_wr_req_r) = chan("ml_def_fse_wr_req"); + let (ml_def_fse_wr_resp_s, ml_def_fse_wr_resp_r) = chan("ml_def_fse_wr_resp"); + + let (ml_def_rd_req_s, ml_def_rd_req_r) = chan("ml_def_rd_req"); + let (ml_def_rd_resp_s, ml_def_rd_resp_r) = chan("ml_def_rd_resp"); + let (ml_def_wr_req_s, ml_def_wr_req_r) = chan("ml_def_wr_req"); + let (ml_def_wr_resp_s, ml_def_wr_resp_r) = chan("ml_def_wr_resp"); + + spawn ram_mux::RamMux< + TEST_FSE_RAM_ADDR_W, + TEST_FSE_RAM_DATA_W, + TEST_FSE_RAM_NUM_PARTITIONS, + >( + ml_sel_test_r, + ml_def_test_rd_req_r, ml_def_test_rd_resp_s, ml_def_test_wr_req_r, ml_def_test_wr_resp_s, + ml_def_fse_rd_req_r, ml_def_fse_rd_resp_s, ml_def_fse_wr_req_r, ml_def_fse_wr_resp_s, + ml_def_rd_req_s, ml_def_rd_resp_r, ml_def_wr_req_s, ml_def_wr_resp_r, + ); + + spawn ram::RamModel< + TEST_FSE_RAM_DATA_W, + TEST_FSE_RAM_SIZE, + TEST_FSE_RAM_WORD_PARTITION_SIZE + >(ml_def_rd_req_r, ml_def_rd_resp_s, ml_def_wr_req_r, ml_def_wr_resp_s); + + // RAM for FSE lookup for Match Lengths + let (ml_fse_rd_req_s, ml_fse_rd_req_r) = chan("ml_fse_rd_req"); + let (ml_fse_rd_resp_s, ml_fse_rd_resp_r) = chan("ml_fse_rd_resp"); + let (ml_fse_wr_req_s, ml_fse_wr_req_r) = chan("ml_fse_wr_req"); + let (ml_fse_wr_resp_s, ml_fse_wr_resp_r) = chan("ml_fse_wr_resp"); + + spawn ram::RamModel< + TEST_FSE_RAM_DATA_W, + TEST_FSE_RAM_SIZE, + TEST_FSE_RAM_WORD_PARTITION_SIZE + >(ml_fse_rd_req_r, ml_fse_rd_resp_s, ml_fse_wr_req_r, ml_fse_wr_resp_s); + + // RAM with default FSE lookup for Offsets + + let (of_sel_test_s, of_sel_test_r) = chan("of_sel_test"); + + let (of_def_test_rd_req_s, of_def_test_rd_req_r) = chan("of_def_test_rd_req"); + let (of_def_test_rd_resp_s, of_def_test_rd_resp_r) = chan("of_def_test_rd_resp"); + let (of_def_test_wr_req_s, of_def_test_wr_req_r) = chan("of_def_test_wr_req"); + let (of_def_test_wr_resp_s, of_def_test_wr_resp_r) = chan("of_def_test_wr_resp"); + + let (of_def_fse_rd_req_s, of_def_fse_rd_req_r) = chan("of_def_fse_rd_req"); + let (of_def_fse_rd_resp_s, of_def_fse_rd_resp_r) = chan("of_def_fse_rd_resp"); + let (of_def_fse_wr_req_s, of_def_fse_wr_req_r) = chan("of_def_fse_wr_req"); + let (of_def_fse_wr_resp_s, of_def_fse_wr_resp_r) = chan("of_def_fse_wr_resp"); + + let (of_def_rd_req_s, of_def_rd_req_r) = chan("of_def_rd_req"); + let (of_def_rd_resp_s, of_def_rd_resp_r) = chan("of_def_rd_resp"); + let (of_def_wr_req_s, of_def_wr_req_r) = chan("of_def_wr_req"); + let (of_def_wr_resp_s, of_def_wr_resp_r) = chan("of_def_wr_resp"); + + spawn ram_mux::RamMux< + TEST_FSE_RAM_ADDR_W, + TEST_FSE_RAM_DATA_W, + TEST_FSE_RAM_NUM_PARTITIONS, + >( + of_sel_test_r, + of_def_test_rd_req_r, of_def_test_rd_resp_s, of_def_test_wr_req_r, of_def_test_wr_resp_s, + of_def_fse_rd_req_r, of_def_fse_rd_resp_s, of_def_fse_wr_req_r, of_def_fse_wr_resp_s, + of_def_rd_req_s, of_def_rd_resp_r, of_def_wr_req_s, of_def_wr_resp_r, + ); + + spawn ram::RamModel< + TEST_FSE_RAM_DATA_W, + TEST_FSE_RAM_SIZE, + TEST_FSE_RAM_WORD_PARTITION_SIZE + >(of_def_rd_req_r, of_def_rd_resp_s, of_def_wr_req_r, of_def_wr_resp_s); + + // RAM for FSE lookup for Offsets + let (of_fse_rd_req_s, of_fse_rd_req_r) = chan("of_fse_rd_req"); + let (of_fse_rd_resp_s, of_fse_rd_resp_r) = chan("of_fse_rd_resp"); + let (of_fse_wr_req_s, of_fse_wr_req_r) = chan("of_fse_wr_req"); + let (of_fse_wr_resp_s, of_fse_wr_resp_r) = chan("of_fse_wr_resp"); + + spawn ram::RamModel< + TEST_FSE_RAM_DATA_W, + TEST_FSE_RAM_SIZE, + TEST_FSE_RAM_WORD_PARTITION_SIZE + >(of_fse_rd_req_r, of_fse_rd_resp_s, of_fse_wr_req_r, of_fse_wr_resp_s); + + // Input Memory + + let (input0_rd_req_s, input0_rd_req_r) = chan("input_rd_req"); + let (input0_rd_resp_s, input0_rd_resp_r) = chan("input_rd_resp"); + let (input0_wr_req_s, input0_wr_req_r) = chan("input_wr_req"); + let (input0_wr_resp_s, input0_wr_resp_r) = chan("input_wr_resp"); + + spawn ram::RamModel< + TEST_INPUT_RAM_DATA_W, + TEST_INPUT_RAM_SIZE, + TEST_INPUT_RAM_WORD_PARTITION_SIZE, + TEST_INPUT_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_INPUT_RAM_INITIALIZED, + TEST_INPUT_RAM_ASSERT_VALID_READ, + TEST_INPUT_RAM_ADDR_W, + TEST_INPUT_RAM_NUM_PARTITIONS, + >(input0_rd_req_r, input0_rd_resp_s, input0_wr_req_r, input0_wr_resp_s); + + let (ss_axi_ar_s, ss_axi_ar_r) = chan("ss_axi_ar"); + let (ss_axi_r_s, ss_axi_r_r) = chan("ss_axi_r"); + + spawn axi_ram::AxiRamReader< + TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, + TEST_INPUT_RAM_SIZE + >( + ss_axi_ar_r, ss_axi_r_s, + input0_rd_req_s, input0_rd_resp_r, + ); + + let (input1_rd_req_s, input1_rd_req_r) = chan("input_rd_req"); + let (input1_rd_resp_s, input1_rd_resp_r) = chan("input_rd_resp"); + let (input1_wr_req_s, input1_wr_req_r) = chan("input_wr_req"); + let (input1_wr_resp_s, input1_wr_resp_r) = chan("input_wr_resp"); + + spawn ram::RamModel< + TEST_INPUT_RAM_DATA_W, + TEST_INPUT_RAM_SIZE, + TEST_INPUT_RAM_WORD_PARTITION_SIZE, + TEST_INPUT_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_INPUT_RAM_INITIALIZED, + TEST_INPUT_RAM_ASSERT_VALID_READ, + TEST_INPUT_RAM_ADDR_W, + TEST_INPUT_RAM_NUM_PARTITIONS, + >(input1_rd_req_r, input1_rd_resp_s, input1_wr_req_r, input1_wr_resp_s); + + let (fl_axi_ar_s, fl_axi_ar_r) = chan("fl_axi_ar"); + let (fl_axi_r_s, fl_axi_r_r) = chan("fl_axi_r"); + + spawn axi_ram::AxiRamReader< + TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, + TEST_INPUT_RAM_SIZE + >( + fl_axi_ar_r, fl_axi_r_s, + input1_rd_req_s, input1_rd_resp_r, + ); + + let (input2_rd_req_s, input2_rd_req_r) = chan("input_rd_req"); + let (input2_rd_resp_s, input2_rd_resp_r) = chan("input_rd_resp"); + let (input2_wr_req_s, input2_wr_req_r) = chan("input_wr_req"); + let (input2_wr_resp_s, input2_wr_resp_r) = chan("input_wr_resp"); + + spawn ram::RamModel< + TEST_INPUT_RAM_DATA_W, + TEST_INPUT_RAM_SIZE, + TEST_INPUT_RAM_WORD_PARTITION_SIZE, + TEST_INPUT_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_INPUT_RAM_INITIALIZED, + TEST_INPUT_RAM_ASSERT_VALID_READ, + TEST_INPUT_RAM_ADDR_W, + TEST_INPUT_RAM_NUM_PARTITIONS, + >(input2_rd_req_r, input2_rd_resp_s, input2_wr_req_r, input2_wr_resp_s); + + + let (fd_axi_ar_s, fd_axi_ar_r) = chan("fd_axi_ar"); + let (fd_axi_r_s, fd_axi_r_r) = chan("fd_axi_r"); + + spawn axi_ram::AxiRamReader< + TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, + TEST_INPUT_RAM_SIZE + >( + fd_axi_ar_r, fd_axi_r_s, + input2_rd_req_s, input2_rd_resp_r, + ); + + // Sequence Decoder + + let (req_s, req_r) = chan("req"); + let (resp_s, resp_r) = chan("resp"); + + let (fd_command_s, fd_command_r) = chan("fd_command"); + + spawn SequenceDecoder< + TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, + TEST_DPD_RAM_ADDR_W, TEST_DPD_RAM_DATA_W, TEST_DPD_RAM_NUM_PARTITIONS, + TEST_TMP_RAM_ADDR_W, TEST_TMP_RAM_DATA_W, TEST_TMP_RAM_NUM_PARTITIONS, + TEST_FSE_RAM_ADDR_W, TEST_FSE_RAM_DATA_W, TEST_FSE_RAM_NUM_PARTITIONS, + > ( + ss_axi_ar_s, ss_axi_r_r, + fl_axi_ar_s, fl_axi_r_r, + fd_axi_ar_s, fd_axi_r_r, + + req_r, resp_s, + fd_command_s, + + dpd_rd_req_s, dpd_rd_resp_r, dpd_wr_req_s, dpd_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + + ll_def_fse_rd_req_s, ll_def_fse_rd_resp_r, ll_def_fse_wr_req_s, ll_def_fse_wr_resp_r, + ll_fse_rd_req_s, ll_fse_rd_resp_r, ll_fse_wr_req_s, ll_fse_wr_resp_r, + + ml_def_fse_rd_req_s, ml_def_fse_rd_resp_r, ml_def_fse_wr_req_s, ml_def_fse_wr_resp_r, + ml_fse_rd_req_s, ml_fse_rd_resp_r, ml_fse_wr_req_s, ml_fse_wr_resp_r, + + of_def_fse_rd_req_s, of_def_fse_rd_resp_r, of_def_fse_wr_req_s, of_def_fse_wr_resp_r, + of_fse_rd_req_s, of_fse_rd_resp_r, of_fse_wr_req_s, of_fse_wr_resp_r, + ); + + ( + terminator, + req_s, resp_r, + fd_command_r, + + input0_rd_req_s, input0_rd_resp_r, input0_wr_req_s, input0_wr_resp_r, + input1_rd_req_s, input1_rd_resp_r, input1_wr_req_s, input1_wr_resp_r, + input2_rd_req_s, input2_rd_resp_r, input2_wr_req_s, input2_wr_resp_r, + + ll_sel_test_s, + ll_def_test_rd_req_s, ll_def_test_rd_resp_r, ll_def_test_wr_req_s, ll_def_test_wr_resp_r, + + ml_sel_test_s, + ml_def_test_rd_req_s, ml_def_test_rd_resp_r, ml_def_test_wr_req_s, ml_def_test_wr_resp_r, + + of_sel_test_s, + of_def_test_rd_req_s, of_def_test_rd_resp_r, of_def_test_wr_req_s, of_def_test_wr_resp_r, + ) + } + + next(state: ()) { + let tok = join(); + + // FILL THE TEST DATA + let tok = unroll_for! (i, tok): (u32, token) in range(u32:0, array_size(TEST_RAM_DATA)) { + let req = InputRamWrReq { + addr: i as InputAddr, + data: TEST_RAM_DATA[i] as InputData, + mask: !InputMask:0, + }; + let tok = send(tok, input0_wr_req_s, req); + let (tok, _) = recv(tok, input0_wr_resp_r); + let tok = send(tok, input1_wr_req_s, req); + let (tok, _) = recv(tok, input1_wr_resp_r); + let tok = send(tok, input2_wr_req_s, req); + let (tok, _) = recv(tok, input2_wr_resp_r); + tok + }(tok); + + // FILL THE LL DEFAULT RAM + let tok = send(tok, ll_sel_test_s, u1:0); + let tok = unroll_for! (i, tok): (u32, token) in range(u32:0, array_size(DEFAULT_LL_TABLE)) { + let req = FseRamWrReq { + addr: i as FseAddr, + data: fse_table_creator::fse_record_to_bits(DEFAULT_LL_TABLE[i]), + mask: !FseMask:0, + }; + let tok = send(tok, ll_def_test_wr_req_s, req); + let (tok, _) = recv(tok, ll_def_test_wr_resp_r); + tok + }(tok); + let tok = send(tok, ll_sel_test_s, u1:1); + + // FILL THE OF DEFAULT RAM + let tok = send(tok, of_sel_test_s, u1:0); + let tok = unroll_for! (i, tok): (u32, token) in range(u32:0, array_size(DEFAULT_OF_TABLE)) { + let req = FseRamWrReq { + addr: i as FseAddr, + data: fse_table_creator::fse_record_to_bits(DEFAULT_OF_TABLE[i]), + mask: !FseMask:0, + }; + let tok = send(tok, of_def_test_wr_req_s, req); + let (tok, _) = recv(tok, of_def_test_wr_resp_r); + tok + }(tok); + let tok = send(tok, of_sel_test_s, u1:1); + + // FILL THE ML DEFAULT RAM + let tok = send(tok, ml_sel_test_s, u1:0); + let tok = unroll_for! (i, tok): (u32, token) in range(u32:0, array_size(DEFAULT_ML_TABLE)) { + let req = FseRamWrReq { + addr: i as FseAddr, + data: fse_table_creator::fse_record_to_bits(DEFAULT_ML_TABLE[i]), + mask: !FseMask:0, + }; + let tok = send(tok, ml_def_test_wr_req_s, req); + let (tok, _) = recv(tok, ml_def_test_wr_resp_r); + tok + }(tok); + let tok = send(tok, ml_sel_test_s, u1:1); + + // START DECODING + let tok = send(tok, req_s, Req { + sync: BlockSyncData { + id: u32:0, + last_block: false, + }, + start_addr: uN[TEST_AXI_ADDR_W]:0x100, + end_addr: uN[TEST_AXI_ADDR_W]:0x120, + literals_count: u20:24, + }); + + let tok = for ((i, output), tok): ((u32, SequenceExecutorPacket), token) in enumerate(EXPECTED_OUTPUT) { + let (tok, recv_output) = recv(tok, fd_command_r); + trace_fmt!("[{}]: Expected: {:#x}\nGot: {:#x}\n", i, output, recv_output); + assert_eq(output, recv_output.data); + tok + }(tok); + + let (tok, _) = recv(tok, resp_r); + + // START DECODING - ask for more literals - expecting additional empty output packet with + // last set + let tok = send(tok, req_s, Req { + sync: BlockSyncData { + id: u32:0, + last_block: false, + }, + start_addr: uN[TEST_AXI_ADDR_W]:0x100, + end_addr: uN[TEST_AXI_ADDR_W]:0x120, + literals_count: u20:26, + }); + + // Don't read the last output packet from the expected output array + let tok = for (i, tok): (u32, token) in u32:0..(array_size(EXPECTED_OUTPUT) - u32:1) { + let output = EXPECTED_OUTPUT[i]; + let (tok, recv_output) = recv(tok, fd_command_r); + trace_fmt!("[{}]: Expected: {:#x}\nGot: {:#x}\n", i, output, recv_output); + assert_eq(output, recv_output.data); + tok + }(tok); + + // The last packet from the expected output array is now expected to have last not set + let expected = SequenceExecutorPacket { + last: false, + ..EXPECTED_OUTPUT[array_size(EXPECTED_OUTPUT) - u32:1] + }; + let (tok, recv_output) = recv(tok, fd_command_r); + trace_fmt!("[LAST-1]: Expected: {:#x}\nGot: {:#x}\n", expected, recv_output); + assert_eq(expected, recv_output.data); + + // This is the actual last output packet + let expected = SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x2, + content: u64:0x0, + last: true, + }; + let (tok, recv_output) = recv(tok, fd_command_r); + trace_fmt!("[LAST]: Expected: {:#x}\nGot: {:#x}\n", expected, recv_output); + assert_eq(expected, recv_output.data); + + let (tok, _) = recv(tok, resp_r); + + send(tok, terminator, true); + } + +} From aedd11692b265cfbd15a8f293d8e1593d7ce46fc Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 3 Dec 2024 16:12:33 +0100 Subject: [PATCH 44/85] modules/zstd: Rework literals decoding to memory-based architecture Internal-tag: [#69203] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 271 ++- xls/modules/zstd/common.x | 12 +- xls/modules/zstd/huffman_axi_reader.x | 134 +- xls/modules/zstd/huffman_code_builder.x | 8 +- xls/modules/zstd/huffman_ctrl.x | 476 ++++- xls/modules/zstd/huffman_data_preprocessor.x | 2 + xls/modules/zstd/huffman_decoder.x | 96 +- xls/modules/zstd/huffman_literals_dec.x | 1107 +++++++---- xls/modules/zstd/huffman_weights_dec.x | 1016 ++++++++++ xls/modules/zstd/literals_block_header_dec.x | 176 +- xls/modules/zstd/literals_buffer.x | 162 +- xls/modules/zstd/literals_decoder.x | 1795 +++++++++++++++--- xls/modules/zstd/literals_dispatcher.x | 243 --- xls/modules/zstd/memory/axi_ram.x | 8 +- xls/modules/zstd/raw_literals_dec.x | 279 ++- xls/modules/zstd/rle_literals_dec.x | 516 ++--- 16 files changed, 4725 insertions(+), 1576 deletions(-) create mode 100644 xls/modules/zstd/huffman_weights_dec.x delete mode 100644 xls/modules/zstd/literals_dispatcher.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index bf69578959..81826fe9a4 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1953,88 +1953,6 @@ xls_benchmark_ir( tags = ["manual"], ) -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 = 64, - die_width_microns = 64, - 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", -) - xls_dslx_library( name = "rle_literals_dec_dslx", srcs = [ @@ -2042,8 +1960,6 @@ xls_dslx_library( ], deps = [ ":common_dslx", - "//xls/modules/rle:rle_common_dslx", - "//xls/modules/rle:rle_dec_dslx", ], ) @@ -2062,13 +1978,14 @@ xls_dslx_verilog( "worst_case_throughput": "1", "use_system_verilog": "false", }, - dslx_top = "RleLiteralsDecoder", + dslx_top = "RleLiteralsDecoderInst", library = ":rle_literals_dec_dslx", opt_ir_args = { "inline_procs": "true", - "top": "__rle_literals_dec__RleLiteralsDecoder__BatchPacker_0_next", + "top": "__rle_literals_dec__RleLiteralsDecoderInst__RleLiteralsDecoder_0__64_next", }, verilog_file = "rle_literals_dec.v", + tags = ["manual"], ) xls_benchmark_ir( @@ -2117,6 +2034,7 @@ xls_dslx_library( srcs = ["raw_literals_dec.x"], deps = [ ":common_dslx", + "//xls/modules/zstd/memory:mem_reader_dslx", ], ) @@ -2134,9 +2052,13 @@ xls_dslx_verilog( "reset": "rst", "use_system_verilog": "false", }, - dslx_top = "RawLiteralsDecoder", + opt_ir_args = { + "top": "__raw_literals_dec__RawLiteralsDecoderInst__RawLiteralsDecoder_0__16_64_next" + }, + dslx_top = "RawLiteralsDecoderInst", library = ":raw_literals_dec_dslx", verilog_file = "raw_literals_dec.v", + tags = ["manual"], ) xls_benchmark_ir( @@ -2145,12 +2067,15 @@ xls_benchmark_ir( benchmark_ir_args = { "pipeline_stages": "10", "delay_model": "asap7", + "top": "__raw_literals_dec__RawLiteralsDecoderInst__RawLiteralsDecoder_0__16_64_next" }, + tags = ["manual"], ) xls_benchmark_verilog( name = "raw_literals_dec_verilog_benchmark", verilog_target = "raw_literals_dec_verilog", + tags = ["manual"], ) verilog_library( @@ -2167,22 +2092,25 @@ synthesize_rtl( deps = [ ":raw_literals_dec_lib", ], + tags = ["manual"], ) benchmark_synth( name = "raw_literals_dec_benchmark_synth", synth_target = ":raw_literals_dec_asap7", + tags = ["manual"], ) place_and_route( name = "raw_literals_dec_place_and_route", clock_period = "750", core_padding_microns = 2, - min_pin_distance = "0.09", + min_pin_distance = "0.5", placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":raw_literals_dec_asap7", target_die_utilization_percentage = "10", + tags = ["manual"], ) xls_dslx_library( @@ -2286,13 +2214,17 @@ xls_dslx_library( ], deps = [ "//xls/examples:ram_dslx", + "//xls/modules/zstd/memory:axi_dslx", + "//xls/modules/zstd/memory:axi_ram_dslx", + "//xls/modules/zstd/memory:mem_reader_dslx", ":common_dslx", ":literals_buffer_dslx", - ":literals_dispatcher_dslx", + ":literals_block_header_dec_dslx", ":parallel_rams_dslx", ":ram_printer_dslx", ":raw_literals_dec_dslx", ":rle_literals_dec_dslx", + ":huffman_literals_dec_dslx", ], ) @@ -2301,6 +2233,64 @@ xls_dslx_test( library = ":literals_decoder_dslx", ) +literals_decoder_ctrl_codegen_args = common_codegen_args | { + "module_name": "LiteralsDecoderCtrl", + "pipeline_stages": "10", +} + +xls_dslx_verilog( + name = "literals_decoder_ctrl_verilog", + codegen_args = literals_decoder_ctrl_codegen_args, + opt_ir_args = { + "inline_procs": "false", + }, + dslx_top = "LiteralsDecoderCtrlInst", + library = ":literals_decoder_dslx", + verilog_file = "literals_decoder_ctrl.v", + tags = ["manual"], +) + +xls_benchmark_ir( + name = "literals_decoder_ctrl_opt_ir_benchmark", + src = ":literals_decoder_ctrl_verilog.opt.ir", + benchmark_ir_args = literals_decoder_ctrl_codegen_args | { + "multi_proc": "true", + "inline_procs": "false", + }, +) + +verilog_library( + name = "literals_decoder_ctrl_verilog_lib", + srcs = [ + ":literals_decoder_ctrl.v", + ], +) + +synthesize_rtl( + name = "literals_decoder_ctrl_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "LiteralsDecoderCtrl", + deps = [ + ":literals_decoder_ctrl_verilog_lib", + ], +) + +benchmark_synth( + name = "literals_decoder_ctrl_benchmark_synth", + synth_target = ":literals_decoder_ctrl_synth_asap7", +) + +place_and_route( + name = "literals_decoder_ctrl_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_ctrl_synth_asap7", + target_die_utilization_percentage = "10", +) + xls_dslx_verilog( name = "literals_decoder_verilog", codegen_args = { @@ -2319,7 +2309,7 @@ xls_dslx_verilog( ]), "pipeline_stages": "8", "reset": "rst", - "worst_case_throughput": "1", + "worst_case_throughput": "2", "use_system_verilog": "false", }, dslx_top = "LiteralsDecoderInst", @@ -2336,13 +2326,18 @@ xls_benchmark_ir( src = ":literals_decoder_verilog.opt.ir", benchmark_ir_args = { "pipeline_stages": "10", + "worst_case_throughput": "2", "delay_model": "asap7", + "inline_procs": "true", + "top": "__xls_modules_zstd_literals_buffer__LiteralsDecoderInst__LiteralsDecoder_0__LiteralsBuffer_0__LiteralsBufferReader_0__64_0_0_0_13_8192_65536_next", }, + tags = ["manual"], ) xls_benchmark_verilog( name = "literals_decoder_verilog_benchmark", verilog_target = "literals_decoder_verilog", + tags = ["manual"], ) verilog_library( @@ -2553,6 +2548,7 @@ xls_dslx_library( ], deps = [ "//xls/modules/zstd/memory:axi_dslx", + "//xls/modules/zstd/memory:mem_reader_dslx", ], ) @@ -2566,7 +2562,7 @@ huffman_axi_reader_codegen_args = common_codegen_args | { "module_name": "HuffmanAxiReader", "pipeline_stages": "8", "clock_period_ps": "750", - "worst_case_throughput": "2", + "worst_case_throughput": "4", } xls_dslx_verilog( @@ -2576,7 +2572,7 @@ xls_dslx_verilog( library = ":huffman_axi_reader_dslx", opt_ir_args = { "inline_procs": "true", - "top": "__huffman_axi_reader__HuffmanAxiReaderInst__HuffmanAxiReader_0__32_32_32_next", + "top": "__huffman_axi_reader__HuffmanAxiReaderInst__HuffmanAxiReader_0__16_8_3_64_4_4_next", }, tags = ["manual"], verilog_file = "huffman_axi_reader.v", @@ -2801,6 +2797,7 @@ xls_dslx_library( ":huffman_data_preprocessor_dslx", ":huffman_decoder_dslx", ":huffman_prescan_dslx", + ":huffman_weights_dec_dslx", ], ) @@ -2812,9 +2809,9 @@ xls_dslx_test( huffman_ctrl_codegen_args = common_codegen_args | { "module_name": "HuffmanCtrl", - "pipeline_stages": "4", + "pipeline_stages": "8", "clock_period_ps": "750", - "worst_case_throughput": "1", + "worst_case_throughput": "2", } xls_dslx_verilog( @@ -2824,7 +2821,7 @@ xls_dslx_verilog( library = ":huffman_ctrl_dslx", opt_ir_args = { "inline_procs": "true", - "top": "__huffman_ctrl__HuffmanControlAndSequenceInst__HuffmanControlAndSequence_0__32_next", + "top": "__huffman_ctrl__HuffmanControlAndSequenceInst__HuffmanControlAndSequence_0__32_64_next", }, tags = ["manual"], verilog_file = "huffman_ctrl.v", @@ -2873,12 +2870,96 @@ place_and_route( target_die_utilization_percentage = "10", ) +xls_dslx_library( + name = "huffman_weights_dec_dslx", + srcs = [ + "huffman_weights_dec.x", + ], + deps = [ + "//xls/modules/zstd:huffman_prescan_dslx", + "//xls/modules/zstd/memory:mem_reader_dslx", + "//xls/modules/zstd/memory:axi_ram_dslx", + "//xls/examples:ram_dslx", + ":ram_mux_dslx", + ], +) + +xls_dslx_test( + name = "huffman_weights_dec_dslx_test", + library = ":huffman_weights_dec_dslx", + tags = ["manual"], +) + +huffman_weights_dec_codegen_args = common_codegen_args | { + "module_name": "HuffmanWeightsDecoder", + "pipeline_stages": "25", + "clock_period_ps": "750", + "worst_case_throughput": "17", +} + +xls_dslx_verilog( + name = "huffman_weights_dec_verilog", + codegen_args = huffman_weights_dec_codegen_args, + dslx_top = "HuffmanWeightsDecoderInst", + library = ":huffman_weights_dec_dslx", + opt_ir_args = { + "inline_procs": "true", + "top": "__huffman_weights_dec__HuffmanWeightsDecoderInst__HuffmanWeightsDecoder_0__32_64_6_32_8_next", + }, + tags = ["manual"], + verilog_file = "huffman_weights_dec.v", +) + +xls_benchmark_ir( + name = "huffman_weights_dec_opt_ir_benchmark", + src = ":huffman_weights_dec_verilog.opt.ir", + benchmark_ir_args = huffman_weights_dec_codegen_args, + tags = ["manual"], +) + +verilog_library( + name = "huffman_weights_dec_verilog_lib", + srcs = [ + ":huffman_weights_dec.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "huffman_weights_dec_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "HuffmanWeightsDecoder", + deps = [ + ":huffman_weights_dec_verilog_lib", + ], +) + +benchmark_synth( + name = "huffman_weights_dec_benchmark_synth", + synth_target = ":huffman_weights_dec_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "huffman_weights_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 = ":huffman_weights_dec_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + xls_dslx_library( name = "huffman_literals_dec_dslx", srcs = [ "huffman_literals_dec.x", ], deps = [ + "//xls/modules/zstd/memory:axi_ram_dslx", ":common_dslx", ":huffman_axi_reader_dslx", ":huffman_code_builder_dslx", diff --git a/xls/modules/zstd/common.x b/xls/modules/zstd/common.x index 68ff6bf81a..725d74a713 100644 --- a/xls/modules/zstd/common.x +++ b/xls/modules/zstd/common.x @@ -62,6 +62,7 @@ pub struct ExtendedBlockDataPacket { pub struct SequenceExecutorPacket { msg_type: SequenceExecutorMessageType, + // TODO: this should be max(8, clog2(maximum match value)) length: CopyOrMatchLength, // Literal length or match length content: uN[DATA_W * u32:8], // Literal data or match offset last: bool, // Last packet in frame @@ -277,13 +278,6 @@ pub struct LiteralsPathCtrl { literals_type: LiteralType, } -pub struct RleLiteralsData { - data: RleLitData, - repeat: RleLitRepeat, - last: bool, - id: LitID, -} - pub struct LiteralsData { data: LitData, length: LitLength, @@ -293,9 +287,9 @@ pub struct LiteralsData { pub struct LiteralsDataWithSync { data: LitData, length: LitLength, - last: bool, + last: bool, // last packet in single literals section decoding id: LitID, - literals_last: bool, + literals_last: bool, // last literals section in ZSTD frame } pub struct LiteralsBufferCtrl { diff --git a/xls/modules/zstd/huffman_axi_reader.x b/xls/modules/zstd/huffman_axi_reader.x index 507008cefa..5166789228 100644 --- a/xls/modules/zstd/huffman_axi_reader.x +++ b/xls/modules/zstd/huffman_axi_reader.x @@ -14,7 +14,9 @@ // This file contains the implementation of Huffmann data preprocessor. +import std; import xls.modules.zstd.memory.axi as axi; +import xls.modules.zstd.memory.mem_reader as mem_reader; pub struct HuffmanAxiReaderCtrl { base_addr: uN[AXI_ADDR_W], @@ -26,28 +28,33 @@ pub struct HuffmanAxiReaderData { last: bool, } -struct HuffmanAxiReaderState { +struct HuffmanAxiReaderState { ctrl: HuffmanAxiReaderCtrl, bytes_requested: uN[AXI_ADDR_W], bytes_sent: uN[AXI_ADDR_W], } -pub proc HuffmanAxiReader { - // FIXME: Replace hard-coded values with proc params AXI_DATA_W, AXI_ID_W - type AxiR = axi::AxiR; - // FIXME: Replace hard-coded values with proc params AXI_ADDR_W, AXI_ID_W - type AxiAr = axi::AxiAr; +pub proc HuffmanAxiReader { + type AxiAr = axi::AxiAr; + type AxiR = axi::AxiR; - // FIXME: Replace hard-coded values with proc params AXI_ADDR_W - type Ctrl = HuffmanAxiReaderCtrl; + type Ctrl = HuffmanAxiReaderCtrl; type Data = HuffmanAxiReaderData; - // FIXME: Replace hard-coded values with proc params AXI_DATA_W, AXI_ADDR_W - type State = HuffmanAxiReaderState; + type MemRdReq = mem_reader::MemReaderReq; + type MemRdResp = mem_reader::MemReaderResp; + + type State = HuffmanAxiReaderState; ctrl_r: chan in; - axi_r_r: chan in; - axi_ar_s: chan out; + mem_rd_req_s: chan out; + mem_rd_resp_r: chan in; data_s: chan out; config ( @@ -56,10 +63,20 @@ pub proc HuffmanAxiReader { axi_ar_s: chan out, data_s: chan out, ) { + let (mem_rd_req_s, mem_rd_req_r) = chan("mem_rd_req"); + let (mem_rd_resp_s, mem_rd_resp_r) = chan("mem_rd_resp"); + + spawn mem_reader::MemReader ( + mem_rd_req_r, + mem_rd_resp_s, + axi_ar_s, + axi_r_r + ); + ( ctrl_r, - axi_r_r, - axi_ar_s, + mem_rd_req_s, + mem_rd_resp_r, data_s, ) } @@ -83,18 +100,17 @@ pub proc HuffmanAxiReader { // send AXI read request // this could be optimized to read multiple bytes per AXI transaction let addr = state.ctrl.base_addr + state.ctrl.len - uN[AXI_ADDR_W]:1 - state.bytes_requested; - let axi_ar = AxiAr { - id: uN[AXI_ID_W]:0, + let mem_rd_req = MemRdReq { addr: addr, - ..zero!() + length: uN[AXI_ADDR_W]:1 }; - let do_send_axi_req = (state.bytes_requested < state.ctrl.len); - send_if(join(), axi_ar_s, do_send_axi_req, axi_ar); - if (do_send_axi_req) { - trace_fmt!("Sent AXI read request {:#x}", axi_ar); + let do_send_mem_rd_req = (state.bytes_requested < state.ctrl.len); + send_if(join(), mem_rd_req_s, do_send_mem_rd_req, mem_rd_req); + if (do_send_mem_rd_req) { + trace_fmt!("Sent memory read request {:#x}", mem_rd_req); } else {}; - let state = if do_send_axi_req { + let state = if do_send_mem_rd_req { State { bytes_requested: state.bytes_requested + uN[AXI_ADDR_W]:1, ..state @@ -103,20 +119,27 @@ pub proc HuffmanAxiReader { state }; - // receive data from AXI - let do_read_axi_resp = (state.bytes_requested > state.bytes_sent) && (state.bytes_sent < state.bytes_requested); - let (tok, axi_r, axi_r_valid) = recv_if_non_blocking(join(), axi_r_r, do_read_axi_resp, zero!()); + // receive data + let do_read_mem_rd_resp = (state.bytes_requested > state.bytes_sent) && (state.bytes_sent < state.bytes_requested); + let (tok, mem_rd_resp, mem_rd_resp_valid) = recv_if_non_blocking(join(), mem_rd_resp_r, do_read_mem_rd_resp, zero!()); + if mem_rd_resp_valid { + trace_fmt!("Received memory read response {:#x}", mem_rd_resp); + } else {}; // send data - let last = axi_r_valid && ((state.bytes_sent + uN[AXI_ADDR_W]:1) == state.ctrl.len); - let tok = send_if(tok, data_s, axi_r_valid, Data { - data: axi_r.data as u8, + let last = mem_rd_resp_valid && ((state.bytes_sent + uN[AXI_ADDR_W]:1) == state.ctrl.len); + let data = Data { + data: mem_rd_resp.data as u8, last: last, - }); + }; + let tok = send_if(tok, data_s, mem_rd_resp_valid, data); + if mem_rd_resp_valid { + trace_fmt!("Sent output data {:#x}", data); + } else {}; + let state = if last { zero!() - } else if axi_r_valid { - trace_fmt!("Received AXI read response {:#x}", axi_r); + } else if mem_rd_resp_valid { State { bytes_sent: state.bytes_sent + uN[AXI_ADDR_W]:1, ..state @@ -127,9 +150,10 @@ pub proc HuffmanAxiReader { } } -const INST_AXI_DATA_W = u32:32; -const INST_AXI_ADDR_W = u32:32; -const INST_AXI_ID_W = u32:32; +const INST_AXI_DATA_W = u32:64; +const INST_AXI_ADDR_W = u32:16; +const INST_AXI_ID_W = u32:4; +const INST_AXI_DEST_W = u32:4; proc HuffmanAxiReaderInst { type InstHuffmanAxiReaderCtrl = HuffmanAxiReaderCtrl; @@ -143,7 +167,7 @@ proc HuffmanAxiReaderInst { axi_ar_s: chan out, data_s: chan out, ) { - spawn HuffmanAxiReader( + spawn HuffmanAxiReader( ctrl_r, axi_r_r, axi_ar_s, @@ -156,9 +180,12 @@ proc HuffmanAxiReaderInst { next (state: ()) { } } -const TEST_AXI_DATA_W = u32:32; -const TEST_AXI_ADDR_W = u32:32; -const TEST_AXI_ID_W = u32:32; +const TEST_AXI_DATA_W = u32:64; +const TEST_AXI_ADDR_W = u32:16; +const TEST_AXI_ID_W = u32:4; +const TEST_AXI_DEST_W = u32:4; +const TEST_AXI_DATA_DIV8 = TEST_AXI_DATA_W / u32:8; +const TEST_AXI_DATA_DIV8_W = std::clog2(TEST_AXI_DATA_DIV8); type TestHuffmanAxiReaderCtrl = HuffmanAxiReaderCtrl; @@ -188,23 +215,23 @@ const TEST_DATA_CTRL = TestHuffmanAxiReaderCtrl[3]:[ ]; const TEST_DATA_AXI = TestAxiData[7]:[ - TestAxiData { addr: uN[TEST_AXI_ADDR_W]:0, data: uN[TEST_AXI_DATA_W]:0x12, len: u8:0, last: true, }, - TestAxiData { addr: uN[TEST_AXI_ADDR_W]:131, data: uN[TEST_AXI_DATA_W]:0xAA, len: u8:0, last: true, }, - TestAxiData { addr: uN[TEST_AXI_ADDR_W]:130, data: uN[TEST_AXI_DATA_W]:0xBB, len: u8:0, last: true, }, - TestAxiData { addr: uN[TEST_AXI_ADDR_W]:129, data: uN[TEST_AXI_DATA_W]:0xCC, len: u8:0, last: true, }, - TestAxiData { addr: uN[TEST_AXI_ADDR_W]:128, data: uN[TEST_AXI_DATA_W]:0xDD, len: u8:0, last: true, }, - TestAxiData { addr: uN[TEST_AXI_ADDR_W]:65, data: uN[TEST_AXI_DATA_W]:0x44, len: u8:0, last: false, }, - TestAxiData { addr: uN[TEST_AXI_ADDR_W]:64, data: uN[TEST_AXI_DATA_W]:0x55, len: u8:0, last: true, }, + TestAxiData { addr: uN[TEST_AXI_ADDR_W]:0, data: uN[TEST_AXI_DATA_W]:0x0123456789ABCDF0, len: u8:0, last: true, }, + TestAxiData { addr: uN[TEST_AXI_ADDR_W]:131, data: uN[TEST_AXI_DATA_W]:0x8899AABBCCDDEEFF, len: u8:0, last: true, }, + TestAxiData { addr: uN[TEST_AXI_ADDR_W]:130, data: uN[TEST_AXI_DATA_W]:0x8899AABBCCDDEEFF, len: u8:0, last: true, }, + TestAxiData { addr: uN[TEST_AXI_ADDR_W]:129, data: uN[TEST_AXI_DATA_W]:0x8899AABBCCDDEEFF, len: u8:0, last: true, }, + TestAxiData { addr: uN[TEST_AXI_ADDR_W]:128, data: uN[TEST_AXI_DATA_W]:0x8899AABBCCDDEEFF, len: u8:0, last: true, }, + TestAxiData { addr: uN[TEST_AXI_ADDR_W]:65, data: uN[TEST_AXI_DATA_W]:0xDEADBEEFFEEBDAED, len: u8:0, last: false, }, + TestAxiData { addr: uN[TEST_AXI_ADDR_W]:64, data: uN[TEST_AXI_DATA_W]:0xDEADBEEFFEEBDAED, len: u8:0, last: true, }, ]; const TEST_DATA_OUT = HuffmanAxiReaderData[7]:[ - HuffmanAxiReaderData { data: u8:0x12, last: true, }, - HuffmanAxiReaderData { data: u8:0xAA, last: false, }, - HuffmanAxiReaderData { data: u8:0xBB, last: false, }, + HuffmanAxiReaderData { data: u8:0xF0, last: true, }, HuffmanAxiReaderData { data: u8:0xCC, last: false, }, - HuffmanAxiReaderData { data: u8:0xDD, last: true, }, - HuffmanAxiReaderData { data: u8:0x44, last: false, }, - HuffmanAxiReaderData { data: u8:0x55, last: true, }, + HuffmanAxiReaderData { data: u8:0xDD, last: false, }, + HuffmanAxiReaderData { data: u8:0xEE, last: false, }, + HuffmanAxiReaderData { data: u8:0xFF, last: true, }, + HuffmanAxiReaderData { data: u8:0xDA, last: false, }, + HuffmanAxiReaderData { data: u8:0xED, last: true, }, ]; #[test_proc] @@ -222,7 +249,7 @@ proc HuffmanAxiReader_test { let (axi_ar_s, axi_ar_r) = chan("axi_ar"); let (data_s, data_r) = chan("data"); - spawn HuffmanAxiReader ( + spawn HuffmanAxiReader ( ctrl_r, axi_r_r, axi_ar_s, @@ -251,8 +278,9 @@ proc HuffmanAxiReader_test { let tok = for ((i, test_axi), tok): ((u32, TestAxiData), token) in enumerate(TEST_DATA_AXI) { let (tok, axi_req) = recv(tok, axi_ar_r); trace_fmt!("Received #{} AXI request {:#x}", i + u32:1, axi_req); + let aligned_addr = test_axi.addr & !(test_axi.addr % TEST_AXI_DATA_DIV8 as uN[TEST_AXI_ADDR_W]); - assert_eq(test_axi.addr, axi_req.addr); + assert_eq(aligned_addr, axi_req.addr); assert_eq(test_axi.len, axi_req.len); let axi_resp = TestAxiR { diff --git a/xls/modules/zstd/huffman_code_builder.x b/xls/modules/zstd/huffman_code_builder.x index 4b554779c1..226a6ad2d8 100644 --- a/xls/modules/zstd/huffman_code_builder.x +++ b/xls/modules/zstd/huffman_code_builder.x @@ -288,16 +288,16 @@ pub proc WeightCodeBuilder update(codes, i, code) }(zero!()); - if send_codes { - trace_fmt!("{}\n{}\n{:#b}\n{:#b}", symbols_valid, codes_length, codes, state.huffman_codes); - } else {}; - let code_packet = DecoderOutput { symbol_valid: symbols_valid, code_length: codes_length, code: codes }; send_if(tok, codes_s, send_codes, code_packet); + if send_codes { + trace_fmt!("Sent codes: \nsymbols_valid: {}\ncodes_length: {}\ncodes: {:#b}\nstate.huffman_codes: {:#b}", symbols_valid, codes_length, codes, state.huffman_codes); + } else {}; + next_state } diff --git a/xls/modules/zstd/huffman_ctrl.x b/xls/modules/zstd/huffman_ctrl.x index d5ac04d427..036a765525 100644 --- a/xls/modules/zstd/huffman_ctrl.x +++ b/xls/modules/zstd/huffman_ctrl.x @@ -15,12 +15,14 @@ // This file contains Huffman decoder control and sequence proc implementation. import xls.modules.zstd.common as common; +import xls.modules.zstd.memory.mem_reader as mem_reader; import xls.modules.zstd.huffman_common as hcommon; import xls.modules.zstd.huffman_axi_reader as axi_reader; import xls.modules.zstd.huffman_code_builder as code_builder; import xls.modules.zstd.huffman_data_preprocessor as data_preprocessor; import xls.modules.zstd.huffman_decoder as decoder; import xls.modules.zstd.huffman_prescan as prescan; +import xls.modules.zstd.huffman_weights_dec as weights_dec; enum HuffmanControlAndSequenceFSM: u2 { @@ -32,22 +34,57 @@ pub struct HuffmanControlAndSequenceCtrl { base_addr: uN[AXI_ADDR_W], len: uN[AXI_ADDR_W], new_config: bool, + multi_stream: bool, + id: u32, + literals_last: bool, } -struct HuffmanControlAndSequenceState { +pub enum HuffmanControlAndSequenceStatus: u1 { + OKAY = 0, + ERROR = 1, +} + +pub struct HuffmanControlAndSequenceResp { + status: HuffmanControlAndSequenceStatus +} + +struct HuffmanControlAndSequenceState { fsm: HuffmanControlAndSequenceFSM, + weights_dec_pending: bool, + stream_dec_pending: bool, + multi_stream_dec_pending: bool, + multi_stream_decodings_finished: u3, + jump_table_dec_pending: bool, + jump_table_req_sent: bool, + tree_description_size: uN[AXI_ADDR_W], + ctrl: HuffmanControlAndSequenceCtrl, + stream_sizes: uN[AXI_ADDR_W][4], } -pub proc HuffmanControlAndSequence { +const JUMP_TABLE_SIZE = u32:6; + +pub proc HuffmanControlAndSequence { type AxiReaderCtrl = axi_reader::HuffmanAxiReaderCtrl; type DataPreprocessorStart = data_preprocessor::HuffmanDataPreprocessorStart; type DecoderStart = decoder::HuffmanDecoderStart; + type WeightsDecReq = weights_dec::HuffmanWeightsDecoderReq; + type WeightsDecResp = weights_dec::HuffmanWeightsDecoderResp; + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + - type State = HuffmanControlAndSequenceState; + type State = HuffmanControlAndSequenceState; type FSM = HuffmanControlAndSequenceFSM; type Ctrl = HuffmanControlAndSequenceCtrl; + type Resp = HuffmanControlAndSequenceResp; + type Status = HuffmanControlAndSequenceStatus; ctrl_r: chan in; + resp_s: chan out; + + // Huffman tree description decoder + weights_dec_req_s: chan out; + weights_dec_resp_r: chan in; // prescan prescan_start_s: chan out; @@ -65,23 +102,36 @@ pub proc HuffmanControlAndSequence { decoder_start_s: chan out; decoder_done_r: chan<()> in; + // MemReader interface for fetching the Jump Table + mem_rd_req_s: chan out; + mem_rd_resp_r: chan in; + + config ( ctrl_r: chan in, + resp_s: chan out, + weights_dec_req_s: chan out, + weights_dec_resp_r: chan in, prescan_start_s: chan out, code_builder_start_s: chan out, axi_reader_ctrl_s: chan out, data_preprocess_start_s: chan out, decoder_start_s: chan out, decoder_done_r: chan<()> in, + mem_rd_req_s: chan out, + mem_rd_resp_r: chan in, ) { ( - ctrl_r, + ctrl_r, resp_s, + weights_dec_req_s, + weights_dec_resp_r, prescan_start_s, code_builder_start_s, axi_reader_ctrl_s, data_preprocess_start_s, decoder_start_s, decoder_done_r, + mem_rd_req_s, mem_rd_resp_r, ) } @@ -92,10 +142,16 @@ pub proc HuffmanControlAndSequence { next (state: State) { // receive start let (tok, ctrl, ctrl_valid) = recv_if_non_blocking(join(), ctrl_r, state.fsm == FSM::IDLE, zero!()); + if (ctrl_valid) { trace_fmt!("Received Ctrl: {:#x}", ctrl); } else {}; let state = if ctrl_valid { State { fsm: FSM::DECODING, + ctrl: ctrl, + weights_dec_pending: ctrl.new_config, + multi_stream_dec_pending: ctrl.multi_stream, + jump_table_dec_pending: ctrl.multi_stream, + ..state } } else { state @@ -104,73 +160,204 @@ pub proc HuffmanControlAndSequence { // send start to prescan and code builder let new_config = ctrl_valid & ctrl.new_config; - if new_config { - trace_fmt!("Sending start to prescan and code builder"); + // New config means the requirement to read and decode new Huffman Tree Description + // Delegate this task to HuffmanWeightsDecoder + let weights_dec_req = WeightsDecReq { + addr: ctrl.base_addr + }; + send_if(tok, weights_dec_req_s, new_config, weights_dec_req); + if (new_config) { trace_fmt!("Sent Weights Decoding Request: {:#x}", weights_dec_req); } else {}; + + // recv response + let (tok, weights_dec_resp, weights_dec_resp_valid) = recv_if_non_blocking(tok, weights_dec_resp_r, state.weights_dec_pending, zero!()); + if (weights_dec_resp_valid) { trace_fmt!("Received Weights Decoding response: {:#x}", weights_dec_resp); } else {}; + let state = if weights_dec_resp_valid { + State { + weights_dec_pending: false, + tree_description_size: weights_dec_resp.tree_description_size, + ..state + } + } else { + state + }; + + // Fetch the Jump Table if neccessary + let jump_table_req = MemReaderReq { + addr: state.ctrl.base_addr + state.tree_description_size, + length: JUMP_TABLE_SIZE as uN[AXI_ADDR_W], + }; + let do_send_jump_table_req = !state.weights_dec_pending && state.jump_table_dec_pending && !state.jump_table_req_sent; + let tok = send_if(tok, mem_rd_req_s, do_send_jump_table_req, jump_table_req); + if do_send_jump_table_req { + trace_fmt!("Sent Jump Table read request {:#x}", jump_table_req); } else {}; - send_if(tok, prescan_start_s, new_config, true); - send_if(tok, code_builder_start_s, new_config, true); + let (tok, jump_table_raw, jump_table_valid) = recv_if_non_blocking(tok, mem_rd_resp_r, state.jump_table_dec_pending, zero!()); + let stream_sizes = jump_table_raw.data[0:48] as u16[3]; + let total_streams_size = state.ctrl.len - state.tree_description_size; + let stream_sizes = uN[AXI_ADDR_W][4]:[stream_sizes[0] as uN[AXI_ADDR_W], stream_sizes[1] as uN[AXI_ADDR_W], stream_sizes[2] as uN[AXI_ADDR_W], total_streams_size - JUMP_TABLE_SIZE as uN[AXI_ADDR_W] - (stream_sizes[0] + stream_sizes[1] + stream_sizes[2]) as uN[AXI_ADDR_W]]; + if jump_table_valid { + trace_fmt!("Received Jump Table: {:#x}", jump_table_raw); + trace_fmt!("total_streams_size: {:#x}", total_streams_size); + trace_fmt!("state.tree_description_size: {:#x}", state.tree_description_size); + trace_fmt!("stream sizes: {:#x}", stream_sizes); + } else {}; + let state = if do_send_jump_table_req { + State { + jump_table_req_sent: true, + ..state + } + } else if jump_table_valid { + State { + jump_table_dec_pending: false, + jump_table_req_sent: false, + stream_sizes: stream_sizes, + ..state + } + } else { + state + }; + + let start_decoding = ((state.fsm == FSM::DECODING) & (!state.weights_dec_pending) & (!state.stream_dec_pending) & (!state.jump_table_dec_pending) & ((!state.multi_stream_dec_pending) || (state.multi_stream_dec_pending && state.multi_stream_decodings_finished != u3:4))); + send_if(tok, prescan_start_s, start_decoding, true); + send_if(tok, code_builder_start_s, start_decoding, true); + if (start_decoding) { trace_fmt!("Sent START to prescan and code builder"); } else {}; + + let stream_sizes = state.stream_sizes; + let (huffman_stream_addr, huffman_stream_len) = match(state.ctrl.new_config, state.ctrl.multi_stream, state.multi_stream_decodings_finished) { + (false, false, _) => (state.ctrl.base_addr, state.ctrl.len), + (true, false, _) => ((state.ctrl.base_addr + state.tree_description_size), (state.ctrl.len - state.tree_description_size)), + + (false, true, u3:0) => ((state.ctrl.base_addr + JUMP_TABLE_SIZE as uN[AXI_ADDR_W]), (stream_sizes[0] as uN[AXI_ADDR_W])), + (false, true, u3:1) => ((state.ctrl.base_addr + JUMP_TABLE_SIZE as uN[AXI_ADDR_W] + stream_sizes[0]), (stream_sizes[1] as uN[AXI_ADDR_W])), + (false, true, u3:2) => ((state.ctrl.base_addr + JUMP_TABLE_SIZE as uN[AXI_ADDR_W] + stream_sizes[0] + stream_sizes[1]), (stream_sizes[2] as uN[AXI_ADDR_W])), + (false, true, u3:3) => ((state.ctrl.base_addr + JUMP_TABLE_SIZE as uN[AXI_ADDR_W] + stream_sizes[0] + stream_sizes[1] + stream_sizes[2]), (stream_sizes[3] as uN[AXI_ADDR_W])), + + (true, true, u3:0) => ((state.ctrl.base_addr + state.tree_description_size + JUMP_TABLE_SIZE as uN[AXI_ADDR_W]), (stream_sizes[0] as uN[AXI_ADDR_W])), + (true, true, u3:1) => ((state.ctrl.base_addr + state.tree_description_size + JUMP_TABLE_SIZE as uN[AXI_ADDR_W] + stream_sizes[0]), (stream_sizes[1] as uN[AXI_ADDR_W])), + (true, true, u3:2) => ((state.ctrl.base_addr + state.tree_description_size + JUMP_TABLE_SIZE as uN[AXI_ADDR_W] + stream_sizes[0] + stream_sizes[1]), (stream_sizes[2] as uN[AXI_ADDR_W])), + (true, true, u3:3) => ((state.ctrl.base_addr + state.tree_description_size + JUMP_TABLE_SIZE as uN[AXI_ADDR_W] + stream_sizes[0] + stream_sizes[1] + stream_sizes[2]), (stream_sizes[3] as uN[AXI_ADDR_W])), + + (_, _, _) => (state.ctrl.base_addr, state.ctrl.len) + }; // send address and length to AXI reader - if ctrl_valid { - trace_fmt!("Sending ctrl to AXI reader"); - } else {}; - send_if(tok, axi_reader_ctrl_s, ctrl_valid, AxiReaderCtrl { - base_addr: ctrl.base_addr, - len: ctrl.len, - }); + let axi_reader_ctrl = AxiReaderCtrl { + base_addr: huffman_stream_addr, + len: huffman_stream_len, + }; + send_if(tok, axi_reader_ctrl_s, start_decoding, axi_reader_ctrl); + if (start_decoding) { trace_fmt!("Sent request to AXI reader: {:#x}", axi_reader_ctrl); } else {}; // send reconfigure/keep to data preprocessor and decoder - if ctrl_valid { - trace_fmt!("Sending start to data preprocessor and decoder"); - } else {}; - send_if(tok, data_preprocess_start_s, ctrl_valid, DataPreprocessorStart { - new_config: new_config, - }); - send_if(tok, decoder_start_s, ctrl_valid, DecoderStart { - new_config: new_config, - }); + let config = if (state.multi_stream_decodings_finished > u3:0) { + false + } else { + state.ctrl.new_config + }; + let preprocessor_start = DataPreprocessorStart { + new_config: config, + }; + send_if(tok, data_preprocess_start_s, start_decoding, preprocessor_start); + if start_decoding { trace_fmt!("Sent preprocessor start: {:#x}", preprocessor_start); } else {}; + let decoder_start = DecoderStart { + new_config: config, + id: state.ctrl.id, // sending only if ctrl is valid + literals_last: state.ctrl.literals_last, + last_stream: !state.ctrl.multi_stream || (state.ctrl.multi_stream && state.multi_stream_decodings_finished == u3:3), + }; + send_if(tok, decoder_start_s, start_decoding, decoder_start); + if start_decoding { trace_fmt!("Sent decoder start: {:#x}", decoder_start); } else {}; + let state = if start_decoding { + State { + stream_dec_pending: true, + ..state + } + } else { + state + }; // receive done let (_, _, decoder_done_valid) = recv_if_non_blocking(tok, decoder_done_r, state.fsm == FSM::DECODING, ()); - if decoder_done_valid { - trace_fmt!("Received decoder done"); - } else {}; + if (decoder_done_valid) { trace_fmt!("Received Decoder Done"); } else {}; + let multi_stream_decodings_finished = if state.multi_stream_dec_pending { + state.multi_stream_decodings_finished + u3:1 + } else { + state.multi_stream_decodings_finished + }; + + let state = if decoder_done_valid { + State { + stream_dec_pending: false, + multi_stream_decodings_finished: multi_stream_decodings_finished, + ..state + } + } else { + state + }; - if decoder_done_valid { + let state = if (multi_stream_decodings_finished == u3:4) { + trace_fmt!("Multi-Stream decoding done"); State { - fsm: FSM::IDLE + multi_stream_dec_pending: false, + multi_stream_decodings_finished: u3:0, + ..state } } else { state + }; + + let resp = Resp { status: Status::OKAY }; + let do_send_resp = decoder_done_valid && !state.multi_stream_dec_pending && state.multi_stream_decodings_finished == u3:0; + send_if(tok, resp_s, do_send_resp, resp); + if (do_send_resp) { trace_fmt!("Sent Ctrl response: {:#x}", resp); } else {}; + + if do_send_resp { + zero!() + } else { + state } } } const INST_AXI_ADDR_W = u32:32; +const INST_AXI_DATA_W = u32:64; proc HuffmanControlAndSequenceInst { type AxiReaderCtrl = axi_reader::HuffmanAxiReaderCtrl; type DataPreprocessorStart = data_preprocessor::HuffmanDataPreprocessorStart; type DecoderStart = decoder::HuffmanDecoderStart; + type WeightsDecReq = weights_dec::HuffmanWeightsDecoderReq; + type WeightsDecResp = weights_dec::HuffmanWeightsDecoderResp; + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; config ( ctrl_r: chan> in, + resp_s: chan out, + weights_dec_req_s: chan out, + weights_dec_resp_r: chan in, prescan_start_s: chan out, code_builder_start_s: chan out, axi_reader_ctrl_s: chan out, data_preprocess_start_s: chan out, decoder_start_s: chan out, decoder_done_r: chan<()> in, + mem_rd_req_s: chan out, + mem_rd_resp_r: chan in, ) { - spawn HuffmanControlAndSequence( - ctrl_r, + spawn HuffmanControlAndSequence( + ctrl_r, resp_s, + weights_dec_req_s, + weights_dec_resp_r, prescan_start_s, code_builder_start_s, axi_reader_ctrl_s, data_preprocess_start_s, decoder_start_s, decoder_done_r, + mem_rd_req_s, + mem_rd_resp_r, ); } @@ -181,52 +368,78 @@ proc HuffmanControlAndSequenceInst { const TEST_AXI_ADDR_W = u32:32; +const TEST_AXI_DATA_W = u32:64; #[test_proc] proc HuffmanControlAndSequence_test { - type Ctrl = HuffmanControlAndSequenceCtrl; + type Ctrl = HuffmanControlAndSequenceCtrl; + type Resp = HuffmanControlAndSequenceResp; + type Status = HuffmanControlAndSequenceStatus; type AxiReaderCtrl = axi_reader::HuffmanAxiReaderCtrl; type DataPreprocessorStart = data_preprocessor::HuffmanDataPreprocessorStart; type DecoderStart = decoder::HuffmanDecoderStart; + type WeightsDecReq = weights_dec::HuffmanWeightsDecoderReq; + type WeightsDecResp = weights_dec::HuffmanWeightsDecoderResp; + type WeightsDecStatus = weights_dec::HuffmanWeightsDecoderStatus; + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; terminator: chan out; ctrl_s: chan> out; + resp_r: chan in; + weights_dec_req_r: chan in; + weights_dec_resp_s: chan out; prescan_start_r: chan in; code_builder_start_r: chan in; axi_reader_ctrl_r: chan in; data_preprocess_start_r: chan in; decoder_start_r: chan in; decoder_done_s: chan<()> out; + mem_rd_req_r: chan in; + mem_rd_resp_s: chan out; config (terminator: chan out) { let (ctrl_s, ctrl_r) = chan("ctrl"); + let (resp_s, resp_r) = chan("resp"); + let (weights_dec_req_s, weights_dec_req_r) = chan("weights_dec_req"); + let (weights_dec_resp_s, weights_dec_resp_r) = chan("weights_dec_resp"); let (prescan_start_s, prescan_start_r) = chan("prescan_start"); let (code_builder_start_s, code_builder_start_r) = chan("code_builder_start"); let (axi_reader_ctrl_s, axi_reader_ctrl_r) = chan("axi_reader_ctrl"); let (data_preprocess_start_s, data_preprocess_start_r) = chan("data_preprocess_start"); let (decoder_start_s, decoder_start_r) = chan("decoder_start"); let (decoder_done_s, decoder_done_r) = chan<()>("decoder_done"); - - spawn HuffmanControlAndSequence( - ctrl_r, + let (mem_rd_req_s, mem_rd_req_r) = chan("mem_rd_req"); + let (mem_rd_resp_s, mem_rd_resp_r) = chan("mem_rd_resp"); + + spawn HuffmanControlAndSequence( + ctrl_r, resp_s, + weights_dec_req_s, + weights_dec_resp_r, prescan_start_s, code_builder_start_s, axi_reader_ctrl_s, data_preprocess_start_s, decoder_start_s, decoder_done_r, + mem_rd_req_s, + mem_rd_resp_r ); ( terminator, - ctrl_s, + ctrl_s, resp_r, + weights_dec_req_r, + weights_dec_resp_s, prescan_start_r, code_builder_start_r, axi_reader_ctrl_r, data_preprocess_start_r, decoder_start_r, decoder_done_s, + mem_rd_req_r, + mem_rd_resp_s ) } @@ -234,50 +447,227 @@ proc HuffmanControlAndSequence_test { next (state: ()) { let tok = join(); - - // without new config + // Single Stream + // Without new config + trace_fmt!("[TEST] Case #1"); let ctrl = Ctrl { base_addr: uN[TEST_AXI_ADDR_W]:0x1, len: uN[TEST_AXI_ADDR_W]:0x2, new_config: false, + multi_stream: false, + id: u32:10, + literals_last: true, }; let tok = send(tok, ctrl_s, ctrl); + let (tok, prescan_start) = recv(tok, prescan_start_r); + trace_fmt!("[TEST] Received prescan START"); + assert_eq(true, prescan_start); + + let (tok, code_builder_start) = recv(tok, code_builder_start_r); + trace_fmt!("[TEST] Received code builder START"); + assert_eq(true, code_builder_start); + let (tok, axi_reader_ctrl) = recv(tok, axi_reader_ctrl_r); + trace_fmt!("[TEST] Received AXI reader CTRL"); assert_eq(AxiReaderCtrl {base_addr: ctrl.base_addr, len: ctrl.len}, axi_reader_ctrl); let (tok, data_preprocess_start) = recv(tok, data_preprocess_start_r); + trace_fmt!("[TEST] Received data preprocess START"); assert_eq(DataPreprocessorStart {new_config: ctrl.new_config}, data_preprocess_start); let (tok, decoder_start) = recv(tok, decoder_start_r); - assert_eq(DecoderStart {new_config: ctrl.new_config}, decoder_start); + trace_fmt!("[TEST] Received decoder START"); + assert_eq(DecoderStart {new_config: ctrl.new_config, id: ctrl.id, literals_last: ctrl.literals_last, last_stream: true }, decoder_start); let tok = send(tok, decoder_done_s, ()); + let (tok, resp) = recv(tok, resp_r); + trace_fmt!("[TEST] Received resp"); + assert_eq(Resp {status: Status::OKAY}, resp); - // with new config + // Single Stream + // With new config + trace_fmt!("[TEST] Case #2"); let ctrl = Ctrl { base_addr: uN[TEST_AXI_ADDR_W]:0x1, - len: uN[TEST_AXI_ADDR_W]:0x2, + len: uN[TEST_AXI_ADDR_W]:0x50, new_config: true, + multi_stream: false, + id: u32:0, + literals_last: false, }; let tok = send(tok, ctrl_s, ctrl); + let (tok, weights_dec_req) = recv(tok, weights_dec_req_r); + trace_fmt!("[TEST] Received weights decode request"); + assert_eq(WeightsDecReq {addr: uN[TEST_AXI_ADDR_W]:0x1}, weights_dec_req); + + // Signal Weight decoding done + let tree_description_size = uN[TEST_AXI_ADDR_W]:0x25; + let tok = send(tok, weights_dec_resp_s, WeightsDecResp{ + status: WeightsDecStatus::OKAY, + tree_description_size: tree_description_size + }); + let (tok, prescan_start) = recv(tok, prescan_start_r); + trace_fmt!("[TEST] Received prescan START"); assert_eq(true, prescan_start); let (tok, code_builder_start) = recv(tok, code_builder_start_r); + trace_fmt!("[TEST] Received code builder START"); assert_eq(true, code_builder_start); let (tok, axi_reader_ctrl) = recv(tok, axi_reader_ctrl_r); - assert_eq(AxiReaderCtrl {base_addr: ctrl.base_addr, len: ctrl.len}, axi_reader_ctrl); + trace_fmt!("[TEST] Received AXI reader CTRL"); + assert_eq(AxiReaderCtrl {base_addr: ctrl.base_addr + tree_description_size, len: ctrl.len - tree_description_size}, axi_reader_ctrl); let (tok, data_preprocess_start) = recv(tok, data_preprocess_start_r); + trace_fmt!("[TEST] Received data preprocess START"); assert_eq(DataPreprocessorStart {new_config: ctrl.new_config}, data_preprocess_start); let (tok, decoder_start) = recv(tok, decoder_start_r); - assert_eq(DecoderStart {new_config: ctrl.new_config}, decoder_start); + trace_fmt!("[TEST] Received decoder START"); + assert_eq(DecoderStart {new_config: ctrl.new_config, id: ctrl.id, literals_last: ctrl.literals_last, last_stream: true }, decoder_start); let tok = send(tok, decoder_done_s, ()); + let (tok, resp) = recv(tok, resp_r); + trace_fmt!("[TEST] Received resp"); + assert_eq(Resp {status: Status::OKAY}, resp); + + // 4 Streams + // Without new config + trace_fmt!("[TEST] Case #3"); + let ctrl = Ctrl { + base_addr: uN[TEST_AXI_ADDR_W]:0x1, + len: uN[TEST_AXI_ADDR_W]:0xF, + new_config: false, + multi_stream: true, + id: u32:10, + literals_last: true, + }; + let tok = send(tok, ctrl_s, ctrl); + + let (tok, mem_rd_req) = recv(tok, mem_rd_req_r); + trace_fmt!("[TEST] Received jump table read request"); + assert_eq(MemReaderReq {addr: uN[TEST_AXI_ADDR_W]:0x1, length: uN[TEST_AXI_ADDR_W]:0x6}, mem_rd_req); + + let tok = send(tok, mem_rd_resp_s, MemReaderResp {status: mem_reader::MemReaderStatus::OKAY, data: uN[TEST_AXI_DATA_W]:0x0003_0002_0001, length: uN[TEST_AXI_ADDR_W]:0x6, last:true}); + + const TEST_STREAM_ADDR = uN[TEST_AXI_ADDR_W][4]:[ + ctrl.base_addr + JUMP_TABLE_SIZE, + ctrl.base_addr + JUMP_TABLE_SIZE + uN[TEST_AXI_ADDR_W]:0x3, + ctrl.base_addr + JUMP_TABLE_SIZE + uN[TEST_AXI_ADDR_W]:0x5, + ctrl.base_addr + JUMP_TABLE_SIZE + uN[TEST_AXI_ADDR_W]:0x6, + ]; + const TEST_STREAM_LENGTH = uN[TEST_AXI_ADDR_W][4]:[ + uN[TEST_AXI_ADDR_W]:0x3, + uN[TEST_AXI_ADDR_W]:0x2, + uN[TEST_AXI_ADDR_W]:0x1, + uN[TEST_AXI_ADDR_W]:0x3, + ]; + + for (i, tok) in u32:0..u32:4 { + trace_fmt!("[TEST] Stream #{}", i); + + let (tok, prescan_start) = recv(tok, prescan_start_r); + trace_fmt!("[TEST] Received prescan START"); + assert_eq(true, prescan_start); + + let (tok, code_builder_start) = recv(tok, code_builder_start_r); + trace_fmt!("[TEST] Received code builder START"); + assert_eq(true, code_builder_start); + + let (tok, axi_reader_ctrl) = recv(tok, axi_reader_ctrl_r); + trace_fmt!("[TEST] Received AXI reader CTRL"); + assert_eq(AxiReaderCtrl {base_addr: TEST_STREAM_ADDR[i], len: TEST_STREAM_LENGTH[i]}, axi_reader_ctrl); + + let (tok, data_preprocess_start) = recv(tok, data_preprocess_start_r); + trace_fmt!("[TEST] Received data preprocess START"); + assert_eq(DataPreprocessorStart {new_config: ctrl.new_config}, data_preprocess_start); + + let (tok, decoder_start) = recv(tok, decoder_start_r); + trace_fmt!("[TEST] Received decoder START"); + assert_eq(DecoderStart {new_config: ctrl.new_config, id: ctrl.id, literals_last: ctrl.literals_last, last_stream: (i == u32:3) }, decoder_start); + + let tok = send(tok, decoder_done_s, ()); + + tok + }(tok); + + let (tok, resp) = recv(tok, resp_r); + trace_fmt!("[TEST] Received resp"); + assert_eq(Resp {status: Status::OKAY}, resp); + + // 4 Streams + // With new config + trace_fmt!("[TEST] Case #4"); + let ctrl = Ctrl { + base_addr: uN[TEST_AXI_ADDR_W]:0x1, + len: uN[TEST_AXI_ADDR_W]:0x50, + new_config: true, + multi_stream: true, + id: u32:0, + literals_last: false, + }; + let tok = send(tok, ctrl_s, ctrl); + + let (tok, weights_dec_req) = recv(tok, weights_dec_req_r); + trace_fmt!("[TEST] Received weights decode request"); + assert_eq(WeightsDecReq {addr: uN[TEST_AXI_ADDR_W]:0x1}, weights_dec_req); + + // Signal Weight decoding done + let tree_description_size = uN[TEST_AXI_ADDR_W]:0x25; + let tok = send(tok, weights_dec_resp_s, WeightsDecResp{ + status: WeightsDecStatus::OKAY, + tree_description_size: tree_description_size + }); + + let (tok, mem_rd_req) = recv(tok, mem_rd_req_r); + trace_fmt!("[TEST] Received jump table read request"); + assert_eq(MemReaderReq {addr: uN[TEST_AXI_ADDR_W]:0x26, length: uN[TEST_AXI_ADDR_W]:0x6}, mem_rd_req); + + let tok = send(tok, mem_rd_resp_s, MemReaderResp {status: mem_reader::MemReaderStatus::OKAY, data: uN[TEST_AXI_DATA_W]:0x0003_0002_0001, length: uN[TEST_AXI_ADDR_W]:0x6, last:true}); + + const TEST_STREAM_ADDR = uN[TEST_AXI_ADDR_W][4]:[ + ctrl.base_addr + tree_description_size + JUMP_TABLE_SIZE, + ctrl.base_addr + tree_description_size + JUMP_TABLE_SIZE + uN[TEST_AXI_ADDR_W]:0x3, + ctrl.base_addr + tree_description_size + JUMP_TABLE_SIZE + uN[TEST_AXI_ADDR_W]:0x5, + ctrl.base_addr + tree_description_size + JUMP_TABLE_SIZE + uN[TEST_AXI_ADDR_W]:0x6, + ]; + const TEST_STREAM_LENGTH = uN[TEST_AXI_ADDR_W][4]:[ + uN[TEST_AXI_ADDR_W]:0x3, + uN[TEST_AXI_ADDR_W]:0x2, + uN[TEST_AXI_ADDR_W]:0x1, + uN[TEST_AXI_ADDR_W]:0x1F, + ]; + + for (i, tok) in u32:0..u32:4 { + trace_fmt!("[TEST] Stream #{}", i); + + let (tok, prescan_start) = recv(tok, prescan_start_r); + trace_fmt!("[TEST] Received prescan START"); + assert_eq(true, prescan_start); + + let (tok, code_builder_start) = recv(tok, code_builder_start_r); + trace_fmt!("[TEST] Received code builder START"); + assert_eq(true, code_builder_start); + + let (tok, axi_reader_ctrl) = recv(tok, axi_reader_ctrl_r); + trace_fmt!("[TEST] Received AXI reader CTRL"); + assert_eq(AxiReaderCtrl {base_addr: TEST_STREAM_ADDR[i], len: TEST_STREAM_LENGTH[i]}, axi_reader_ctrl); + + let (tok, data_preprocess_start) = recv(tok, data_preprocess_start_r); + trace_fmt!("[TEST] Received data preprocess START"); + assert_eq(DataPreprocessorStart {new_config: ctrl.new_config && (i == u32:0)}, data_preprocess_start); + + let (tok, decoder_start) = recv(tok, decoder_start_r); + trace_fmt!("[TEST] Received decoder START"); + assert_eq(DecoderStart {new_config: ctrl.new_config && (i == u32:0), id: ctrl.id, literals_last: ctrl.literals_last, last_stream: (i == u32:3) }, decoder_start); + + let tok = send(tok, decoder_done_s, ()); + + tok + }(tok); send(tok, terminator, true); } diff --git a/xls/modules/zstd/huffman_data_preprocessor.x b/xls/modules/zstd/huffman_data_preprocessor.x index 4f97cb9943..445f6fed8d 100644 --- a/xls/modules/zstd/huffman_data_preprocessor.x +++ b/xls/modules/zstd/huffman_data_preprocessor.x @@ -111,6 +111,7 @@ pub proc HuffmanDataPreprocessor { ); let state = if config_valid { + trace_fmt!("Received config {:#x}", config); State { fsm: FSM::READ_DATA, lookahead_config: config, @@ -124,6 +125,7 @@ pub proc HuffmanDataPreprocessor { // process data let state = if data_valid { + trace_fmt!("Received data {:#x}", data); let fsm = if data.last { FSM::PROCESSING } else { diff --git a/xls/modules/zstd/huffman_decoder.x b/xls/modules/zstd/huffman_decoder.x index e13bc484d1..83bc91959b 100644 --- a/xls/modules/zstd/huffman_decoder.x +++ b/xls/modules/zstd/huffman_decoder.x @@ -40,6 +40,10 @@ enum HuffmanDecoderFSM: u3 { pub struct HuffmanDecoderStart { new_config: bool, + id: u32, + literals_last: bool, + last_stream: bool, // 4'th huffman coded stream decoding for multi_stream + // or single stream decoding } struct HuffmanDecoderState { @@ -53,6 +57,9 @@ struct HuffmanDecoderState { code_length: CodeLen[BUFF_W], decoded_literals: uN[common::SYMBOL_WIDTH][u32:8], decoded_literals_len: u4, + id: u32, + literals_last: bool, + last_stream: bool, } fn extend_buff_array(buff: CodeLen[N], buff_len: u32, array: CodeLen[M]) -> CodeLen[N] { @@ -124,14 +131,14 @@ pub proc HuffmanDecoder { data_r: chan in; done_s: chan<()> out; - decoded_literals_s: chan out; + decoded_literals_s: chan out; config ( start_r: chan in, codes_r: chan in, data_r: chan in, done_s: chan<()> out, - decoded_literals_s: chan out, + decoded_literals_s: chan out, ) { ( start_r, @@ -154,30 +161,38 @@ pub proc HuffmanDecoder { let state = if start_valid { if start.new_config { - trace_fmt!("IDLE -> AWAITING_CONFIG"); + trace_fmt!("{} -> AWAITING_CONFIG", state.fsm); + assert!(state.fsm == FSM::IDLE, "invalid_state_transition"); State { fsm: FSM::AWAITING_CONFIG, symbol_config_id: u5:0, + id: start.id, + literals_last: start.literals_last, + last_stream: start.last_stream, ..state } } else { - trace_fmt!("IDLE -> READ_DATA"); + trace_fmt!("{} -> READ_DATA", state.fsm); + assert!(state.fsm == FSM::IDLE, "invalid_state_transition"); State { fsm: FSM::READ_DATA, + id: start.id, + literals_last: start.literals_last, + last_stream: start.last_stream, ..state } } } else { state }; // wait for config - let (tok, config, config_valid) = recv_if_non_blocking( + let (tok, config) = recv_if( tok, codes_r, state.fsm == FSM::AWAITING_CONFIG, zero!() ); - let state = if config_valid { + let state = if state.fsm == FSM::AWAITING_CONFIG { let (symbol_valid, symbol_code, symbol_code_len) = for (i, (symbol_valid, symbol_code, symbol_code_len)): ( @@ -194,8 +209,12 @@ pub proc HuffmanDecoder { update(symbol_code_len, (state.symbol_config_id as u32 * u32:8) + i, config.code_length[i]), ) }((state.symbol_valid, state.symbol_code, state.symbol_code_len)); + trace_fmt!("state.symbol_config_id+1: {:#x}", state.symbol_config_id as u32 + u32:1); + trace_fmt!("SYMBOLS_N: {:#x}", SYMBOLS_N); + trace_fmt!("hcommon::PARALLEL_ACCESS_WIDTH: {:#x}", hcommon::PARALLEL_ACCESS_WIDTH); let fsm = if (state.symbol_config_id as u32 + u32:1) == (SYMBOLS_N / hcommon::PARALLEL_ACCESS_WIDTH) { - trace_fmt!("AWAITING_CONFIG -> READ_DATA"); + trace_fmt!("{} -> READ_DATA", state.fsm); + assert!(state.fsm == FSM::AWAITING_CONFIG, "invalid_state_transition"); trace_fmt!("Received codes:"); for (i, ()) in range(u32:0, SYMBOLS_N) { if symbol_valid[i] { @@ -222,7 +241,8 @@ pub proc HuffmanDecoder { ); let state = if data_valid { - trace_fmt!("READ_DATA -> DECODE"); + trace_fmt!("{} -> DECODE", state.fsm); + assert!(state.fsm == FSM::READ_DATA, "invalid_state_transition"); trace_fmt!("Received data: {:#b} (len: {})", data.data, data.data_len); State { fsm: FSM::DECODE, @@ -289,20 +309,28 @@ pub proc HuffmanDecoder { zero!() }; - let done = state.data_len == uN[BUFF_W_LOG2]:0; - send_if(tok, decoded_literals_s, do_send_literals, common::LiteralsData{ + let done = (state.data_len == uN[BUFF_W_LOG2]:0) && (state.fsm == FSM::DECODE); + let decoded_literals = common::LiteralsDataWithSync{ data: data, length: state.decoded_literals_len as common::LitLength, - last: done, - }); + last: done && state.last_stream, + id: state.id, + literals_last: state.literals_last, + }; + send_if(tok, decoded_literals_s, do_send_literals, decoded_literals); + if (do_send_literals) { + trace_fmt!("Sent decoded literals: {:#x}", decoded_literals); + } else {}; let state = if do_send_literals { let fsm = if state.data_len == uN[BUFF_W_LOG2]:0 { - trace_fmt!("DECODE -> IDLE"); + trace_fmt!("{} -> IDLE", state.fsm); FSM::IDLE } else { + trace_fmt!("{} -> DECODE", state.fsm); FSM::DECODE }; + assert!(state.fsm == FSM::DECODE, "invalid_state_transition"); State { fsm: fsm, decoded_literals_len: u4:0, @@ -347,9 +375,9 @@ fn generate_codes(data: SymbolData[8]) -> Codes { } const TEST_START = HuffmanDecoderStart[3]:[ - HuffmanDecoderStart { new_config: true }, - HuffmanDecoderStart { new_config: false }, - HuffmanDecoderStart { new_config: true }, + HuffmanDecoderStart { new_config: true, id: u32:0, literals_last: false, last_stream: true }, + HuffmanDecoderStart { new_config: false, id: u32:1, literals_last: true, last_stream: true }, + HuffmanDecoderStart { new_config: true, id: u32:0, literals_last: false, last_stream: true }, ]; // config #1 @@ -599,41 +627,55 @@ const TEST_DATA = huffman_data_preprocessor::HuffmanDataPreprocessorData[3]:[ }, ]; -const TEST_LITERALS = common::LiteralsData[7]:[ - common::LiteralsData { +const TEST_LITERALS = common::LiteralsDataWithSync[7]:[ + common::LiteralsDataWithSync { data: common::LitData:0x06B5_0002_0606_B500, length: common::LitLength:8, last: false, + id: u32:0, + literals_last: false, }, - common::LiteralsData { + common::LiteralsDataWithSync { data: common::LitData:0x0200, length: common::LitLength:2, last: true, + id: u32:0, + literals_last: false, }, - common::LiteralsData { + common::LiteralsDataWithSync { data: common::LitData:0x06B5_0002_0606_B500, length: common::LitLength:8, last: false, + id: u32:1, + literals_last: true, }, - common::LiteralsData { + common::LiteralsDataWithSync { data: common::LitData:0x0200, length: common::LitLength:2, last: true, + id: u32:1, + literals_last: true, }, - common::LiteralsData { + common::LiteralsDataWithSync { data: common::LitData:0x7AD2_8947_478A_D247, length: common::LitLength:8, last: false, + id: u32:0, + literals_last: false, }, - common::LiteralsData { + common::LiteralsDataWithSync { data: common::LitData:0x4141_D347_D28A_47D2, length: common::LitLength:8, last: false, + id: u32:0, + literals_last: false, }, - common::LiteralsData { + common::LiteralsDataWithSync { data: common::LitData:0x47D2_418A_47D2, length: common::LitLength:6, last: true, + id: u32:0, + literals_last: false, }, ]; @@ -649,14 +691,14 @@ proc HuffmanDecoder_test { data_s: chan out; done_r: chan<()> in; - decoded_literals_r: chan in; + decoded_literals_r: chan in; config (terminator_s: chan out) { let (start_s, start_r) = chan("start"); let (codes_s, codes_r) = chan("codes"); let (data_s, data_r) = chan("data"); let (done_s, done_r) = chan<()>("done"); - let (decoded_literals_s, decoded_literals_r) = chan("decoded_literals"); + let (decoded_literals_s, decoded_literals_r) = chan("decoded_literals"); spawn HuffmanDecoder( start_r, codes_r, data_r, @@ -700,7 +742,7 @@ proc HuffmanDecoder_test { (tok, codes_idx) }((tok, u32:0)); - let tok = for ((i, expected_literals), tok): ((u32, common::LiteralsData), token) in enumerate(TEST_LITERALS) { + let tok = for ((i, expected_literals), tok): ((u32, common::LiteralsDataWithSync), token) in enumerate(TEST_LITERALS) { // receive literals let (tok, literals) = recv(tok, decoded_literals_r); trace_fmt!("Received #{} literals {:#x}", i + u32:1, literals); diff --git a/xls/modules/zstd/huffman_literals_dec.x b/xls/modules/zstd/huffman_literals_dec.x index c93a736404..ad396a78c6 100644 --- a/xls/modules/zstd/huffman_literals_dec.x +++ b/xls/modules/zstd/huffman_literals_dec.x @@ -14,6 +14,7 @@ // This file contains Huffman literals decoder proc implementation. +import std; import xls.modules.zstd.common as common; import xls.modules.zstd.huffman_common as hcommon; import xls.modules.zstd.huffman_axi_reader as axi_reader; @@ -22,37 +23,90 @@ import xls.modules.zstd.huffman_data_preprocessor as data_preprocessor; import xls.modules.zstd.huffman_decoder as decoder; import xls.modules.zstd.huffman_prescan as prescan; import xls.modules.zstd.huffman_ctrl as ctrl; +import xls.modules.zstd.huffman_weights_dec as weights_dec; import xls.modules.zstd.memory.axi as axi; +import xls.modules.zstd.memory.axi_ram; +import xls.modules.zstd.memory.mem_reader as mem_reader; import xls.examples.ram; -pub proc HuffmanLiteralsDecoder { +pub fn WeightPreScanMetaDataSize() -> u32 { + prescan::WeightPreScanMetaDataSize() +} + +pub type HuffmanLiteralsDecoderReq = ctrl::HuffmanControlAndSequenceCtrl; +pub type HuffmanLiteralsDecoderResp = ctrl::HuffmanControlAndSequenceResp; +pub type HuffmanLiteralsDecoderStatus = ctrl::HuffmanControlAndSequenceStatus; + +pub const WEIGHTS_ADDR_WIDTH: u32 = prescan::RAM_ADDR_WIDTH; +pub const WEIGHTS_DATA_WIDTH: u32 = prescan::RAM_ACCESS_WIDTH; +pub const WEIGHTS_NUM_PARTITIONS: u32 = u32:1; +pub const PRESCAN_ADDR_WIDTH: u32 = prescan::RAM_ADDR_WIDTH; +pub const PRESCAN_DATA_WIDTH: u32 = prescan::WeightPreScanMetaDataSize(); +pub const PRESCAN_NUM_PARTITIONS: u32 = u32:1; + +pub proc HuffmanLiteralsDecoder< + AXI_DATA_W: u32, AXI_ADDR_W: u32, AXI_ID_W: u32, AXI_DEST_W: u32, + WEIGHTS_RAM_ADDR_WIDTH: u32 = {WEIGHTS_ADDR_WIDTH}, + WEIGHTS_RAM_DATA_WIDTH: u32 = {WEIGHTS_DATA_WIDTH}, + WEIGHTS_RAM_NUM_PARTITIONS: u32 = {WEIGHTS_NUM_PARTITIONS}, + PRESCAN_RAM_ADDR_WIDTH: u32 = {PRESCAN_ADDR_WIDTH}, + PRESCAN_RAM_DATA_WIDTH: u32 = {PRESCAN_DATA_WIDTH}, + PRESCAN_RAM_NUM_PARTITIONS: u32 = {PRESCAN_NUM_PARTITIONS}, + > { type AxiR = axi::AxiR; type AxiAr = axi::AxiAr; - type ReadReq = ram::ReadReq; - type ReadResp = ram::ReadResp; + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + + type WeightsRamRdReq = ram::ReadReq; + type WeightsRamRdResp = ram::ReadResp; + type WeightsRamWrReq = ram::WriteReq; + type WeightsRamWrResp = ram::WriteResp; + type PrescanRamRdReq = ram::ReadReq; + type PrescanRamRdResp = ram::ReadResp; + type PrescanRamWrReq = ram::WriteReq; + type PrescanRamWrResp = ram::WriteResp; + + type WeightsDecReq = weights_dec::HuffmanWeightsDecoderReq; + type WeightsDecResp = weights_dec::HuffmanWeightsDecoderResp; type HuffmanAxiReaderCtrl = axi_reader::HuffmanAxiReaderCtrl; - type PrescanInternalReadReq = ram::ReadReq; - type PrescanInternalReadResp = ram::ReadResp<{prescan::WeightPreScanMetaDataSize()}>; - type PrescanInternalWriteReq = ram::WriteReq; - type PrescanInternalWriteResp = ram::WriteResp; + type Ctrl = HuffmanLiteralsDecoderReq; + type Resp = HuffmanLiteralsDecoderResp; config ( // ctrl - ctrl_r: chan in, + ctrl_r: chan in, + resp_s: chan out, // output literals - decoded_literals_s: chan out, - // AXI interface + decoded_literals_s: chan out, + // AXI interface - reverse reader axi_ar_s: chan out, axi_r_r: chan in, + // AXI interface - Huffman Jump Table decoder + jump_table_axi_ar_s: chan out, + jump_table_axi_r_r: chan in, + // AXI interface - Huffman tree description header decoder + weights_header_dec_axi_ar_s: chan out, + weights_header_dec_axi_r_r: chan in, + // AXI interface - RAW Huffman tree description decoder + weights_raw_dec_axi_ar_s: chan out, + weights_raw_dec_axi_r_r: chan in, + // AXI interface - FSE Huffman tree description decoder + weights_fse_dec_axi_ar_s: chan out, + weights_fse_dec_axi_r_r: chan in, // weight memory - ram_read_req_s: chan out, - ram_read_resp_r: chan in, - // code builder loopback - weights_pow_sum_loopback_s: chan out, - weights_pow_sum_loopback_r: chan in, + weights_ram_rd_req_s: chan out, + weights_ram_rd_resp_r: chan in, + weights_ram_wr_req_s: chan out, + weights_ram_wr_resp_r: chan in, + // prescan memory + prescan_ram_rd_req_s: chan out, + prescan_ram_rd_resp_r: chan in, + prescan_ram_wr_req_s: chan out, + prescan_ram_wr_resp_r: chan in, ) { let (prescan_start_s, prescan_start_r) = chan("prescan_start"); let (code_builder_start_s, code_builder_start_r) = chan("code_buider"); @@ -65,37 +119,75 @@ pub proc HuffmanLiteralsDecoder("lookahead_config"); let (axi_data_s, axi_data_r) = chan("axi_data"); let (preprocessed_data_s, preprocessed_data_r) = chan("preprocessed_data"); + let (weights_dec_req_s, weights_dec_req_r) = chan("weights_dec_req"); + let (weights_dec_resp_s, weights_dec_resp_r) = chan("weights_dec_resp"); + let (jump_table_mem_rd_req_s, jump_table_mem_rd_req_r) = chan("jump_table_req"); + let (jump_table_mem_rd_resp_s, jump_table_mem_rd_resp_r) = chan("jump_table_resp"); + let (weights_header_dec_mem_rd_req_s, weights_header_dec_mem_rd_req_r) = chan("weights_dec_mem_rd_req"); + let (weights_header_dec_mem_rd_resp_s, weights_header_dec_mem_rd_resp_r) = chan("weights_dec_mem_rd_resp"); + let (weights_raw_dec_mem_rd_req_s, weights_raw_dec_mem_rd_req_r) = chan("weights_dec_mem_rd_req"); + let (weights_raw_dec_mem_rd_resp_s, weights_raw_dec_mem_rd_resp_r) = chan("weights_dec_mem_rd_resp"); + let (weights_fse_dec_mem_rd_req_s, weights_fse_dec_mem_rd_req_r) = chan("weights_dec_mem_rd_req"); + let (weights_fse_dec_mem_rd_resp_s, weights_fse_dec_mem_rd_resp_r) = chan("weights_dec_mem_rd_resp"); - // prescan internal memory - let (prescan_internal_ram_write_req_s, prescan_internal_ram_write_req_r) = chan("prescan_internal_ram_write_req"); - let (prescan_internal_ram_write_resp_s, prescan_internal_ram_write_resp_r) = chan("prescan_internal_ram_write_resp"); - let (prescan_internal_ram_read_req_s, prescan_internal_ram_read_req_r) = chan("prescan_internal_ram_read_req"); - let (prescan_internal_ram_read_resp_s, prescan_internal_ram_read_resp_r) = chan("prescan_internal_ram_read_resp"); - - spawn ram::RamModel<{prescan::WeightPreScanMetaDataSize()}, prescan::RAM_SIZE, {prescan::WeightPreScanMetaDataSize()}>( - prescan_internal_ram_read_req_r, prescan_internal_ram_read_resp_s, - prescan_internal_ram_write_req_r, prescan_internal_ram_write_resp_s, - ); + // code builder loopback + let (weights_pow_sum_loopback_s, weights_pow_sum_loopback_r) = chan("weights_pow_sum_loopback"); - spawn ctrl::HuffmanControlAndSequence( - ctrl_r, + spawn ctrl::HuffmanControlAndSequence( + ctrl_r, resp_s, + weights_dec_req_s, weights_dec_resp_r, prescan_start_s, code_builder_start_s, axi_reader_ctrl_s, data_preprocess_start_s, decoder_start_s, decoder_done_r, + jump_table_mem_rd_req_s, + jump_table_mem_rd_resp_r, + ); + + spawn mem_reader::MemReader( + jump_table_mem_rd_req_r, jump_table_mem_rd_resp_s, + jump_table_axi_ar_s, jump_table_axi_r_r + ); + + spawn mem_reader::MemReader( + weights_header_dec_mem_rd_req_r, weights_header_dec_mem_rd_resp_s, + weights_header_dec_axi_ar_s, weights_header_dec_axi_r_r + ); + + spawn mem_reader::MemReader( + weights_raw_dec_mem_rd_req_r, weights_raw_dec_mem_rd_resp_s, + weights_raw_dec_axi_ar_s, weights_raw_dec_axi_r_r + ); + + spawn mem_reader::MemReader( + weights_fse_dec_mem_rd_req_r, weights_fse_dec_mem_rd_resp_s, + weights_fse_dec_axi_ar_s, weights_fse_dec_axi_r_r + ); + + spawn weights_dec::HuffmanWeightsDecoder< + AXI_ADDR_W, AXI_DATA_W, + WEIGHTS_RAM_ADDR_WIDTH, + WEIGHTS_RAM_DATA_WIDTH, + WEIGHTS_RAM_NUM_PARTITIONS, + >( + weights_dec_req_r, weights_dec_resp_s, + weights_header_dec_mem_rd_req_s, weights_header_dec_mem_rd_resp_r, + weights_raw_dec_mem_rd_req_s, weights_raw_dec_mem_rd_resp_r, + weights_fse_dec_mem_rd_req_s, weights_fse_dec_mem_rd_resp_r, + weights_ram_wr_req_s, weights_ram_wr_resp_r, ); spawn prescan::WeightPreScan( prescan_start_r, - ram_read_req_s, - ram_read_resp_r, + weights_ram_rd_req_s, + weights_ram_rd_resp_r, prescan_response_s, - prescan_internal_ram_read_req_s, - prescan_internal_ram_read_resp_r, - prescan_internal_ram_write_req_s, - prescan_internal_ram_write_resp_r, + prescan_ram_rd_req_s, + prescan_ram_rd_resp_r, + prescan_ram_wr_req_s, + prescan_ram_wr_resp_r, ); spawn code_builder::WeightCodeBuilder( @@ -107,7 +199,7 @@ pub proc HuffmanLiteralsDecoder( + spawn axi_reader::HuffmanAxiReader( axi_reader_ctrl_r, axi_r_r, axi_ar_s, @@ -137,40 +229,72 @@ pub proc HuffmanLiteralsDecoder; + type Ctrl = HuffmanLiteralsDecoderReq; + type Resp = HuffmanLiteralsDecoderResp; type AxiR = axi::AxiR; type AxiAr = axi::AxiAr; - type ReadReq = ram::ReadReq; - type ReadResp = ram::ReadResp; + type WeightsRamRdReq = ram::ReadReq; + type WeightsRamRdResp = ram::ReadResp; + type WeightsRamWrReq = ram::WriteReq; + type WeightsRamWrResp = ram::WriteResp; + type PrescanRamRdReq = ram::ReadReq; + type PrescanRamRdResp = ram::ReadResp; + type PrescanRamWrReq = ram::WriteReq; + type PrescanRamWrResp = ram::WriteResp; config ( ctrl_r: chan in, - decoded_literals_s: chan out, + resp_s: chan out, + decoded_literals_s: chan out, axi_ar_s: chan out, axi_r_r: chan in, - ram_read_req_s: chan out, - ram_read_resp_r: chan in, - weights_pow_sum_loopback_s: chan out, - weights_pow_sum_loopback_r: chan in, + jump_table_axi_ar_s: chan out, + jump_table_axi_r_r: chan in, + weights_header_dec_axi_ar_s: chan out, + weights_header_dec_axi_r_r: chan in, + weights_raw_dec_axi_ar_s: chan out, + weights_raw_dec_axi_r_r: chan in, + weights_fse_dec_axi_ar_s: chan out, + weights_fse_dec_axi_r_r: chan in, + weights_ram_rd_req_s: chan out, + weights_ram_rd_resp_r: chan in, + weights_ram_wr_req_s: chan out, + weights_ram_wr_resp_r: chan in, + prescan_ram_rd_req_s: chan out, + prescan_ram_rd_resp_r: chan in, + prescan_ram_wr_req_s: chan out, + prescan_ram_wr_resp_r: chan in, ) { - spawn HuffmanLiteralsDecoder( - ctrl_r, + spawn HuffmanLiteralsDecoder< + INST_AXI_DATA_W, INST_AXI_ADDR_W, INST_AXI_ID_W, INST_AXI_DEST_W, + INST_WEIGHTS_RAM_ADDR_WIDTH, INST_WEIGHTS_RAM_DATA_WIDTH, INST_WEIGHTS_RAM_NUM_PARTITIONS, + INST_PRESCAN_RAM_ADDR_WIDTH, INST_PRESCAN_RAM_DATA_WIDTH, INST_PRESCAN_RAM_NUM_PARTITIONS + >( + ctrl_r, resp_s, decoded_literals_s, - axi_ar_s, - axi_r_r, - ram_read_req_s, - ram_read_resp_r, - weights_pow_sum_loopback_s, - weights_pow_sum_loopback_r, + axi_ar_s, axi_r_r, + jump_table_axi_ar_s, jump_table_axi_r_r, + weights_header_dec_axi_ar_s, weights_header_dec_axi_r_r, + weights_raw_dec_axi_ar_s, weights_raw_dec_axi_r_r, + weights_fse_dec_axi_ar_s, weights_fse_dec_axi_r_r, + weights_ram_rd_req_s, weights_ram_rd_resp_r, + weights_ram_wr_req_s, weights_ram_wr_resp_r, + prescan_ram_rd_req_s, prescan_ram_rd_resp_r, + prescan_ram_wr_req_s, prescan_ram_wr_resp_r, ); } @@ -179,369 +303,630 @@ proc HuffmanLiteralsDecoderInst { next (state: ()) { } } -const TEST_AXI_DATA_W = u32:32; -const TEST_AXI_ADDR_W = u32:32; -const TEST_AXI_ID_W = u32:32; - -const TEST_RAM_ADDR_WIDTH = prescan::RAM_ADDR_WIDTH; -const TEST_RAM_ACCESS_WIDTH = prescan::RAM_ACCESS_WIDTH; - -type TestCtrl = ctrl::HuffmanControlAndSequenceCtrl; -type TestAxiR = axi::AxiR; -type TestAxiAr = axi::AxiAr; - -type TestReadReq = ram::ReadReq; -type TestReadResp = ram::ReadResp; - -type TestRamEntry = uN[TEST_RAM_ACCESS_WIDTH]; - -// data for test case #0 -const TEST_CTRL_0 = TestCtrl { - base_addr: uN[TEST_AXI_ADDR_W]:0x0, - len: uN[TEST_AXI_ADDR_W]:0x8, - new_config: true -}; - -const TEST_DATA_LEN_0 = u32:64; -const TEST_DATA_0 = ( - u8:0b1_001_010_1 ++ - u8:0b01_010_1_01 ++ - u8:0b0100_001_0 ++ - u8:0b11_010_1_00 ++ - u8:0b001_010_1_0 ++ - u8:0b01_010_000 ++ - u8:0b11_1_1_0001 ++ - u8:0b001_1_010_0 -); - -// code symbol length weight -// 0b1 0x47 1 9 -// 0b001 0x41 3 7 -// 0b010 0x8A 3 7 -// 0b011 0xD2 3 7 -// 0b000001 0x45 6 4 -// 0b000010 0x7A 6 4 -// 0b000011 0x89 6 4 -// 0b000100 0x8D 6 4 -// 0b000101 0xD1 6 4 -// 0b000110 0xD3 6 4 -// 0b000111 0xDA 6 4 -// 0b000000000 0x12 9 1 -// 0b000000001 0x8F 9 1 -// 0b000000010 0xAC 9 1 -// 0b000000011 0xD4 9 1 -// 0b000000100 0xD7 9 1 -// 0b000000101 0xDB 9 1 -// 0b000000110 0xDE 9 1 -// 0b000000111 0xFE 9 1 - -const TEST_WEIGHT_MEMORY_0 = TestRamEntry[32]:[ - // x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x0x - TestRamEntry:0x_0__0__1__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x1x - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x2x - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x3x - TestRamEntry:0x_0__7__0__0__0__4__0__9, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x4x - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x5x - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x6x - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__4__0__0__0__0__0, // 0x7x - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__4__7__0__0__4__0__1, // 0x8x - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x9x - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__1__0__0__0, // 0xAx - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xBx - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xCx - TestRamEntry:0x_0__4__7__4__1__0__0__1, TestRamEntry:0x_0__0__4__1__0__0__1__0, // 0xDx - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xEx - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__1__0, // 0xFx +const TEST_AXI_RAM_DATA_W = u32:64; +const TEST_AXI_RAM_ADDR_W = u32:32; +const TEST_AXI_RAM_ID_W = u32:32; +const TEST_AXI_RAM_DEST_W = u32:32; +const TEST_AXI_RAM_DATA_DIV8 = TEST_AXI_RAM_DATA_W / u32:8; +const TEST_AXI_RAM_DATA_DIV8_W = std::clog2(TEST_AXI_RAM_DATA_DIV8); + +// Parameters for RamModels used for mocking the system memory for +// the LiteralsBlockHeaderDecoder, RawLiteralsDecoder and HuffmanLiteralsDecoder +const TEST_AXI_RAM_MODEL_DATA_WIDTH:u32 = TEST_AXI_RAM_DATA_W; +const TEST_AXI_RAM_MODEL_SIZE:u32 = u32:2048; +const TEST_AXI_RAM_MODEL_ADDR_WIDTH:u32 = std::clog2(TEST_AXI_RAM_MODEL_SIZE); +const TEST_AXI_RAM_MODEL_WORD_PARTITION_SIZE:u32 = u32:8; +const TEST_AXI_RAM_MODEL_NUM_PARTITIONS:u32 = ram::num_partitions(TEST_AXI_RAM_MODEL_WORD_PARTITION_SIZE, TEST_AXI_RAM_MODEL_DATA_WIDTH); +const TEST_AXI_RAM_MODEL_BASE_ADDR:u32 = u32:0; +const TEST_AXI_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_AXI_RAM_MODEL_INITIALIZED = true; +const TEST_AXI_RAM_MODEL_ASSERT_VALID_READ = true; +const TEST_AXI_RAM_MODEL_NUM = u32:1; + +pub const TEST_WEIGHTS_RAM_SIZE = prescan::RAM_SIZE; +pub const TEST_WEIGHTS_RAM_ADDR_WIDTH = WEIGHTS_ADDR_WIDTH; +pub const TEST_WEIGHTS_RAM_DATA_WIDTH = WEIGHTS_DATA_WIDTH; +pub const TEST_WEIGHTS_RAM_NUM_PARTITIONS = WEIGHTS_NUM_PARTITIONS; +pub const TEST_WEIGHTS_WORD_PARTITION_SIZE = TEST_WEIGHTS_RAM_DATA_WIDTH; +pub const TEST_PRESCAN_RAM_ADDR_WIDTH = PRESCAN_ADDR_WIDTH; +pub const TEST_PRESCAN_RAM_DATA_WIDTH = PRESCAN_DATA_WIDTH; +pub const TEST_PRESCAN_RAM_NUM_PARTITIONS = PRESCAN_NUM_PARTITIONS; +pub const TEST_PRESCAN_RAM_SIZE = prescan::RAM_SIZE; +pub const TEST_PRESCAN_WORD_PARTITION_SIZE = prescan::WeightPreScanMetaDataSize(); + +type TestCtrl = HuffmanLiteralsDecoderReq; +type TestResp = HuffmanLiteralsDecoderResp; +type TestAxiR = axi::AxiR; +type TestAxiAr = axi::AxiAr; + +// System bus external memory +type TestAxiRamRdReq = ram::ReadReq; +type TestAxiRamRdResp = ram::ReadResp; +type TestAxiRamWrReq = ram::WriteReq; +type TestAxiRamWrResp = ram::WriteResp; + +type TestWeightsRamRdReq = ram::ReadReq; +type TestWeightsRamRdResp = ram::ReadResp; +type TestWeightsRamWrReq = ram::WriteReq; +type TestWeightsRamWrResp = ram::WriteResp; +type TestPrescanRamRdReq = ram::ReadReq; +type TestPrescanRamRdResp = ram::ReadResp; +type TestPrescanRamWrReq = ram::WriteReq; +type TestPrescanRamWrResp = ram::WriteResp; + +type TestRamEntry = uN[TEST_WEIGHTS_RAM_DATA_WIDTH]; + +type TestAxiRamData = uN[TEST_AXI_RAM_MODEL_DATA_WIDTH]; +type TestAxiRamAddr = uN[TEST_AXI_RAM_MODEL_ADDR_WIDTH]; +type TestAxiRamMask = uN[TEST_AXI_RAM_MODEL_NUM_PARTITIONS]; + +// Data for test case +// Source: Example from RFC 8878, 4.2.2. Huffman-Coded Streams +// https://datatracker.ietf.org/doc/html/rfc8878#huffman_coded_streams +// Weights taken from Table 25 +// Bitstream fixed to encode literal sequence "0145" +// See https://www.rfc-editor.org/errata/eid8195 + +const TEST_MEMORY: TestAxiRamWrReq[7] = [ + // Literals #0 + // Length: 6 bytes + // New config, 1 Stream + // HTD Header: 0x85 (Direct representation, HTD length: 3 + HTD_header (1 byte)) + // Huffman Tree Description + // code symbol length weight + // N/A 0x03 0 0 + // 0b0000 0x04 4 1 + // 0b0001 0x05 4 1 // last weight implicit + // 0b001 0x02 3 2 + // 0b01 0x01 2 3 + // 0b1 0x00 1 4 + // 0b00001 padding + + TestAxiRamWrReq { addr: TestAxiRamAddr:0x0, data: (u16:0b00001_1_01_0000_0001 ++ u24:0x010234 ++ u8:0x85) as TestAxiRamData, mask: TestAxiRamMask:0xFF }, + // AXI addr: 0x0 ^ ^ ^ + // Huffman-coded stream HTD HTD Header + + // Literals #1 + // Length: 2 bytes + // Old config, 1 Stream + TestAxiRamWrReq { addr: TestAxiRamAddr:0x20, data: TestAxiRamData:0b00001_0001_0000_01_1, mask: TestAxiRamMask:0xFF }, + // AXI addr: 0x100 ^ + // Huffman-coded stream + + + // Literals #2 + // Length: 18 bytes + // New config, 4 Streams + // HTD Header: 0x85 (Direct representation, HTD length: 3 + HTD_header (1 byte)) + // Jump Table: 0x0002_0002_0002 (Stream1: 2 bytes; Stream2: 2 bytes; Stream3: 2 bytes) + // Huffman Tree Description + // code symbol length weight + // N/A 0x03 0 0 + // 0b0000 0x04 4 1 + // 0b0001 0x05 4 1 // last weight implicit + // 0b001 0x02 3 2 + // 0b01 0x01 2 3 + // 0b1 0x00 1 4 + // 0b00001 padding + TestAxiRamWrReq { addr: TestAxiRamAddr:0x40, data: (u32:0x0002_0002 ++ u24:0x010234 ++ u8:0x85) as TestAxiRamData, mask: TestAxiRamMask:0xFF }, + // AXI addr: 0x200 ^ ^ ^ + // Jump table HTD HTD Header + TestAxiRamWrReq { addr: TestAxiRamAddr:0x41, data: (u16:0b00001_1_01_0000_0001 ++ u16:0b00001_1_01_0000_0001 ++ u16:0b00001_1_01_0000_0001 ++ u16:0x0002) as TestAxiRamData, mask: TestAxiRamMask:0xFF }, + // AXI addr: 0x208 ^ ^ ^ ^ + // Huffman-coded stream #3 Huffman-coded stream #2 Huffman-coded stream #1 Jump table continued + TestAxiRamWrReq { addr: TestAxiRamAddr:0x42, data: TestAxiRamData:0b00001_1_01_0000_0001, mask: TestAxiRamMask:0xFF }, + // AXI addr: 0x210 ^ + // Huffman-coded stream #4 + + // Literals #3 + // Length: 14 bytes + // Old config, 4 Streams + // Jump Table: 0x0002_0002_0002 (Stream1: 2 bytes; Stream2: 2 bytes; Stream3: 2 bytes) + TestAxiRamWrReq { addr: TestAxiRamAddr:0x60, data: (u16:0b00001_1_01_0000_0001 ++ u48:0x0002_0002_0002) as TestAxiRamData, mask: TestAxiRamMask:0xFF }, + // AXI addr: 0x300 ^ ^ + // Huffman-coded stream #1 Jump table + TestAxiRamWrReq { addr: TestAxiRamAddr:0x61, data: (u16:0b00001_1_01_0000_0001 ++ u16:0b00001_1_01_0000_0001 ++ u16:0b00001_1_01_0000_0001) as TestAxiRamData, mask: TestAxiRamMask:0xFF }, + // AXI addr: 0x308 ^ ^ ^ + // Huffman-coded stream #4 Huffman-coded stream #3 Huffman-coded stream #2 ]; -const TEST_DECODED_LITERALS_0 = common::LiteralsData[3]:[ - common::LiteralsData { - data: common::LitData:0x458A_D147_47D2_8A47, - length: common::LitLength:8, - last: false, +const TEST_CTRL: TestCtrl[4] = [ + // Literals #0 + TestCtrl { + base_addr: uN[TEST_AXI_RAM_ADDR_W]:0x0, + len: uN[TEST_AXI_RAM_ADDR_W]:0x6, + new_config: true, + multi_stream: false, + id: u32:0, + literals_last: false, }, - common::LiteralsData { - data: common::LitData:0x4141_8D47_8AD2_478A, - length: common::LitLength:8, - last: false, + + // Literals #1 + TestCtrl { + base_addr: uN[TEST_AXI_RAM_ADDR_W]:0x100, + len: uN[TEST_AXI_RAM_ADDR_W]:0x2, + new_config: false, + multi_stream: false, + id: u32:1, + literals_last: false, }, - common::LiteralsData { - data: common::LitData:0x478A_41D2_478A, - length: common::LitLength:6, - last: true, + + // Literals #2 + TestCtrl { + base_addr: uN[TEST_AXI_RAM_ADDR_W]:0x200, + len: uN[TEST_AXI_RAM_ADDR_W]:0x12, + new_config: true, + multi_stream: true, + id: u32:2, + literals_last: false, + }, + + // Literals #3 + TestCtrl { + base_addr: uN[TEST_AXI_RAM_ADDR_W]:0x300, + len: uN[TEST_AXI_RAM_ADDR_W]:0xE, + new_config: false, + multi_stream: true, + id: u32:3, + literals_last: true, }, ]; -// data for test case #1 (same config) -const TEST_CTRL_1 = TestCtrl { - base_addr: uN[TEST_AXI_ADDR_W]:0x20, - len: uN[TEST_AXI_ADDR_W]:0x4, - new_config: false -}; - -const TEST_DATA_LEN_1 = u32:32; -const TEST_DATA_1 = ( - u8:0b0010_1_010 ++ - u8:0b000_0_000 ++ - u8:0b1_1_000000 ++ - u8:0b001_011_1_1 -); - -const TEST_DECODED_LITERALS_1 = common::LiteralsData[2]:[ - common::LiteralsData { - data: common::LitData:0x47AC_1247_4747_47D2, - length: common::LitLength:8, +const TEST_DECODED_LITERALS = common::LiteralsDataWithSync[10]:[ + // Literals #0 + common::LiteralsDataWithSync { + data: common::LitData:0x0504_0100, + length: common::LitLength:4, + last: true, + id: u32:0, + literals_last: false, + }, + // Literals #1 + common::LiteralsDataWithSync { + data: common::LitData:0x0001_0405, + length: common::LitLength:4, + last: true, + id: u32:1, + literals_last: false, + }, + // Literals #2 + common::LiteralsDataWithSync { + data: common::LitData:0x0504_0100, + length: common::LitLength:4, + last: false, + id: u32:2, + literals_last: false, + }, + common::LiteralsDataWithSync { + data: common::LitData:0x0504_0100, + length: common::LitLength:4, + last: false, + id: u32:2, + literals_last: false, + }, + common::LiteralsDataWithSync { + data: common::LitData:0x0504_0100, + length: common::LitLength:4, last: false, + id: u32:2, + literals_last: false, }, - common::LiteralsData { - data: common::LitData:0x8A, - length: common::LitLength:1, + common::LiteralsDataWithSync { + data: common::LitData:0x0504_0100, + length: common::LitLength:4, last: true, + id: u32:2, + literals_last: false, }, -]; - -// Data for test case #2 -// Source: Example from RFC 8878, 4.2.2. Huffman-Coded Streams -// https://datatracker.ietf.org/doc/html/rfc8878#huffman_coded_streams -// Weights taken from Table 25 -// Bitstream fixed to encode literal sequence "0145" -// See https://www.rfc-editor.org/errata/eid8195 - -const TEST_CTRL_2 = TestCtrl { - base_addr: uN[TEST_AXI_ADDR_W]:0x0, - len: uN[TEST_AXI_ADDR_W]:0x2, - new_config: true -}; - -const TEST_DATA_LEN_2 = u32:16; -const TEST_DATA_2 = u64:0b00000001_00001101; - -// code symbol length weight -// N/A 0x03 0 0 -// 0b0000 0x04 4 1 -// 0b0001 0x05 4 1 -// 0b001 0x02 3 2 -// 0b01 0x01 2 3 -// 0b1 0x00 1 4 - -const TEST_WEIGHT_MEMORY_2 = TestRamEntry[32]:[ - // x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF - TestRamEntry:0x_4__3__2__0__1__1__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x0x - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x1x - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x2x - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x3x - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x4x - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x5x - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x6x - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x7x - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x8x - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x9x - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xAx - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xBx - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xCx - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xDx - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xEx - TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xFx -]; - -const TEST_DECODED_LITERALS_2 = common::LiteralsData[1]:[ - common::LiteralsData { + // Literals #3 + common::LiteralsDataWithSync { + data: common::LitData:0x0504_0100, + length: common::LitLength:4, + last: false, + id: u32:3, + literals_last: true, + }, + common::LiteralsDataWithSync { + data: common::LitData:0x0504_0100, + length: common::LitLength:4, + last: false, + id: u32:3, + literals_last: true, + }, + common::LiteralsDataWithSync { + data: common::LitData:0x0504_0100, + length: common::LitLength:4, + last: false, + id: u32:3, + literals_last: true, + }, + common::LiteralsDataWithSync { data: common::LitData:0x0504_0100, length: common::LitLength:4, last: true, + id: u32:3, + literals_last: true, }, ]; + #[test_proc] proc HuffmanLiteralsDecoder_test { + type Status = HuffmanLiteralsDecoderStatus; + terminator: chan out; ctrl_s: chan out; - decoded_literals_r: chan in; - axi_ar_r: chan in; - axi_r_s: chan out; - ram_read_req_r: chan in; - ram_read_resp_s: chan out; + resp_r: chan in; + decoded_literals_r: chan in; + ram_wr_req_huffman_s : chan out; + ram_wr_resp_huffman_r : chan in; + ram_wr_req_jump_table_s : chan out; + ram_wr_resp_jump_table_r : chan in; + ram_wr_req_huffman_weights_header_s : chan out; + ram_wr_resp_huffman_weights_header_r : chan in; + ram_wr_req_huffman_weights_raw_s : chan out; + ram_wr_resp_huffman_weights_raw_r : chan in; + ram_wr_req_huffman_weights_fse_s : chan out; + ram_wr_resp_huffman_weights_fse_r : chan in; config (terminator: chan out) { let (ctrl_s, ctrl_r) = chan("ctrl"); - let (decoded_literals_s, decoded_literals_r) = chan("decoded_literals"); + let (resp_s, resp_r) = chan("resp"); + let (decoded_literals_s, decoded_literals_r) = chan("decoded_literals"); let (axi_ar_s, axi_ar_r) = chan("axi_ar"); let (axi_r_s, axi_r_r) = chan("axi_r"); - let (ram_read_req_s, ram_read_req_r) = chan("ram_read_req"); - let (ram_read_resp_s, ram_read_resp_r) = chan("ram_read_resp"); - let (weights_pow_sum_loopback_s, weights_pow_sum_loopback_r) = chan("weights_pow_sum_loopback"); + let (jump_table_axi_ar_s, jump_table_axi_ar_r) = chan("jump_table_axi_ar"); + let (jump_table_axi_r_s, jump_table_axi_r_r) = chan("jump_table_axi_r"); + let (weights_header_dec_axi_ar_s, weights_header_dec_axi_ar_r) = chan("weights_header_dec_axi_ar"); + let (weights_header_dec_axi_r_s, weights_header_dec_axi_r_r) = chan("weights_header_dec_axi_r"); + let (weights_raw_dec_axi_ar_s, weights_raw_dec_axi_ar_r) = chan("weights_raw_dec_axi_ar"); + let (weights_raw_dec_axi_r_s, weights_raw_dec_axi_r_r) = chan("weights_raw_dec_axi_r"); + let (weights_fse_dec_axi_ar_s, weights_fse_dec_axi_ar_r) = chan("weights_fse_dec_axi_ar"); + let (weights_fse_dec_axi_r_s, weights_fse_dec_axi_r_r) = chan("weights_fse_dec_axi_r"); + + // weights internal memory + let (weights_ram_rd_req_s, weights_ram_rd_req_r) = chan("weights_ram_rd_req"); + let (weights_ram_rd_resp_s, weights_ram_rd_resp_r) = chan("weights_ram_rd_resp"); + let (weights_ram_wr_req_s, weights_ram_wr_req_r) = chan("weights_ram_wr_req"); + let (weights_ram_wr_resp_s, weights_ram_wr_resp_r) = chan("weights_ram_wr_resp"); - spawn HuffmanLiteralsDecoder( - ctrl_r, decoded_literals_s, + // prescan internal memory + let (prescan_ram_wr_req_s, prescan_ram_wr_req_r) = chan("prescan_ram_wr_req"); + let (prescan_ram_wr_resp_s, prescan_ram_wr_resp_r) = chan("prescan_ram_wr_resp"); + let (prescan_ram_rd_req_s, prescan_ram_rd_req_r) = chan("prescan_ram_rd_req"); + let (prescan_ram_rd_resp_s, prescan_ram_rd_resp_r) = chan("prescan_ram_rd_resp"); + + spawn HuffmanLiteralsDecoder< + TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_DEST_W, + TEST_WEIGHTS_RAM_ADDR_WIDTH, TEST_WEIGHTS_RAM_DATA_WIDTH, TEST_WEIGHTS_RAM_NUM_PARTITIONS, + TEST_PRESCAN_RAM_ADDR_WIDTH, TEST_PRESCAN_RAM_DATA_WIDTH, TEST_PRESCAN_RAM_NUM_PARTITIONS + >( + ctrl_r, resp_s, decoded_literals_s, axi_ar_s, axi_r_r, - ram_read_req_s, ram_read_resp_r, - weights_pow_sum_loopback_s, weights_pow_sum_loopback_r, + jump_table_axi_ar_s, jump_table_axi_r_r, + weights_header_dec_axi_ar_s, weights_header_dec_axi_r_r, + weights_raw_dec_axi_ar_s, weights_raw_dec_axi_r_r, + weights_fse_dec_axi_ar_s, weights_fse_dec_axi_r_r, + weights_ram_rd_req_s, weights_ram_rd_resp_r, + weights_ram_wr_req_s, weights_ram_wr_resp_r, + prescan_ram_rd_req_s, prescan_ram_rd_resp_r, + prescan_ram_wr_req_s, prescan_ram_wr_resp_r, ); - ( - terminator, - ctrl_s, decoded_literals_r, - axi_ar_r, axi_r_s, - ram_read_req_r, ram_read_resp_s, - ) - } - - init { } - - next (state: ()) { - let tok = join(); + // Mock RAM for HuffmanLiteralsDecoder MemReader + let (ram_rd_req_huffman_s, ram_rd_req_huffman_r) = chan("ram_rd_req_huffman"); + let (ram_rd_resp_huffman_s, ram_rd_resp_huffman_r) = chan("ram_rd_resp_huffman"); + let (ram_wr_req_huffman_s, ram_wr_req_huffman_r) = chan("ram_wr_req_huffman"); + let (ram_wr_resp_huffman_s, ram_wr_resp_huffman_r) = chan("ram_wr_resp_huffman"); + + spawn ram::RamModel< + TEST_AXI_RAM_MODEL_DATA_WIDTH, + TEST_AXI_RAM_MODEL_SIZE, + TEST_AXI_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_AXI_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_AXI_RAM_MODEL_INITIALIZED, + TEST_AXI_RAM_MODEL_ASSERT_VALID_READ, + TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + ram_rd_req_huffman_r, ram_rd_resp_huffman_s, ram_wr_req_huffman_r, ram_wr_resp_huffman_s + ); - trace_fmt!("Test Case #1"); - // send ctrl - let tok = send(tok, ctrl_s, TEST_CTRL_0); - trace_fmt!("Sent #1 ctrl {:#x}", TEST_CTRL_0); + spawn axi_ram::AxiRamReader< + TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, + TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + axi_ar_r, axi_r_s, + ram_rd_req_huffman_s, ram_rd_resp_huffman_r + ); - // receive RAM read requests and send responses - trace_fmt!("Sending weight memory content"); - let tok = for (_, tok): (u32, token) in range(u32:0, u32:2) { - for (i, tok):(u32, token) in range(u32:0, array_size(TEST_WEIGHT_MEMORY_0)) { - let (tok, ram_read_req) = recv(tok, ram_read_req_r); - trace_fmt!("Received #{} ReadReq {:#x}", i + u32:1, ram_read_req); + // Mock RAM for Huffman Jump Table decoder MemReader + let (ram_rd_req_jump_table_s, ram_rd_req_jump_table_r) = chan("ram_rd_req_jump_table"); + let (ram_rd_resp_jump_table_s, ram_rd_resp_jump_table_r) = chan("ram_rd_resp_jump_table"); + let (ram_wr_req_jump_table_s, ram_wr_req_jump_table_r) = chan("ram_wr_req_jump_table"); + let (ram_wr_resp_jump_table_s, ram_wr_resp_jump_table_r) = chan("ram_wr_resp_jump_table"); + + spawn ram::RamModel< + TEST_AXI_RAM_MODEL_DATA_WIDTH, + TEST_AXI_RAM_MODEL_SIZE, + TEST_AXI_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_AXI_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_AXI_RAM_MODEL_INITIALIZED, + TEST_AXI_RAM_MODEL_ASSERT_VALID_READ, + TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + ram_rd_req_jump_table_r, ram_rd_resp_jump_table_s, ram_wr_req_jump_table_r, ram_wr_resp_jump_table_s + ); - let read_resp = TestReadResp { - data: TEST_WEIGHT_MEMORY_0[ram_read_req.addr] as u32, - }; + spawn axi_ram::AxiRamReader< + TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, + TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + jump_table_axi_ar_r, jump_table_axi_r_s, + ram_rd_req_jump_table_s, ram_rd_resp_jump_table_r + ); - let tok = send(tok, ram_read_resp_s, read_resp); - trace_fmt!("Sent #{} ReadResp {:#x}", i + u32:1, read_resp); + // Mock RAM for HuffmanWeights header decoder MemReader + let (ram_rd_req_huffman_weights_header_s, ram_rd_req_huffman_weights_header_r) = chan("ram_rd_req_huffman_weights_header"); + let (ram_rd_resp_huffman_weights_header_s, ram_rd_resp_huffman_weights_header_r) = chan("ram_rd_resp_huffman_weights_header"); + let (ram_wr_req_huffman_weights_header_s, ram_wr_req_huffman_weights_header_r) = chan("ram_wr_req_huffman_weights_header"); + let (ram_wr_resp_huffman_weights_header_s, ram_wr_resp_huffman_weights_header_r) = chan("ram_wr_resp_huffman_weights_header"); + + spawn ram::RamModel< + TEST_AXI_RAM_MODEL_DATA_WIDTH, + TEST_AXI_RAM_MODEL_SIZE, + TEST_AXI_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_AXI_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_AXI_RAM_MODEL_INITIALIZED, + TEST_AXI_RAM_MODEL_ASSERT_VALID_READ, + TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + ram_rd_req_huffman_weights_header_r, ram_rd_resp_huffman_weights_header_s, ram_wr_req_huffman_weights_header_r, ram_wr_resp_huffman_weights_header_s + ); - tok - }(tok) - }(tok); + spawn axi_ram::AxiRamReader< + TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, + TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + weights_header_dec_axi_ar_r, weights_header_dec_axi_r_s, + ram_rd_req_huffman_weights_header_s, ram_rd_resp_huffman_weights_header_r + ); - // receive Axi requests and send responses - trace_fmt!("Sending data from AXI"); - const AXI_READS_NUM = (TEST_DATA_LEN_0 + u32:7) / u32:8; - let tok = for (i, tok):(u32, token) in range(u32:0, AXI_READS_NUM) { - let expected_axi_ar = TestAxiAr { - addr: TEST_CTRL_0.base_addr + (AXI_READS_NUM - u32:1 - i) as uN[TEST_AXI_ADDR_W], - ..zero!() - }; - let (tok, axi_ar) = recv(tok, axi_ar_r); - trace_fmt!("Received #{} AxiAr {:#x}", i + u32:1, axi_ar); - assert_eq(expected_axi_ar, axi_ar); - - let axi_r = TestAxiR { - id: axi_ar.id, - data: (TEST_DATA_0 >> (u32:8 * i)) as u32, - resp: axi::AxiReadResp::OKAY, - last: i == (AXI_READS_NUM - u32:1), - }; - - let tok = send(tok, axi_r_s, axi_r); - trace_fmt!("Sent #{} AxiR {:#x}", i + u32:1, axi_r); + // Mock RAM for HuffmanWeights raw decoder MemReader + let (ram_rd_req_huffman_weights_raw_s, ram_rd_req_huffman_weights_raw_r) = chan("ram_rd_req_huffman_weights_raw"); + let (ram_rd_resp_huffman_weights_raw_s, ram_rd_resp_huffman_weights_raw_r) = chan("ram_rd_resp_huffman_weights_raw"); + let (ram_wr_req_huffman_weights_raw_s, ram_wr_req_huffman_weights_raw_r) = chan("ram_wr_req_huffman_weights_raw"); + let (ram_wr_resp_huffman_weights_raw_s, ram_wr_resp_huffman_weights_raw_r) = chan("ram_wr_resp_huffman_weights_raw"); + + spawn ram::RamModel< + TEST_AXI_RAM_MODEL_DATA_WIDTH, + TEST_AXI_RAM_MODEL_SIZE, + TEST_AXI_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_AXI_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_AXI_RAM_MODEL_INITIALIZED, + TEST_AXI_RAM_MODEL_ASSERT_VALID_READ, + TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + ram_rd_req_huffman_weights_raw_r, ram_rd_resp_huffman_weights_raw_s, ram_wr_req_huffman_weights_raw_r, ram_wr_resp_huffman_weights_raw_s + ); - tok - }(tok); + spawn axi_ram::AxiRamReader< + TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, + TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + weights_raw_dec_axi_ar_r, weights_raw_dec_axi_r_s, + ram_rd_req_huffman_weights_raw_s, ram_rd_resp_huffman_weights_raw_r + ); - // receive decoded literals - let tok = for ((i, test_decoded_literals), tok):((u32, common::LiteralsData), token) in enumerate(TEST_DECODED_LITERALS_0) { - let (tok, decoded_literals) = recv(tok, decoded_literals_r); - trace_fmt!("Received #{} decoded literals {:#x}", i + u32:1, decoded_literals); - assert_eq(test_decoded_literals, decoded_literals); - tok - }(tok); + // Mock RAM for HuffmanWeights fse decoder MemReader + let (ram_rd_req_huffman_weights_fse_s, ram_rd_req_huffman_weights_fse_r) = chan("ram_rd_req_huffman_weights_fse"); + let (ram_rd_resp_huffman_weights_fse_s, ram_rd_resp_huffman_weights_fse_r) = chan("ram_rd_resp_huffman_weights_fse"); + let (ram_wr_req_huffman_weights_fse_s, ram_wr_req_huffman_weights_fse_r) = chan("ram_wr_req_huffman_weights_fse"); + let (ram_wr_resp_huffman_weights_fse_s, ram_wr_resp_huffman_weights_fse_r) = chan("ram_wr_resp_huffman_weights_fse"); + + spawn ram::RamModel< + TEST_AXI_RAM_MODEL_DATA_WIDTH, + TEST_AXI_RAM_MODEL_SIZE, + TEST_AXI_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_AXI_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_AXI_RAM_MODEL_INITIALIZED, + TEST_AXI_RAM_MODEL_ASSERT_VALID_READ, + TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + ram_rd_req_huffman_weights_fse_r, ram_rd_resp_huffman_weights_fse_s, ram_wr_req_huffman_weights_fse_r, ram_wr_resp_huffman_weights_fse_s + ); - trace_fmt!("Test Case #2"); - // send ctrl - let tok = send(tok, ctrl_s, TEST_CTRL_1); - trace_fmt!("Sent #2 ctrl {:#x}", TEST_CTRL_1); - - // receive Axi requests and send responses - trace_fmt!("Sending data from AXI"); - const AXI_READS_NUM = (TEST_DATA_LEN_1 + u32:7) / u32:8; - let tok = for (i, tok):(u32, token) in range(u32:0, AXI_READS_NUM) { - let expected_axi_ar = TestAxiAr { - addr: TEST_CTRL_1.base_addr + (AXI_READS_NUM - u32:1 - i) as uN[TEST_AXI_ADDR_W], - ..zero!() - }; - let (tok, axi_ar) = recv(tok, axi_ar_r); - trace_fmt!("Received #{} AxiAr {:#x}", i + u32:1, axi_ar); - assert_eq(expected_axi_ar, axi_ar); - - let axi_r = TestAxiR { - id: axi_ar.id, - data: (TEST_DATA_1 >> (u32:8 * i)) as u32, - resp: axi::AxiReadResp::OKAY, - last: i == (AXI_READS_NUM - u32:1), - }; - - let tok = send(tok, axi_r_s, axi_r); - trace_fmt!("Sent #{} AxiR {:#x}", i + u32:1, axi_r); + spawn axi_ram::AxiRamReader< + TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, + TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + weights_fse_dec_axi_ar_r, weights_fse_dec_axi_r_s, + ram_rd_req_huffman_weights_fse_s, ram_rd_resp_huffman_weights_fse_r + ); - tok - }(tok); + spawn ram::RamModel< + TEST_WEIGHTS_RAM_DATA_WIDTH, TEST_WEIGHTS_RAM_SIZE, TEST_WEIGHTS_WORD_PARTITION_SIZE + >( + weights_ram_rd_req_r, weights_ram_rd_resp_s, + weights_ram_wr_req_r, weights_ram_wr_resp_s, + ); - // receive decoded literals - let tok = for ((i, test_decoded_literals), tok):((u32, common::LiteralsData), token) in enumerate(TEST_DECODED_LITERALS_1) { - let (tok, decoded_literals) = recv(tok, decoded_literals_r); - trace_fmt!("Received #{} decoded literals {:#x}", i + u32:1, decoded_literals); - assert_eq(test_decoded_literals, decoded_literals); - tok - }(tok); + spawn ram::RamModel< + TEST_PRESCAN_RAM_DATA_WIDTH, TEST_PRESCAN_RAM_SIZE, TEST_PRESCAN_WORD_PARTITION_SIZE + >( + prescan_ram_rd_req_r, prescan_ram_rd_resp_s, + prescan_ram_wr_req_r, prescan_ram_wr_resp_s, + ); - trace_fmt!("Test Case #3"); - // send ctrl - let tok = send(tok, ctrl_s, TEST_CTRL_2); - trace_fmt!("Sent #3 ctrl {:#x}", TEST_CTRL_2); + ( + terminator, + ctrl_s, resp_r, decoded_literals_r, + ram_wr_req_huffman_s, ram_wr_resp_huffman_r, + ram_wr_req_jump_table_s, ram_wr_resp_jump_table_r, + ram_wr_req_huffman_weights_header_s, ram_wr_resp_huffman_weights_header_r, + ram_wr_req_huffman_weights_raw_s, ram_wr_resp_huffman_weights_raw_r, + ram_wr_req_huffman_weights_fse_s, ram_wr_resp_huffman_weights_fse_r + ) - // receive RAM read requests and send responses - trace_fmt!("Sending weight memory content"); - let tok = for (_, tok): (u32, token) in range(u32:0, u32:2) { - for (i, tok):(u32, token) in range(u32:0, array_size(TEST_WEIGHT_MEMORY_2)) { - let (tok, ram_read_req) = recv(tok, ram_read_req_r); - trace_fmt!("Received #{} ReadReq {:#x}", i + u32:1, ram_read_req); + } - let read_resp = TestReadResp { - data: TEST_WEIGHT_MEMORY_2[ram_read_req.addr] as u32, - }; + init { } - let tok = send(tok, ram_read_resp_s, read_resp); - trace_fmt!("Sent #{} ReadResp {:#x}", i + u32:1, read_resp); + next (state: ()) { + let tok = join(); - tok - }(tok) + trace_fmt!("Filling system memory mock"); + let tok = for ((i, mem_req), tok):((u32, TestAxiRamWrReq), token) in enumerate(TEST_MEMORY) { + trace_fmt!("Sent memory write request #{}: {:#x}", i + u32:1, mem_req); + let tok = send(tok, ram_wr_req_huffman_s, mem_req); + let (tok, _) = recv(tok, ram_wr_resp_huffman_r); + let tok = send(tok, ram_wr_req_jump_table_s, mem_req); + let (tok, _) = recv(tok, ram_wr_resp_jump_table_r); + let tok = send(tok, ram_wr_req_huffman_weights_header_s, mem_req); + let (tok, _) = recv(tok, ram_wr_resp_huffman_weights_header_r); + let tok = send(tok, ram_wr_req_huffman_weights_raw_s, mem_req); + let (tok, _) = recv(tok, ram_wr_resp_huffman_weights_raw_r); + let tok = send(tok, ram_wr_req_huffman_weights_fse_s, mem_req); + let (tok, _) = recv(tok, ram_wr_resp_huffman_weights_fse_r); + tok }(tok); + trace_fmt!("Filling system memory mock done"); - // receive Axi requests and send responses - trace_fmt!("Sending data from AXI"); - const AXI_READS_NUM = (TEST_DATA_LEN_2 + u32:7) / u32:8; - let tok = for (i, tok):(u32, token) in range(u32:0, AXI_READS_NUM) { - let expected_axi_ar = TestAxiAr { - addr: TEST_CTRL_2.base_addr + (AXI_READS_NUM - u32:1 - i) as uN[TEST_AXI_ADDR_W], - ..zero!() - }; - let (tok, axi_ar) = recv(tok, axi_ar_r); - trace_fmt!("Received #{} AxiAr {:#x}", i + u32:1, axi_ar); - assert_eq(expected_axi_ar, axi_ar); - - let axi_r = TestAxiR { - id: axi_ar.id, - data: (TEST_DATA_2 >> (u32:8 * i)) as u32, - resp: axi::AxiReadResp::OKAY, - last: i == (AXI_READS_NUM - u32:1), - }; - - let tok = send(tok, axi_r_s, axi_r); - trace_fmt!("Sent #{} AxiR {:#x}", i + u32:1, axi_r); - + // Send Huffman Literals decoding requests + let tok = for ((i, ctrl_req), tok):((u32, TestCtrl), token) in enumerate(TEST_CTRL) { + let tok = send(tok, ctrl_s, ctrl_req); + trace_fmt!("Sent #{} ctrl {:#x}", i + u32:1, ctrl_req); tok }(tok); // receive decoded literals - let tok = for ((i, test_decoded_literals), tok):((u32, common::LiteralsData), token) in enumerate(TEST_DECODED_LITERALS_2) { + let tok = for ((i, expected_decoded_literals), tok):((u32, common::LiteralsDataWithSync), token) in enumerate(TEST_DECODED_LITERALS) { + trace_fmt!("Waiting for #{} decoded literals", i + u32:1); let (tok, decoded_literals) = recv(tok, decoded_literals_r); trace_fmt!("Received #{} decoded literals {:#x}", i + u32:1, decoded_literals); - assert_eq(test_decoded_literals, decoded_literals); + assert_eq(expected_decoded_literals, decoded_literals); + + if (decoded_literals.last) { trace_fmt!("Waiting for #{} decoding response", i + u32:1); } else {}; + let (tok, resp) = recv_if(tok, resp_r, decoded_literals.last, zero!()); + if (decoded_literals.last) { trace_fmt!("Received #{} decoding response {:#x}", i + u32:1, resp); } else {}; + assert_eq(TestResp {status: Status::OKAY}, resp); + tok }(tok); send(tok, terminator, true); } } + +// TODO: implement tests with the following Huffman Tree +//const TEST_DATA_LEN_0 = u32:64; +//const TEST_DATA_0 = ( +// u8:0b001_1_010_0 ++ // 0x34 <- last byte in the memory +// u8:0b11_1_1_0001 ++ // 0xF1 +// u8:0b01_010_000 ++ // 0x50 +// u8:0b001_010_1_0 ++ // 0x2A +// u8:0b11_010_1_00 ++ // 0xD4 +// u8:0b0100_001_0 ++ // 0x42 +// u8:0b01_010_1_01 ++ // 0x55 +// u8:0b1_001_010_1 // 0x95 <- first byte in the memory +//); +// +//// code symbol length weight +//// 0b1 0x47 1 9 +//// 0b001 0x41 3 7 +//// 0b010 0x8A 3 7 +//// 0b011 0xD2 3 7 +//// 0b000001 0x45 6 4 +//// 0b000010 0x7A 6 4 +//// 0b000011 0x89 6 4 +//// 0b000100 0x8D 6 4 +//// 0b000101 0xD1 6 4 +//// 0b000110 0xD3 6 4 +//// 0b000111 0xDA 6 4 +//// 0b000000000 0x12 9 1 +//// 0b000000001 0x8F 9 1 +//// 0b000000010 0xAC 9 1 +//// 0b000000011 0xD4 9 1 +//// 0b000000100 0xD7 9 1 +//// 0b000000101 0xDB 9 1 +//// 0b000000110 0xDE 9 1 +//// 0b000000111 0xFE 9 1 +// +//const TEST_WEIGHT_MEMORY_0 = TestRamEntry[32]:[ +// // x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF +// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x0x +// TestRamEntry:0x_0__0__1__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x1x +// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x2x +// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x3x +// TestRamEntry:0x_0__7__0__0__0__4__0__9, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x4x +// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x5x +// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x6x +// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__4__0__0__0__0__0, // 0x7x +// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__4__7__0__0__4__0__1, // 0x8x +// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x9x +// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__1__0__0__0, // 0xAx +// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xBx +// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xCx +// TestRamEntry:0x_0__4__7__4__1__0__0__1, TestRamEntry:0x_0__0__4__1__0__0__1__0, // 0xDx +// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xEx +// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__1__0, // 0xFx +//]; +// +//const TEST_DECODED_LITERALS_0 = common::LiteralsDataWithSync[3]:[ +// common::LiteralsDataWithSync { +// data: common::LitData:0x458A_D147_47D2_8A47, +// length: common::LitLength:8, +// last: false, +// id: u32:0, +// literals_last: false, +// }, +// common::LiteralsDataWithSync { +// data: common::LitData:0x4141_8D47_8AD2_478A, +// length: common::LitLength:8, +// last: false, +// id: u32:0, +// literals_last: false, +// }, +// common::LiteralsDataWithSync { +// data: common::LitData:0x478A_41D2_478A, +// length: common::LitLength:6, +// last: true, +// id: u32:0, +// literals_last: false, +// }, +//]; +// +//// data for test case #1 (same config) +//const TEST_CTRL_1 = TestCtrl { +// base_addr: uN[TEST_AXI_RAM_ADDR_W]:0x20, +// len: uN[TEST_AXI_RAM_ADDR_W]:0x4, +// new_config: false, +// multi_stream: false, +// id: u32:1, +// literals_last: true, +//}; +// +//const TEST_DATA_LEN_1 = u32:32; +//const TEST_DATA_1 = ( +// u8:0b001_011_1_1 ++ // 0x2F <- last byte in the memory +// u8:0b1_1_000000 ++ // 0xC0 +// u8:0b000_0_000 ++ // 0x00 +// u8:0b0010_1_010 // 0x2A <- first byte in the memory +//); +// +//const TEST_DECODED_LITERALS_1 = common::LiteralsDataWithSync[2]:[ +// common::LiteralsDataWithSync { +// data: common::LitData:0x47AC_1247_4747_47D2, +// length: common::LitLength:8, +// last: false, +// id: u32:1, +// literals_last: true, +// }, +// common::LiteralsDataWithSync { +// data: common::LitData:0x8A, +// length: common::LitLength:1, +// last: true, +// id: u32:1, +// literals_last: true, +// }, +//]; + diff --git a/xls/modules/zstd/huffman_weights_dec.x b/xls/modules/zstd/huffman_weights_dec.x new file mode 100644 index 0000000000..4dd04173b8 --- /dev/null +++ b/xls/modules/zstd/huffman_weights_dec.x @@ -0,0 +1,1016 @@ +// 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 as ram; +import xls.modules.zstd.huffman_prescan as huffman_prescan; +import xls.modules.zstd.memory.axi as axi; +import xls.modules.zstd.memory.axi_ram as axi_ram; +import xls.modules.zstd.memory.mem_reader as mem_reader; +import xls.modules.zstd.ram_mux as ram_mux; + +struct HuffmanRawWeightsDecoderReq { + addr: uN[AXI_ADDR_W], + n_symbols: u8, +} + +enum HuffmanRawWeightsDecoderStatus: u1 { + OKAY = 0, + ERROR = 1, +} + +struct HuffmanRawWeightsDecoderResp { + status: HuffmanRawWeightsDecoderStatus, +} + +enum HuffmanRawWeightsDecoderFSM : u2 { + IDLE = 0, + DECODING = 1, + FILL_ZERO = 2, + RESP = 3, +} + +struct HuffmanRawWeightsDecoderState< + AXI_ADDR_W: u32, AXI_DATA_W: u32, + WEIGHTS_RAM_ADDR_W: u32, WEIGHTS_RAM_DATA_W: u32, + BUFF_LEN: u32 = {AXI_DATA_W + WEIGHTS_RAM_DATA_W}, + BUFF_LEN_LOG2: u32 = {std::clog2(BUFF_LEN + u32:1)}, +> { + fsm: HuffmanRawWeightsDecoderFSM, + req: HuffmanRawWeightsDecoderReq, + data_decoded: uN[AXI_ADDR_W], + ram_addr: uN[WEIGHTS_RAM_ADDR_W], + buffer: uN[BUFF_LEN], + buffer_len: uN[BUFF_LEN_LOG2], + ram_wr_resp_to_handle: u4, + sum: u32, // The sum of 2^(weight-1) from HTD +} + +proc HuffmanRawWeightsDecoder< + AXI_ADDR_W: u32, AXI_DATA_W: u32, + WEIGHTS_RAM_ADDR_W: u32, WEIGHTS_RAM_DATA_W: u32, + WEIGHTS_RAM_NUM_PARTITIONS: u32, + BUFF_LEN: u32 = {AXI_DATA_W + WEIGHTS_RAM_DATA_W}, + BUFF_LEN_LOG2: u32 = {std::clog2(BUFF_LEN + u32:1)}, +> { + type Req = HuffmanRawWeightsDecoderReq; + type Resp = HuffmanRawWeightsDecoderResp; + type Status = HuffmanRawWeightsDecoderStatus; + type FSM = HuffmanRawWeightsDecoderFSM; + type State = HuffmanRawWeightsDecoderState< + AXI_ADDR_W, AXI_DATA_W, WEIGHTS_RAM_ADDR_W, WEIGHTS_RAM_DATA_W, + BUFF_LEN, BUFF_LEN_LOG2, + >; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + + type WeightsRamWrReq = ram::WriteReq; + type WeightsRamWrResp = ram::WriteResp; + + // Control + req_r: chan in; + resp_s: chan out; + + // MemReader interface for fetching Huffman Tree Description + mem_rd_req_s: chan out; + mem_rd_resp_r: chan in; + + weights_ram_wr_req_s: chan out; + weights_ram_wr_resp_r: chan in; + + init { + zero!() + } + + config( + // Control + req_r: chan in, + resp_s: chan out, + + // MemReader interface for fetching Huffman Tree Description + mem_rd_req_s: chan out, + mem_rd_resp_r: chan in, + + // RAM Write interface (goes to Huffman Weights Memory) + weights_ram_wr_req_s: chan out, + weights_ram_wr_resp_r: chan in, + ) { + + ( + req_r, resp_s, + mem_rd_req_s, mem_rd_resp_r, + weights_ram_wr_req_s, weights_ram_wr_resp_r + ) + } + + next (state: State) { + let tok = join(); + + // [IDLE] + let (tok, req, req_valid) = recv_if_non_blocking(join(), req_r, state.fsm == FSM::IDLE, zero!()); + + // Fetch Data + let mem_rd_req = MemReaderReq { + addr: req.addr + uN[AXI_ADDR_W]:1, // skip header + length: ((req.n_symbols + u8:1) >> u32:1) as uN[AXI_ADDR_W], // ceil(number_of_symbols/2) + }; + let tok = send_if(tok, mem_rd_req_s, req_valid, mem_rd_req); + + // [DECODING] + let buffer = state.buffer; + let buffer_len = state.buffer_len; + + // Buffer AXI data + let do_recv_data = state.fsm == FSM::DECODING && buffer_len < (WEIGHTS_RAM_DATA_W as uN[BUFF_LEN_LOG2]); + let (tok, mem_rd_resp, mem_rd_resp_valid) = recv_if_non_blocking(tok, mem_rd_resp_r, do_recv_data, zero!()); + if do_recv_data && mem_rd_resp_valid { + trace_fmt!("[RAW] Received MemReader response {:#x}", mem_rd_resp); + } else {}; + + // FIXME: support injecting the last implicit weight for Huffman Tree Descriptions larger + // than a single bus width + // + // ^ This is partially done with saving the sum calculated below in the state. + // When the last mem_reader packet is received (received the last chunk of HTD) + // We should calculate the final sum and use it to calculate the `last_weight` of the HTD + // and put it in a correct spot in the `weights` array + + // Calculate the last weight by summing 2^(weight - 1) + // for each weight read from the HTD (excluding 0's) + // The resulting sum must be subtracted from the next power of 2 after the resulting sum. + // The result is the weight of the last literal. + const MAX_WEIGHTS_IN_PACKET = AXI_DATA_W >> u32:2; + let weights = mem_rd_resp.data as u4[MAX_WEIGHTS_IN_PACKET]; + let sum = for (i, sum): (u32, u32) in u32:0..MAX_WEIGHTS_IN_PACKET { + if (weights[i] != u4:0) { + sum + (u32:1 << (weights[i] - u4:1)) + } else { + sum + } + } (state.sum); + + let state = if (mem_rd_resp_valid) { + State { + sum: sum, + ..state + } + } else { + state + }; + + let next_power = u32:1 << std::clog2(state.sum); + let last_weight = (next_power - state.sum) as u4; + + // It is required to change the ordering of the weights. + // Huffman literals decoder expects the weight of the first symbol + // as the most significant nibble at the most significant byte + // in the first cell of the WeightsMemory. + + // Inject the last weight, take into the acount the reverse + let weights = if (state.req.n_symbols > u8:0 && (mem_rd_resp_valid && mem_rd_resp.last)) { + trace_fmt!("[RAW] The sum of weight's powers of 2's: {}", state.sum); + trace_fmt!("[RAW] The last weight: {}", last_weight); + trace_fmt!("[RAW] Injected {:#x} into weights[{}]", last_weight, MAX_WEIGHTS_IN_PACKET as u8 - state.req.n_symbols); + update(weights, (MAX_WEIGHTS_IN_PACKET as u8 - (state.req.n_symbols % MAX_WEIGHTS_IN_PACKET as u8)), last_weight) + } else { + weights + }; + + let reversed_weights = match(AXI_DATA_W) { + u32:32 => ( + weights[7] ++ weights[6] ++ weights[5] ++ weights[4] ++ + weights[3] ++ weights[2] ++ weights[1] ++ weights[0] + ) as uN[AXI_DATA_W], + u32:64 => ( + weights[15] ++ weights[14] ++ weights[13] ++ weights[12] ++ + weights[11] ++ weights[10] ++ weights[9] ++ weights[8] ++ + weights[7] ++ weights[6] ++ weights[5] ++ weights[4] ++ + weights[3] ++ weights[2] ++ weights[1] ++ weights[0] + ) as uN[AXI_DATA_W], + _ => fail!("unsupported_axi_data_width", uN[AXI_DATA_W]:0), + }; + //trace_fmt!("[RAW] Reversed weights: {:#x}", reversed_weights); + //trace_fmt!("[RAW] BUFF_LEN: {:#x}", BUFF_LEN); + //trace_fmt!("[RAW] WEIGHTS_RAM_DATA_W: {:#x}", WEIGHTS_RAM_DATA_W); + //trace_fmt!("[RAW] AXI_DATA_W: {:#x}", AXI_DATA_W); + //trace_fmt!("[RAW] buffer_len: {:#x}", buffer_len); + + if do_recv_data && mem_rd_resp_valid { + trace_fmt!("[RAW] Weights: {:#x}", weights); + } else {}; + + let (buffer, buffer_len) = if do_recv_data && mem_rd_resp_valid { + //trace_fmt!("[RAW] shift: {:#x}", BUFF_LEN - AXI_DATA_W - buffer_len as u32); + ( + buffer | ((reversed_weights as uN[BUFF_LEN] << (BUFF_LEN - AXI_DATA_W - buffer_len as u32))), + buffer_len + (AXI_DATA_W as uN[BUFF_LEN_LOG2]), + ) + } else { + ( + buffer, + buffer_len, + ) + }; + // Send to RAM + let do_send_data = state.fsm == FSM::DECODING && buffer_len >= (WEIGHTS_RAM_DATA_W as uN[BUFF_LEN_LOG2]); + let weights_ram_wr_req = WeightsRamWrReq { + addr: state.ram_addr, + data: buffer[-(WEIGHTS_RAM_DATA_W as s32):] as uN[WEIGHTS_RAM_DATA_W], + mask: !uN[WEIGHTS_RAM_NUM_PARTITIONS]:0, + }; + let tok = send_if(tok, weights_ram_wr_req_s, do_send_data, weights_ram_wr_req); + if do_send_data { + trace_fmt!("[RAW] Buffer length: {}", buffer_len); + trace_fmt!("[RAW] Sent RAM write request {:#x}", weights_ram_wr_req); + } else {}; + + let (buffer, buffer_len, data_decoded) = if do_send_data { + ( + buffer << WEIGHTS_RAM_DATA_W, + buffer_len - (WEIGHTS_RAM_DATA_W as uN[BUFF_LEN_LOG2]), + WEIGHTS_RAM_DATA_W as uN[AXI_ADDR_W], + ) + } else { + ( + buffer, + buffer_len, + uN[AXI_ADDR_W]:0, + ) + }; + + // [FILL_ZERO] + let weights_ram_wr_req = WeightsRamWrReq { + data: uN[WEIGHTS_RAM_DATA_W]:0, + addr: state.ram_addr, + mask: !uN[WEIGHTS_RAM_NUM_PARTITIONS]:0, + }; + let rok = send_if(tok, weights_ram_wr_req_s, state.fsm == FSM::FILL_ZERO, weights_ram_wr_req); + + // [RESP] + let tok = send_if(tok, resp_s, state.fsm == FSM::RESP, zero!()); + + // Update state + let ram_wr_resp_to_handle = state.ram_wr_resp_to_handle + do_send_data as u4 + (state.fsm == FSM::FILL_ZERO) as u4; + let (tok, _, weights_ram_wr_resp_valid) = recv_if_non_blocking(tok, weights_ram_wr_resp_r, ram_wr_resp_to_handle > u4:0, zero!()); + let state = if weights_ram_wr_resp_valid { + State { + ram_wr_resp_to_handle: ram_wr_resp_to_handle - u4:1, + ..state + } + } else { + State { + ram_wr_resp_to_handle: ram_wr_resp_to_handle, + ..state + } + }; + + match state.fsm { + FSM::IDLE => { + if req_valid { + trace_fmt!("[RAW] Received decoding request {:#x}", req); + trace_fmt!("[RAW] Sent MemReader request {:#x}", mem_rd_req); + State { + fsm: FSM::DECODING, + req: req, + ..zero!() + } + } else { + state + } + }, + FSM::DECODING => { + let data_to_be_decoded = uN[AXI_ADDR_W]:8 * (((state.req.n_symbols as uN[AXI_ADDR_W] + uN[AXI_ADDR_W]:1) >> u32:1)); + trace_fmt!("[RAW] Decoded {} / {}", state.data_decoded + data_decoded, data_to_be_decoded); + trace_fmt!("[RAW] Buffer {:#x}", state.buffer); + if state.data_decoded + data_decoded < data_to_be_decoded { + State { + data_decoded: state.data_decoded + data_decoded, + ram_addr: state.ram_addr + (data_decoded / WEIGHTS_RAM_DATA_W as uN[AXI_ADDR_W]) as uN[WEIGHTS_RAM_ADDR_W], + buffer: buffer, + buffer_len: buffer_len, + ..state + } + } else { + State { + fsm: FSM::FILL_ZERO, + ram_addr: state.ram_addr + (data_decoded / WEIGHTS_RAM_DATA_W as uN[AXI_ADDR_W]) as uN[WEIGHTS_RAM_ADDR_W], + ..state + } + } + }, + FSM::FILL_ZERO => { + if state.ram_addr < !uN[WEIGHTS_RAM_ADDR_W]:0 { + trace_fmt!("[RAW] Filling with zeros {} / {}", state.ram_addr + uN[WEIGHTS_RAM_ADDR_W]:1, !uN[WEIGHTS_RAM_ADDR_W]:0); + State { + ram_addr: state.ram_addr + uN[WEIGHTS_RAM_ADDR_W]:1, + ..state + } + } else { + State { + fsm: FSM::RESP, + ..state + } + } + }, + FSM::RESP => { + State { + fsm: FSM::IDLE, + ..state + } + }, + _ => fail!("impossible_state", state) + } + } +} + +struct HuffmanFseWeightsDecoderReq { + addr: uN[AXI_ADDR_W] +} + +enum HuffmanFseWeightsDecoderStatus: u1 { + OKAY = 0, + ERROR = 1, +} + +struct HuffmanFseWeightsDecoderResp { + status: HuffmanFseWeightsDecoderStatus, +} + +struct HuffmanFseWeightsDecoderState { + placeholder: u1 +} + +proc HuffmanFseWeightsDecoder< + AXI_ADDR_W: u32, AXI_DATA_W: u32, + WEIGHTS_RAM_ADDR_W: u32, WEIGHTS_RAM_DATA_W: u32, + WEIGHTS_RAM_NUM_PARTITIONS: u32 +> { + type Req = HuffmanFseWeightsDecoderReq; + type Resp = HuffmanFseWeightsDecoderResp; + type Status = HuffmanFseWeightsDecoderStatus; + type State = HuffmanFseWeightsDecoderState; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + + type WeightsRamWrReq = ram::WriteReq; + type WeightsRamWrResp = ram::WriteResp; + + // Control + req_r: chan in; + resp_s: chan out; + + // MemReader interface for fetching Huffman Tree Description + mem_rd_req_s: chan out; + mem_rd_resp_r: chan in; + + weights_ram_wr_req_s: chan out; + weights_ram_wr_resp_r: chan in; + + init { + zero!() + } + + config( + // Control + req_r: chan in, + resp_s: chan out, + + // MemReader interface for fetching Huffman Tree Description + mem_rd_req_s: chan out, + mem_rd_resp_r: chan in, + + // RAM Write interface (goes to Huffman Weights Memory) + weights_ram_wr_req_s: chan out, + weights_ram_wr_resp_r: chan in, + ) { + + ( + req_r, resp_s, + mem_rd_req_s, mem_rd_resp_r, + weights_ram_wr_req_s, weights_ram_wr_resp_r + ) + } + + next (state: State) { + let tok = join(); + + let (tok, req) = recv_if(tok, req_r, false, zero!()); + + // Fetch Data + let tok = send_if(tok, mem_rd_req_s, false, zero!()); + let (tok, mem_rd_resp) = recv_if(tok, mem_rd_resp_r, false, zero!()); + + // Send to RAM + let tok = send_if(tok, weights_ram_wr_req_s, false, zero!()); + let (tok, _) = recv_if(tok, weights_ram_wr_resp_r, false, zero!()); + + let tok = send(tok, resp_s, zero!()); + + zero!() + } +} + +pub struct HuffmanWeightsDecoderReq { + addr: uN[AXI_ADDR_W], +} + +pub enum HuffmanWeightsDecoderStatus: u1 { + OKAY = 0, + ERROR = 1, +} + +enum WeightsType: u1 { + RAW = 0, + FSE = 1, +} + +pub struct HuffmanWeightsDecoderResp { + status: HuffmanWeightsDecoderStatus, + tree_description_size: uN[AXI_ADDR_W], +} + +pub struct HuffmanWeightsDecoderState { + req: HuffmanWeightsDecoderReq, +} + +pub proc HuffmanWeightsDecoder< + AXI_ADDR_W: u32, AXI_DATA_W: u32, + WEIGHTS_RAM_ADDR_W: u32, WEIGHTS_RAM_DATA_W: u32, + WEIGHTS_RAM_NUM_PARTITIONS: u32 +> { + type Req = HuffmanWeightsDecoderReq; + type Resp = HuffmanWeightsDecoderResp; + type Status = HuffmanWeightsDecoderStatus; + type State = HuffmanWeightsDecoderState; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + type WeightsRamRdReq = ram::ReadReq; + type WeightsRamRdResp = ram::ReadResp; + type WeightsRamWrReq = ram::WriteReq; + type WeightsRamWrResp = ram::WriteResp; + + // Types used internally + type RawWeightsReq = HuffmanRawWeightsDecoderReq; + type RawWeightsResp = HuffmanRawWeightsDecoderResp; + type FseWeightsReq = HuffmanFseWeightsDecoderReq; + type FseWeightsResp = HuffmanFseWeightsDecoderResp; + + // Control + req_r: chan in; + resp_s: chan out; + + // MemReader interface for fetching Huffman Tree Description + header_mem_rd_req_s: chan out; + header_mem_rd_resp_r: chan in; + + // Select for RamMux + decoded_weights_sel_s: chan out; + + // Raw Huffman Tree Description Decoder control + raw_weights_req_s: chan out; + raw_weights_resp_r: chan in; + + // Fse Huffman Tree Description Decoder control + fse_weights_req_s: chan out; + fse_weights_resp_r: chan in; + + // Fake ram read request channels (required by RamMux) + raw_weights_ram_rd_req_s: chan out; + raw_weights_ram_rd_resp_r: chan in; + fse_weights_ram_rd_req_s: chan out; + fse_weights_ram_rd_resp_r: chan in; + weights_ram_rd_req_r: chan in; + weights_ram_rd_resp_s: chan out; + + init { + zero!() + } + + config( + // Control + req_r: chan in, + resp_s: chan out, + + // MemReader interface for fetching Huffman Tree Description + header_mem_rd_req_s: chan out, + header_mem_rd_resp_r: chan in, + + // MemReader interface for Raw Huffman Tree Description Decoder + raw_weights_mem_rd_req_s: chan out, + raw_weights_mem_rd_resp_r: chan in, + + // MemReader interface for Fse Huffman Tree Description Decoder + fse_weights_mem_rd_req_s: chan out, + fse_weights_mem_rd_resp_r: chan in, + + // Muxed internal RAM Write interface (goes to Huffman Weights Memory) + weights_ram_wr_req_s: chan out, + weights_ram_wr_resp_r: chan in, + ) { + // Decoded Weights select for RamMux + let (decoded_weights_sel_s, decoded_weights_sel_r) = chan("decoded_weights_sel"); + + // Raw Huffman Tree Description control + let (raw_weights_req_s, raw_weights_req_r) = chan("raw_weights_req"); + let (raw_weights_resp_s, raw_weights_resp_r) = chan("raw_weights_resp"); + + // Fse Huffman Tree Description control + let (fse_weights_req_s, fse_weights_req_r) = chan("fse_weights_req"); + let (fse_weights_resp_s, fse_weights_resp_r) = chan("fse_weights_resp"); + + // Internal RAM Write interface with decoded RAW Huffman Tree Description + let (raw_weights_ram_wr_req_s, raw_weights_ram_wr_req_r) = chan("raw_weights_ram_wr_req"); + let (raw_weights_ram_wr_resp_s, raw_weights_ram_wr_resp_r) = chan("raw_weights_ram_wr_resp"); + + // Internal RAM Write interface with decoded Fse Huffman Tree Description + let (fse_weights_ram_wr_req_s, fse_weights_ram_wr_req_r) = chan("fse_weights_ram_wr_req"); + let (fse_weights_ram_wr_resp_s, fse_weights_ram_wr_resp_r) = chan("fse_weights_ram_wr_resp"); + + let (raw_weights_ram_rd_req_s, raw_weights_ram_rd_req_r) = chan("raw_weights_ram_rd_req"); + let (raw_weights_ram_rd_resp_s, raw_weights_ram_rd_resp_r) = chan("raw_weights_ram_rd_resp"); + let (fse_weights_ram_rd_req_s, fse_weights_ram_rd_req_r) = chan("fse_weights_ram_rd_req"); + let (fse_weights_ram_rd_resp_s, fse_weights_ram_rd_resp_r) = chan("fse_weights_ram_rd_resp"); + let (weights_ram_rd_req_s, weights_ram_rd_req_r) = chan("weights_ram_rd_req"); + let (weights_ram_rd_resp_s, weights_ram_rd_resp_r) = chan("weights_ram_rd_resp"); + + spawn HuffmanRawWeightsDecoder< + AXI_ADDR_W, AXI_DATA_W, + WEIGHTS_RAM_ADDR_W, WEIGHTS_RAM_DATA_W, + WEIGHTS_RAM_NUM_PARTITIONS + >( + raw_weights_req_r, raw_weights_resp_s, + raw_weights_mem_rd_req_s, raw_weights_mem_rd_resp_r, + raw_weights_ram_wr_req_s, raw_weights_ram_wr_resp_r + ); + + spawn HuffmanFseWeightsDecoder< + AXI_ADDR_W, AXI_DATA_W, + WEIGHTS_RAM_ADDR_W, WEIGHTS_RAM_DATA_W, + WEIGHTS_RAM_NUM_PARTITIONS + >( + fse_weights_req_r, fse_weights_resp_s, + fse_weights_mem_rd_req_s, fse_weights_mem_rd_resp_r, + fse_weights_ram_wr_req_s, fse_weights_ram_wr_resp_r + ); + + spawn ram_mux::RamMux( + decoded_weights_sel_r, + raw_weights_ram_rd_req_r, raw_weights_ram_rd_resp_s, // We don't care about read side + raw_weights_ram_wr_req_r, raw_weights_ram_wr_resp_s, + fse_weights_ram_rd_req_r, fse_weights_ram_rd_resp_s, // We don't care about read side + fse_weights_ram_wr_req_r, fse_weights_ram_wr_resp_s, + weights_ram_rd_req_s, weights_ram_rd_resp_r, // We don't care about read side + weights_ram_wr_req_s, weights_ram_wr_resp_r + ); + + ( + req_r, resp_s, + header_mem_rd_req_s, header_mem_rd_resp_r, + decoded_weights_sel_s, + raw_weights_req_s, raw_weights_resp_r, + fse_weights_req_s, fse_weights_resp_r, + raw_weights_ram_rd_req_s, raw_weights_ram_rd_resp_r, // We don't care about read side + fse_weights_ram_rd_req_s, fse_weights_ram_rd_resp_r, // We don't care about read side + weights_ram_rd_req_r, weights_ram_rd_resp_s, // We don't care about read side + ) + } + + next (state: State) { + let tok = join(); + + let (tok, req) = recv(tok, req_r); + trace_fmt!("Received Huffman weights decoding request {:#x}", req); + // Fetch Huffman Tree Header + let header_mem_rd_req = MemReaderReq { + addr: req.addr, + length: uN[AXI_ADDR_W]:1, + }; + let tok = send(tok, header_mem_rd_req_s, header_mem_rd_req); + let (tok, header_mem_rd_resp) = recv(tok, header_mem_rd_resp_r); + + // Decode Huffman Tree Header + // Now we know Huffman Tree Description size and the type of the description (RAW or FSE) + // Send proper select signal for the RamMux + // Send decoding request to HuffmanRawWeightsDecoder or HuffmanFseWeightsDecoder + // Receive response from HuffmanRawWeightsDecoder or HuffmanFseWeightsDecoder + + let header_byte = header_mem_rd_resp.data as u8; + trace_fmt!("Huffman weights header: {:#x}", header_byte); + + let weights_type = if header_byte < u8:128 { + WeightsType::FSE + } else { + WeightsType::RAW + }; + + let tok = send(tok, decoded_weights_sel_s, weights_type == WeightsType::FSE); + + // FSE + trace_fmt!("Decoding FSE Huffman weights"); + let fse_weights_req = zero!(); + let tok = send_if(tok, fse_weights_req_s, weights_type == WeightsType::FSE, fse_weights_req); + let (tok, fse_weights_resp) = recv_if(tok, fse_weights_resp_r, weights_type == WeightsType::FSE, zero!()); + + let fse_status = match fse_weights_resp.status { + HuffmanFseWeightsDecoderStatus::OKAY => HuffmanWeightsDecoderStatus::OKAY, + HuffmanFseWeightsDecoderStatus::ERROR => HuffmanWeightsDecoderStatus::ERROR, + _ => fail!("impossible_status_fse", HuffmanWeightsDecoderStatus::ERROR) + }; + + // RAW + trace_fmt!("Decoding RAW Huffman weights"); + let raw_weights_req = RawWeightsReq { + addr: req.addr, + n_symbols: header_byte - u8:127, + }; + let tok = send_if(tok, raw_weights_req_s, weights_type == WeightsType::RAW, raw_weights_req); + let (tok, raw_weights_resp) = recv_if(tok, raw_weights_resp_r, weights_type == WeightsType::RAW, zero!()); + + let raw_status = match raw_weights_resp.status { + HuffmanRawWeightsDecoderStatus::OKAY => HuffmanWeightsDecoderStatus::OKAY, + HuffmanRawWeightsDecoderStatus::ERROR => HuffmanWeightsDecoderStatus::ERROR, + _ => fail!("impossible_status_raw", HuffmanWeightsDecoderStatus::ERROR) + }; + + let resp = match weights_type { + WeightsType::RAW => { + Resp { + status: raw_status, + tree_description_size: (((header_byte - u8:127) >> u8:1) + u8:1) as uN[AXI_ADDR_W], + } + }, + WeightsType::FSE => { + Resp { + status: fse_status, + tree_description_size: header_byte as uN[AXI_ADDR_W], + } + }, + _ => fail!("impossible_weights_type", zero!()), + }; + + let tok = send(tok, resp_s, resp); + + // Handle fake ram read request channels + let tok = send_if(tok, raw_weights_ram_rd_req_s, false, zero!()); + let tok = send_if(tok, fse_weights_ram_rd_req_s, false, zero!()); + let (tok, _) = recv_if(tok, weights_ram_rd_req_r, false, zero!()); + let tok = send_if(tok, weights_ram_rd_resp_s, false, zero!()); + let (tok, _) = recv_if(tok, raw_weights_ram_rd_resp_r, false, zero!()); + let (tok, _) = recv_if(tok, fse_weights_ram_rd_resp_r, false, zero!()); + + zero!() + } +} + +const INST_AXI_ADDR_W = u32:32; +const INST_AXI_DATA_W = u32:64; +const INST_AXI_DEST_W = u32:8; +const INST_AXI_ID_W = u32:8; + +const INST_RAM_DATA_W = INST_AXI_DATA_W; +const INST_RAM_SIZE = u32:1024; +const INST_RAM_ADDR_W = INST_AXI_ADDR_W; +const INST_RAM_PARTITION_SIZE = INST_RAM_DATA_W / u32:8; +const INST_RAM_NUM_PARTITIONS = ram::num_partitions(INST_RAM_PARTITION_SIZE, INST_RAM_DATA_W); +const INST_RAM_SIMULTANEOUS_RW_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const INST_RAM_INITIALIZED = true; +const INST_RAM_ASSERT_VALID_READ = true; + +const INST_WEIGHTS_RAM_ADDR_W = huffman_prescan::RAM_ADDR_WIDTH; +const INST_WEIGHTS_RAM_SIZE = huffman_prescan::RAM_SIZE; +const INST_WEIGHTS_RAM_DATA_W = huffman_prescan::RAM_ACCESS_WIDTH; +const INST_WEIGHTS_RAM_PARTITION_SIZE = INST_WEIGHTS_RAM_DATA_W / u32:8; +const INST_WEIGHTS_RAM_NUM_PARTITIONS = ram::num_partitions(INST_WEIGHTS_RAM_PARTITION_SIZE, INST_WEIGHTS_RAM_DATA_W); +const INST_WEIGHTS_RAM_SIMULTANEOUS_RW_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const INST_WEIGHTS_RAM_INITIALIZED = true; +const INST_WEIGHTS_RAM_ASSERT_VALID_READ = true; + +proc HuffmanWeightsDecoderInst { + // Memory Reader + Input + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + + type InputBufferRamRdReq = ram::ReadReq; + type InputBufferRamRdResp = ram::ReadResp; + type InputBufferRamWrReq = ram::WriteReq; + type InputBufferRamWrResp = ram::WriteResp; + + type AxiAr = axi::AxiAr; + type AxiR = axi::AxiR; + + // Weights RAM + + type WeightsRamRdReq = ram::ReadReq; + type WeightsRamRdResp = ram::ReadResp; + type WeightsRamWrReq = ram::WriteReq; + type WeightsRamWrResp = ram::WriteResp; + + // Huffman Weights Decoder + type Req = HuffmanWeightsDecoderReq; + type Resp = HuffmanWeightsDecoderResp; + type Status = HuffmanWeightsDecoderStatus; + type State = HuffmanWeightsDecoderState; + + config ( + req_r: chan in, + resp_s: chan out, + header_mem_rd_req_s: chan out, + header_mem_rd_resp_r: chan in, + raw_weights_mem_rd_req_s: chan out, + raw_weights_mem_rd_resp_r: chan in, + fse_weights_mem_rd_req_s: chan out, + fse_weights_mem_rd_resp_r: chan in, + weights_ram_wr_req_s: chan out, + weights_ram_wr_resp_r: chan in, + ) { + spawn HuffmanWeightsDecoder< + INST_AXI_ADDR_W, INST_AXI_DATA_W, + INST_WEIGHTS_RAM_ADDR_W, INST_WEIGHTS_RAM_DATA_W, + INST_WEIGHTS_RAM_NUM_PARTITIONS + > ( + req_r, resp_s, + header_mem_rd_req_s, header_mem_rd_resp_r, + raw_weights_mem_rd_req_s, raw_weights_mem_rd_resp_r, + fse_weights_mem_rd_req_s, fse_weights_mem_rd_resp_r, + weights_ram_wr_req_s, weights_ram_wr_resp_r, + ); + } + + init { } + + next (state: ()) { } +} + + +const TEST_AXI_ADDR_W = u32:32; +const TEST_AXI_DATA_W = u32:64; +const TEST_AXI_DEST_W = u32:8; +const TEST_AXI_ID_W = u32:8; + +const TEST_RAM_DATA_W = TEST_AXI_DATA_W; +const TEST_RAM_SIZE = u32:1024; +const TEST_RAM_ADDR_W = TEST_AXI_ADDR_W; +const TEST_RAM_PARTITION_SIZE = TEST_RAM_DATA_W / u32:8; +const TEST_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_RAM_PARTITION_SIZE, TEST_RAM_DATA_W); +const TEST_RAM_SIMULTANEOUS_RW_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_RAM_INITIALIZED = true; +const TEST_RAM_ASSERT_VALID_READ = true; + +const TEST_WEIGHTS_RAM_ADDR_W = huffman_prescan::RAM_ADDR_WIDTH; +const TEST_WEIGHTS_RAM_SIZE = huffman_prescan::RAM_SIZE; +const TEST_WEIGHTS_RAM_DATA_W = huffman_prescan::RAM_ACCESS_WIDTH; +const TEST_WEIGHTS_RAM_PARTITION_SIZE = TEST_WEIGHTS_RAM_DATA_W / u32:8; +const TEST_WEIGHTS_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_WEIGHTS_RAM_PARTITION_SIZE, TEST_WEIGHTS_RAM_DATA_W); +const TEST_WEIGHTS_RAM_SIMULTANEOUS_RW_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_WEIGHTS_RAM_INITIALIZED = true; +const TEST_WEIGHTS_RAM_ASSERT_VALID_READ = true; + +const TEST_INPUT_ADDR = uN[TEST_AXI_ADDR_W]:0x40; + +// Weights sum is 1010, so the last one will be 14 +const TEST_DATA = u8[65]:[ + // len x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF + u8:248, u8:0xB__6, u8:0x8__5, u8:0x6__A, u8:0x9__C, u8:0x0__C, u8:0xA__9, u8:0x0__0, u8:0xD__0, // 0x0x + u8:0x6__E, u8:0x3__9, u8:0x8__4, u8:0x7__C, u8:0xC__2, u8:0x4__2, u8:0xB__A, u8:0x4__E, // 0x1x + u8:0xF__6, u8:0x2__7, u8:0x9__4, u8:0xD__1, u8:0xD__8, u8:0x2__B, u8:0xE__2, u8:0xD__1, // 0x2x + u8:0x8__F, u8:0x2__4, u8:0xD__3, u8:0x0__E, u8:0xF__E, u8:0x1__B, u8:0xF__9, u8:0x8__2, // 0x3x + u8:0xC__A, u8:0x6__1, u8:0x0__3, u8:0xD__C, u8:0xF__5, u8:0x1__D, u8:0x7__0, u8:0x1__6, // 0x4x + u8:0xA__A, u8:0x3__2, u8:0x8__8, u8:0x0__6, u8:0xE__7, u8:0x6__7, u8:0x8__E, u8:0x6__2, // 0x5x + u8:0x1__F, u8:0x3__E, u8:0xF__0, u8:0xC__7, u8:0x4__1, u8:0x7__E, u8:0x8__C, u8:0x8__4, // 0x6x + u8:0x3__3, u8:0xA__8, u8:0xE__E, u8:0x4__B, u8:0x0__0, u8:0x0__0, u8:0x0__0, u8:0x0__0, // 0x7x +]; + +const TEST_DATA_LAST_WEIGHT = u8:0xA; + +#[test_proc] +proc HuffmanWeightsDecoder_test { + // Memory Reader + Input + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + + type InputBufferRamRdReq = ram::ReadReq; + type InputBufferRamRdResp = ram::ReadResp; + type InputBufferRamWrReq = ram::WriteReq; + type InputBufferRamWrResp = ram::WriteResp; + + type AxiAr = axi::AxiAr; + type AxiR = axi::AxiR; + + // Weights RAM + + type WeightsRamRdReq = ram::ReadReq; + type WeightsRamRdResp = ram::ReadResp; + type WeightsRamWrReq = ram::WriteReq; + type WeightsRamWrResp = ram::WriteResp; + + // Huffman Weights Decoder + type Req = HuffmanWeightsDecoderReq; + type Resp = HuffmanWeightsDecoderResp; + type Status = HuffmanWeightsDecoderStatus; + type State = HuffmanWeightsDecoderState; + + terminator: chan out; + + req_s: chan out; + resp_r: chan in; + + input_ram_wr_req_s: chan[3] out; + input_ram_wr_resp_r: chan[3] in; + + weights_ram_rd_req_s: chan out; + weights_ram_rd_resp_r: chan in; + + config (terminator: chan out) { + + // Input Memory + + let (input_ram_rd_req_s, input_ram_rd_req_r) = chan[3]("input_ram_rd_req"); + let (input_ram_rd_resp_s, input_ram_rd_resp_r) = chan[3]("input_ram_rd_resp"); + let (input_ram_wr_req_s, input_ram_wr_req_r) = chan[3]("input_ram_wr_req"); + let (input_ram_wr_resp_s, input_ram_wr_resp_r) = chan[3]("input_ram_wr_resp"); + + unroll_for! (i, _) in range(u32:0, u32:3) { + spawn ram::RamModel< + TEST_RAM_DATA_W, TEST_RAM_SIZE, TEST_RAM_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_RW_BEHAVIOR, TEST_RAM_INITIALIZED, + TEST_RAM_ASSERT_VALID_READ, TEST_RAM_ADDR_W, + >( + input_ram_rd_req_r[i], input_ram_rd_resp_s[i], + input_ram_wr_req_r[i], input_ram_wr_resp_s[i], + ); + }(()); + + // Input Memory Axi Reader + + let (axi_ar_s, axi_ar_r) = chan[3]("axi_ar"); + let (axi_r_s, axi_r_r) = chan[3]("axi_r"); + + unroll_for! (i, _) in range(u32:0, u32:3) { + spawn axi_ram::AxiRamReader< + TEST_AXI_ADDR_W, TEST_AXI_DATA_W, + TEST_AXI_DEST_W, TEST_AXI_ID_W, + TEST_RAM_SIZE, + >( + axi_ar_r[i], axi_r_s[i], + input_ram_rd_req_s[i], input_ram_rd_resp_r[i], + ); + }(()); + + // Input Memory Reader + + let (mem_rd_req_s, mem_rd_req_r) = chan[3]("mem_rd_req"); + let (mem_rd_resp_s, mem_rd_resp_r) = chan[3]("mem_rd_resp"); + + unroll_for! (i, _) in range(u32:0, u32:3) { + spawn mem_reader::MemReader< + TEST_AXI_DATA_W, TEST_AXI_ADDR_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, + >( + mem_rd_req_r[i], mem_rd_resp_s[i], + axi_ar_s[i], axi_r_r[i], + ); + }(()); + + // Weights RAM + + let (weights_ram_rd_req_s, weights_ram_rd_req_r) = chan("weights_ram_rd_req"); + let (weights_ram_rd_resp_s, weights_ram_rd_resp_r) = chan("weights_ram_rd_resp"); + let (weights_ram_wr_req_s, weights_ram_wr_req_r) = chan("weights_ram_wr_req"); + let (weights_ram_wr_resp_s, weights_ram_wr_resp_r) = chan("weights_ram_wr_resp"); + + spawn ram::RamModel< + TEST_WEIGHTS_RAM_DATA_W, TEST_WEIGHTS_RAM_SIZE, TEST_WEIGHTS_RAM_PARTITION_SIZE, + TEST_WEIGHTS_RAM_SIMULTANEOUS_RW_BEHAVIOR, TEST_WEIGHTS_RAM_INITIALIZED, + TEST_WEIGHTS_RAM_ASSERT_VALID_READ, TEST_WEIGHTS_RAM_ADDR_W, + >( + weights_ram_rd_req_r, weights_ram_rd_resp_s, + weights_ram_wr_req_r, weights_ram_wr_resp_s, + ); + + // Huffman Weights Decoder + + let (req_s, req_r) = chan("req"); + let (resp_s, resp_r) = chan("resp"); + + spawn HuffmanWeightsDecoder< + TEST_AXI_ADDR_W, TEST_AXI_DATA_W, + TEST_WEIGHTS_RAM_ADDR_W, TEST_WEIGHTS_RAM_DATA_W, + TEST_WEIGHTS_RAM_NUM_PARTITIONS + > ( + req_r, resp_s, + mem_rd_req_s[0], mem_rd_resp_r[0], + mem_rd_req_s[1], mem_rd_resp_r[1], + mem_rd_req_s[2], mem_rd_resp_r[2], + weights_ram_wr_req_s, weights_ram_wr_resp_r, + ); + + ( + terminator, + req_s, resp_r, + input_ram_wr_req_s, input_ram_wr_resp_r, + weights_ram_rd_req_s, weights_ram_rd_resp_r, + ) + } + + init { } + + next (state: ()) { + const TEST_DATA_PER_RAM_WRITE = TEST_RAM_DATA_W / u32:8; + + let tok = join(); + + // Fill input RAM + for (i, tok) in range(u32:0, (array_size(TEST_DATA) + TEST_DATA_PER_RAM_WRITE - u32:1) / TEST_DATA_PER_RAM_WRITE) { + let ram_data = for (j, ram_data) in range(u32:0, TEST_DATA_PER_RAM_WRITE) { + let data_idx = i * TEST_DATA_PER_RAM_WRITE + j; + if (data_idx < array_size(TEST_DATA)) { + ram_data | ((TEST_DATA[data_idx] as uN[TEST_RAM_DATA_W]) << (u32:8 * j)) + } else { + ram_data + } + }(uN[TEST_RAM_DATA_W]:0); + + let input_ram_wr_req = InputBufferRamWrReq { + addr: (TEST_INPUT_ADDR / u32:8) + i as uN[TEST_RAM_ADDR_W], + data: ram_data, + mask: !uN[TEST_RAM_NUM_PARTITIONS]:0, + }; + + let tok = send(tok, input_ram_wr_req_s[0], input_ram_wr_req); + let (tok, _) = recv(tok, input_ram_wr_resp_r[0]); + let tok = send(tok, input_ram_wr_req_s[1], input_ram_wr_req); + let (tok, _) = recv(tok, input_ram_wr_resp_r[1]); + let tok = send(tok, input_ram_wr_req_s[2], input_ram_wr_req); + let (tok, _) = recv(tok, input_ram_wr_resp_r[2]); + + trace_fmt!("[TEST] Sent RAM write request to input RAMs {:#x}", input_ram_wr_req); + + tok + }(tok); + + // Send decoding request + let req = Req { + addr: TEST_INPUT_ADDR, + }; + let tok = send(tok, req_s, req); + trace_fmt!("[TEST] Sent request {:#x}", req); + + // Receive response + let (tok, resp) = recv(tok, resp_r); + trace_fmt!("[TEST] Received respose {:#x}", resp); + assert_eq(HuffmanWeightsDecoderStatus::OKAY, resp.status); + assert_eq(((TEST_DATA[0] - u8:127 + u8:1) >> u32:1) as uN[TEST_AXI_ADDR_W], resp.tree_description_size); + + // Insert last weight in test data + let last_weight_idx = ((TEST_DATA[0] as u32 - u32:127) / u32:2) + u32:1; + let last_weight_entry = ( + TEST_DATA[last_weight_idx] | + (TEST_DATA_LAST_WEIGHT << (u32:4 * (u32:1 - ((TEST_DATA[0] - u8:127) as u1 as u32)))) + ); + let test_data = update(TEST_DATA, last_weight_idx, last_weight_entry); + + // Check output RAM + let tok = for (i, tok) in range(u32:0, u32:32) { + let expected_value = if i < u32:16 { + ( + (test_data[4*i + u32:1] as u4) ++ ((test_data[4*i + u32:1] >> u32:4) as u4) ++ + (test_data[4*i + u32:2] as u4) ++ ((test_data[4*i + u32:2] >> u32:4) as u4) ++ + (test_data[4*i + u32:3] as u4) ++ ((test_data[4*i + u32:3] >> u32:4) as u4) ++ + (test_data[4*i + u32:4] as u4) ++ ((test_data[4*i + u32:4] >> u32:4) as u4) + ) + } else { + u32:0 + }; + + let weights_ram_rd_req = WeightsRamRdReq { + addr: i as uN[TEST_WEIGHTS_RAM_ADDR_W], + mask: !uN[TEST_WEIGHTS_RAM_NUM_PARTITIONS]:0, + }; + let tok = send(tok, weights_ram_rd_req_s, weights_ram_rd_req); + let (tok, weights_ram_rd_resp) = recv(tok, weights_ram_rd_resp_r); + trace_fmt!("[TEST] Weights RAM content - addr: {:#x} data: {:#x}", i, weights_ram_rd_resp.data); + + assert_eq(expected_value, weights_ram_rd_resp.data); + + tok + }(tok); + + send(tok, terminator, true); + } +} diff --git a/xls/modules/zstd/literals_block_header_dec.x b/xls/modules/zstd/literals_block_header_dec.x index 86e6597c01..950c394394 100644 --- a/xls/modules/zstd/literals_block_header_dec.x +++ b/xls/modules/zstd/literals_block_header_dec.x @@ -75,18 +75,18 @@ pub struct LiteralsHeader { compressed_size: u20, } -pub fn parse_literals_header(header_raw: u40) -> (LiteralsHeader, u3) { +pub fn parse_literals_header(header_raw: u40) -> (LiteralsHeader, u3, u8) { let (literal_type, header_size) = parse_literals_header_first_byte(header_raw[0:8]); - let (regenerated_size, compressed_size, header_length) = match (header_size) { - LiteralsHeaderSize::SINGLE_BYTE => (header_raw[3:8] as u20, header_raw[3:8] as u20, u3:1), - LiteralsHeaderSize::TWO_BYTES => (header_raw[4:16] as u20, header_raw[4:16] as u20, u3:2), - LiteralsHeaderSize::THREE_BYTES => (header_raw[4:24] as u20, header_raw[4:24] as u20, u3:3), - LiteralsHeaderSize::COMP_THREE_BYTES => (header_raw[4:14] as u20, header_raw[14:24] as u20, u3:3), - LiteralsHeaderSize::COMP_FOUR_BYTES => (header_raw[4:18] as u20, header_raw[18:32] as u20, u3:4), - LiteralsHeaderSize::COMP_FIVE_BYTES => (header_raw[4:22] as u20, header_raw[22:40] as u20, u3:5), + let (regenerated_size, compressed_size, header_length, symbol) = match (header_size) { + LiteralsHeaderSize::SINGLE_BYTE => (header_raw[3:8] as u20, header_raw[3:8] as u20, u3:1, header_raw[8:16]), + LiteralsHeaderSize::TWO_BYTES => (header_raw[4:16] as u20, header_raw[4:16] as u20, u3:2, header_raw[16:24]), + LiteralsHeaderSize::THREE_BYTES => (header_raw[4:24] as u20, header_raw[4:24] as u20, u3:3, header_raw[24:32]), + LiteralsHeaderSize::COMP_THREE_BYTES => (header_raw[4:14] as u20, header_raw[14:24] as u20, u3:3, header_raw[24:32]), + LiteralsHeaderSize::COMP_FOUR_BYTES => (header_raw[4:18] as u20, header_raw[18:32] as u20, u3:4, u8:0), + LiteralsHeaderSize::COMP_FIVE_BYTES => (header_raw[4:22] as u20, header_raw[22:40] as u20, u3:5, u8:0), // fail!() doesn't work with quicktest, JIT failes to translate such function //_ => fail!("Unrecognized_header_sizeC" ,CompressedBlockSize { - _ => (u20:0, u20:0, u3:0), + _ => (u20:0, u20:0, u3:0, u8:0), }; (LiteralsHeader { literal_type: literal_type, @@ -95,12 +95,12 @@ pub fn parse_literals_header(header_raw: u40) -> (LiteralsHeader, u3) { LiteralsBlockType::RLE => u20:1, _ => compressed_size, } - }, header_length) + }, header_length, symbol) } #[quickcheck] fn test_parse_literals_header(x: u40) -> bool { - let (header, header_length_bytes) = parse_literals_header(x); + let (header, header_length_bytes, symbol) = parse_literals_header(x); let (_, header_size) = parse_literals_header_first_byte(x[0:8]); let length_bytes_equivalence = match (header_size) { @@ -138,7 +138,17 @@ fn test_parse_literals_header(x: u40) -> bool { _ => false } }; - length_bytes_equivalence && raw_length_equivalence && regen_comp_size_equivalence + + let symbol_equivalence = match (header_size) { + LiteralsHeaderSize::SINGLE_BYTE => symbol == x[8:16], + LiteralsHeaderSize::TWO_BYTES => symbol == x[16:24], + LiteralsHeaderSize::THREE_BYTES | LiteralsHeaderSize::COMP_THREE_BYTES => symbol == x[24:32], + LiteralsHeaderSize::COMP_FOUR_BYTES => symbol == u8:0, + LiteralsHeaderSize::COMP_FIVE_BYTES => symbol == u8:0, + _ => false + }; + + length_bytes_equivalence && raw_length_equivalence && regen_comp_size_equivalence && symbol_equivalence } pub enum LiteralsHeaderDecoderStatus : u1 { @@ -152,6 +162,7 @@ pub struct LiteralsHeaderDecoderReq { pub struct LiteralsHeaderDecoderResp { header: LiteralsHeader, + symbol: u8, length: u3, status: LiteralsHeaderDecoderStatus, } @@ -166,22 +177,21 @@ pub proc LiteralsHeaderDecoder { type MemReaderResp = mem_reader::MemReaderResp; type MemReaderStatus = mem_reader::MemReaderStatus; - mem_rd_req_s: chan out; - mem_rd_resp_r: chan in; - req_r: chan in; resp_s: chan out; + mem_rd_req_s: chan out; + mem_rd_resp_r: chan in; + init {} config( - mem_rd_req_s: chan out, - mem_rd_resp_r: chan in, - req_r: chan in, resp_s: chan out, + mem_rd_req_s: chan out, + mem_rd_resp_r: chan in, ) { - (mem_rd_req_s, mem_rd_resp_r, req_r, resp_s) + (req_r, resp_s, mem_rd_req_s, mem_rd_resp_r) } next(state: ()) { @@ -196,9 +206,10 @@ pub proc LiteralsHeaderDecoder { // TODO: handle multiple receives on mem_rd_resp_r when AXI_DATA_W < 40 const_assert!(AXI_DATA_W >= u32:64); let (tok, raw) = recv(tok, mem_rd_resp_r); - let (header, length) = parse_literals_header(raw.data[:40]); + let (header, length, symbol) = parse_literals_header(raw.data[:40]); send(tok, resp_s, Resp { header: header, + symbol: symbol, length: length, status: match (raw.status) { MemReaderStatus::OKAY => Status::OKAY, @@ -223,10 +234,10 @@ proc LiteralsHeaderDecoderTest { terminator: chan out; - mem_rd_req_r: chan in; - mem_rd_resp_s: chan out; req_s: chan out; resp_r: chan in; + mem_rd_req_r: chan in; + mem_rd_resp_s: chan out; init {} @@ -239,125 +250,125 @@ proc LiteralsHeaderDecoderTest { let (resp_s, resp_r) = chan("resp"); spawn LiteralsHeaderDecoder ( - mem_rd_req_s, mem_rd_resp_r, req_r, resp_s + req_r, resp_s, mem_rd_req_s, mem_rd_resp_r ); ( terminator, + req_s, resp_r, mem_rd_req_r, mem_rd_resp_s, - req_s, resp_r ) } next(state: ()) { let tok = join(); - + // test data format: raw header, expected size in bytes, expected parsed header - let tests: (u40, u3, LiteralsHeader)[16] = [ - // 2 bits block type == RAW, 1 bit size_format == 0, 5 bits regenerated_size - (u40:0b10100_0_00, u3:1, LiteralsHeader { + let tests: (u40, u3, LiteralsHeader, u8)[16] = [ + // 2 bits block type == RAW, 1 bit size_format == 0, 5 bits regenerated_size, symbol: 0xAA + (u40:0b10101010_10100_0_00, u3:1, LiteralsHeader { literal_type: LiteralsBlockType::RAW, regenerated_size: u20:0b10100, compressed_size: u20:0b10100, - }), - // 2 bits block type == RAW, 2 bit size_format == 1, 12 bits regenerated_size - (u40:0b101010101010_01_00, u3:2, LiteralsHeader { + }, u8:0xAA), + // 2 bits block type == RAW, 2 bit size_format == 1, 12 bits regenerated_size, symbol: 0xF5 + (u40:0b11110101_101010101010_01_00, u3:2, LiteralsHeader { literal_type: LiteralsBlockType::RAW, regenerated_size: u20:0b101010101010, compressed_size: u20:0b101010101010, - }), - // 2 bits block type == RAW, 1 bit size_format == 2, 5 bits regenerated_size - (u40:0b10101_0_00, u3:1, LiteralsHeader { + }, u8:0xF5), + // 2 bits block type == RAW, 1 bit size_format == 2, 5 bits regenerated_size, symbol: 0xF0 + (u40:0b11110000_10101_0_00, u3:1, LiteralsHeader { literal_type: LiteralsBlockType::RAW, regenerated_size: u20:0b10101, compressed_size: u20:0b10101, - }), - // 2 bits block type == RAW, 2 bit size_format == 3, 20 bits regenerated_size - (u40:0b10101010101010101010_11_00, u3:3, LiteralsHeader { + }, u8:0xF0), + // 2 bits block type == RAW, 2 bit size_format == 3, 20 bits regenerated_size, symbol: 0xF0 + (u40:0b11110000_10101010101010101010_11_00, u3:3, LiteralsHeader { literal_type: LiteralsBlockType::RAW, regenerated_size: u20:0b10101010101010101010, compressed_size: u20:0b10101010101010101010, - }), + }, u8:0xF0), - // 2 bits block type == RLE, 1 bit size_format == 0, 5 bits regenerated_size - (u40:0b10100_0_01, u3:1, LiteralsHeader { + // 2 bits block type == RLE, 1 bit size_format == 0, 5 bits regenerated_size, symbol: 0xF0 + (u40:0b11110000_10100_0_01, u3:1, LiteralsHeader { literal_type: LiteralsBlockType::RLE, regenerated_size: u20:0b10100, compressed_size: u20:1, - }), - // 2 bits block type == RLE, 2 bits size_format == 1, 12 bits regenerated_size - (u40:0b101010101010_01_01, u3:2, LiteralsHeader { + }, u8:0xF0), + // 2 bits block type == RLE, 2 bits size_format == 1, 12 bits regenerated_size, symbol: 0xF0 + (u40:0b11110000_101010101010_01_01, u3:2, LiteralsHeader { literal_type: LiteralsBlockType::RLE, regenerated_size: u20:0b101010101010, compressed_size: u20:1, - }), - // 2 bits block type == RLE, 1 bit size_format == 2, 5 bits regenerated_size - (u40:0b10101_0_01, u3:1, LiteralsHeader { + }, u8:0xF0), + // 2 bits block type == RLE, 1 bit size_format == 2, 5 bits regenerated_size, symbol: 0xF0 + (u40:0b11110000_10101_0_01, u3:1, LiteralsHeader { literal_type: LiteralsBlockType::RLE, regenerated_size: u20:0b10101, compressed_size: u20:1, - }), - // 2 bits block type == RLE, 2 bits size_format == 3, 20 bits regenerated_size - (u40:0b10101010101010101010_11_01, u3:3, LiteralsHeader { + }, u8:0xF0), + // 2 bits block type == RLE, 2 bits size_format == 3, 20 bits regenerated_size, symbol: 0xF0 + (u40:0b11110000_10101010101010101010_11_01, u3:3, LiteralsHeader { literal_type: LiteralsBlockType::RLE, regenerated_size: u20:0b10101010101010101010, compressed_size: u20:1, - }), + }, u8:0xF0), - // 2 bits block type == COMPRESSED, 2 bits size_format == 0, 10 bits regenerated_size and compressed_size - (u40:0b1010101010_0101010101_00_10, u3:3, LiteralsHeader { + // 2 bits block type == COMPRESSED, 2 bits size_format == 0, 10 bits regenerated_size and compressed_size, symbol: 0xF0 + (u40:0b11110000_1010101010_0101010101_00_10, u3:3, LiteralsHeader { literal_type: LiteralsBlockType::COMP, regenerated_size: u20:0b0101010101, compressed_size: u20:0b1010101010, - }), - // 2 bits block type == COMPRESSED, 2 bits size_format == 1, 10 bits regenerated_size and compressed_size - (u40:0b1010101010_0101010101_01_10, u3:3, LiteralsHeader { + }, u8:0xF0), + // 2 bits block type == COMPRESSED, 2 bits size_format == 1, 10 bits regenerated_size and compressed_size, symbol: 0xF0 + (u40:0b11110000_1010101010_0101010101_01_10, u3:3, LiteralsHeader { literal_type: LiteralsBlockType::COMP_4, regenerated_size: u20:0b0101010101, compressed_size: u20:0b1010101010, - }), - // 2 bits block type == COMPRESSED, 2 bits size_format == 2, 14 bits regenerated_size and compressed_size + }, u8:0xF0), + // 2 bits block type == COMPRESSED, 2 bits size_format == 2, 14 bits regenerated_size and compressed_size, symbol: 0x0 (u40:0b10101010101010_01010101010101_10_10, u3:4, LiteralsHeader { literal_type: LiteralsBlockType::COMP_4, regenerated_size: u20:0b01010101010101, compressed_size: u20:0b10101010101010, - }), - // 2 bits block type == COMPRESSED, 2 bits size_format == 3, 18 bits regenerated_size and compressed_size + }, u8:0x0), + // 2 bits block type == COMPRESSED, 2 bits size_format == 3, 18 bits regenerated_size and compressed_size, symbol: 0x0 (u40:0b101010101010101010_010101010101010101_11_10, u3:5, LiteralsHeader { literal_type: LiteralsBlockType::COMP_4, regenerated_size: u20:0b010101010101010101, compressed_size: u20:0b101010101010101010, - }), + }, u8:0x0), - // 2 bits block type == TREELESS, 2 bits size_format == 0, 10 bits regenerated_size and compressed_size + // 2 bits block type == TREELESS, 2 bits size_format == 0, 10 bits regenerated_size and compressed_size, symbol: 0x0 (u40:0b1010101010_0101010101_00_11, u3:3, LiteralsHeader { literal_type: LiteralsBlockType::TREELESS, regenerated_size: u20:0b0101010101, compressed_size: u20:0b1010101010, - }), - // 2 bits block type == TREELESS, 2 bits size_format == 1, 10 bits regenerated_size and compressed_size + }, u8:0x0), + // 2 bits block type == TREELESS, 2 bits size_format == 1, 10 bits regenerated_size and compressed_size, symbol: 0x0 (u40:0b1010101010_0101010101_01_11, u3:3, LiteralsHeader { literal_type: LiteralsBlockType::TREELESS_4, regenerated_size: u20:0b0101010101, compressed_size: u20:0b1010101010, - }), - // 2 bits block type == TREELESS, 2 bits size_format == 2, 14 bits regenerated_size and compressed_size + }, u8:0x0), + // 2 bits block type == TREELESS, 2 bits size_format == 2, 14 bits regenerated_size and compressed_size, symbol: 0x0 (u40:0b10101010101010_01010101010101_10_11, u3:4, LiteralsHeader { literal_type: LiteralsBlockType::TREELESS_4, regenerated_size: u20:0b01010101010101, compressed_size: u20:0b10101010101010, - }), - // 2 bits block type == TREELESS, 2 bits size_format == 3, 18 bits regenerated_size and compressed_size + }, u8:0x0), + // 2 bits block type == TREELESS, 2 bits size_format == 3, 18 bits regenerated_size and compressed_size, symbol: 0x0 (u40:0b101010101010101010_010101010101010101_11_11, u3:5, LiteralsHeader { literal_type: LiteralsBlockType::TREELESS_4, regenerated_size: u20:0b010101010101010101, compressed_size: u20:0b101010101010101010, - }), + }, u8:0x0), ]; - const ADDR = uN[TEST_AXI_ADDR_W]:0xDEAD; - + const ADDR = uN[TEST_AXI_ADDR_W]:0xDEAD; + // positive cases - let tok = for ((_, (test_vec, expected_length, expected_header)), tok): ((u32, (u40, u3, LiteralsHeader)), token) in enumerate(tests) { + let tok = for ((_, (test_vec, expected_length, expected_header, expected_symbol)), tok): ((u32, (u40, u3, LiteralsHeader, u8)), token) in enumerate(tests) { send(tok, req_s, Req { addr: ADDR, }); @@ -367,7 +378,7 @@ proc LiteralsHeaderDecoderTest { length: uN[TEST_AXI_ADDR_W]:5 }); let tok = send(tok, mem_rd_resp_s, MemReaderResp { - status: MemReaderStatus::OKAY, + status: MemReaderStatus::OKAY, data: test_vec as uN[TEST_AXI_DATA_W], length: uN[TEST_AXI_ADDR_W]:5, last: true, @@ -375,6 +386,7 @@ proc LiteralsHeaderDecoderTest { let (tok, resp) = recv(tok, resp_r); assert_eq(resp, LiteralsHeaderDecoderResp { header: expected_header, + symbol: expected_symbol, status: LiteralsHeaderDecoderStatus::OKAY, length: expected_length, }); @@ -391,7 +403,7 @@ proc LiteralsHeaderDecoderTest { length: uN[TEST_AXI_ADDR_W]:5 }); let tok = send(tok, mem_rd_resp_s, MemReaderResp { - status: MemReaderStatus::ERROR, + status: MemReaderStatus::ERROR, data: uN[TEST_AXI_DATA_W]:0, length: uN[TEST_AXI_ADDR_W]:0, last: true, @@ -409,25 +421,25 @@ proc LiteralsHeaderDecoderInst { type ReaderReq = mem_reader::MemReaderReq; type ReaderResp = mem_reader::MemReaderResp; - reader_req_s: chan out; - reader_resp_r: chan in; - decode_req_r: chan in; decode_resp_s: chan out; + reader_req_s: chan out; + reader_resp_r: chan in; + config( - reader_req_s: chan out, - reader_resp_r: chan in, decode_req_r: chan in, decode_resp_s: chan out, + reader_req_s: chan out, + reader_resp_r: chan in, ) { spawn LiteralsHeaderDecoder( - reader_req_s, - reader_resp_r, decode_req_r, - decode_resp_s + decode_resp_s, + reader_req_s, + reader_resp_r ); - (reader_req_s, reader_resp_r, decode_req_r, decode_resp_s) + (decode_req_r, decode_resp_s, reader_req_s, reader_resp_r) } init {} diff --git a/xls/modules/zstd/literals_buffer.x b/xls/modules/zstd/literals_buffer.x index 1ad9ff4407..b1e02c21c8 100644 --- a/xls/modules/zstd/literals_buffer.x +++ b/xls/modules/zstd/literals_buffer.x @@ -103,7 +103,7 @@ struct LiteralsBufferReaderToWriterSync { } // PacketDecoder is responsible for receiving read bytes from RAMs response -// handler, removing the "last" flag from each literal and adding this flag +// handler, removing the "literals_last" flag from each literal and adding this flag // to the packet. It also validates the data. proc PacketDecoder { literals_in_r: chan> in; @@ -133,43 +133,23 @@ proc PacketDecoder { ) }(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, 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), - calc - ) - } else { - ( - last, - literal_last, - packet_valid, - calc - ) - } - }((false, false, true, true)); + let literals_lasts = for (i, lasts): (u32, bool[RAM_NUM]) in range(u32:0, RAM_NUM) { + let last = (literals.content >> (RAM_DATA_WIDTH * (i + u32:1) - u32:1)) as u1; + update(lasts, i, last) + }(bool[RAM_NUM]:[0, ...]); + let literals_last = literals_lasts[literals.length - u64:1]; - assert!(packet_valid && (literals.last == last), "Invalid packet"); + // TODO: Restore this check after extending request to CommandConstructor + // assert!(literals.last == literals_last, "Invalid packet"); // Send literals data - let tok = send(tok, literals_out_s, SequenceExecutorPacket { + let literals_out = SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::LITERAL, length: literals.length, content: literals_data, - last: last - }); + last: literals_last + }; + let tok = send(tok, literals_out_s, literals_out); // Send sync data to buffer writer let tok = send(tok, buffer_sync_s, LiteralsBufferReaderToWriterSync { @@ -348,8 +328,6 @@ proc LiteralsBufferMux { 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) { @@ -369,22 +347,29 @@ proc LiteralsBufferMux { ) } else { ( - literals_data, + zero!(), state ) }; - send_if(tok1, out_literals_s, literals_valid, LiteralsData { + let out_literals = 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 - } + send_if(tok1, out_literals_s, literals_valid, out_literals); + if literals_valid { + trace_fmt!("[LiteralsBufferMux] literals: {:#x}", out_literals); + } else {}; + + let next_state = match (literals_data.last, literals_data.literals_last) { + (true, false) => LiteralsBufferMuxState { literals_id: state.literals_id + LitID:1, ..state }, + (true, true) => zero!(), + (_, _) => state, + }; + + next_state } } @@ -496,8 +481,8 @@ proc LiteralsBufferWriter< // 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); + let last = if literals_data.length as u32 == i + u32:1 { (literals_data.last as LiteralsWithLast) << common::SYMBOL_WIDTH } else {LiteralsWithLast:0}; + let literal = (((literals_data.data >> (common::SYMBOL_WIDTH * i)) as uN[common::SYMBOL_WIDTH]) as LiteralsWithLast) | last; data | (literal << (RAM_DATA_WIDTH * i)) }(LiteralsWithLast:0); @@ -974,27 +959,41 @@ enum LiteralsChannel: u2 { } 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:0x12_3456_789A, length: LitLength:5, last: true, id: LitID:0, literals_last: false}), + (LiteralsChannel::RLE, LiteralsDataWithSync {data: LitData:0xBBBB_BBBB, length: LitLength:4, last: true, id: LitID:1, literals_last: false}), + (LiteralsChannel::HUFF, LiteralsDataWithSync {data: LitData:0x64, length: LitLength:1, last: true, id: LitID:2, literals_last: false}), + (LiteralsChannel::RLE, LiteralsDataWithSync {data: LitData:0xABCD_DCBA_1234_4321, length: LitLength:8, last: true, id: LitID:3, literals_last: false}), + (LiteralsChannel::RAW, LiteralsDataWithSync {data: LitData:0x21_4365, length: LitLength:3, last: true, id: LitID:4, literals_last: false}), + (LiteralsChannel::RLE, LiteralsDataWithSync {data: LitData:0xAA_BBBB_CCCC_DDDD, length: LitLength:7, last: true, id: LitID:5, literals_last: false}), (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::RAW, LiteralsDataWithSync {data: LitData:0x78, length: LitLength:1, last: true, id: LitID:6, literals_last: false}), (LiteralsChannel::HUFF, LiteralsDataWithSync {data: LitData:0x26, length: LitLength:1, last: true, id: LitID:7, literals_last: true}), ]; -const TEST_BUFFER_CTRL: LiteralsBufferCtrl[6] = [ +const TEST_BUFFER_CTRL: LiteralsBufferCtrl[11] = [ + // Literal #0 LiteralsBufferCtrl {length: u32:2, last: false}, LiteralsBufferCtrl {length: u32:1, last: false}, - LiteralsBufferCtrl {length: u32:13, last: false}, + LiteralsBufferCtrl {length: u32:2, last: true}, + // Literal #1 + LiteralsBufferCtrl {length: u32:4, last: true}, + // Literal #2 + LiteralsBufferCtrl {length: u32:1, last: true}, + // Literal #3 + LiteralsBufferCtrl {length: u32:8, last: true}, + // Literal #4 + LiteralsBufferCtrl {length: u32:3, last: true}, + // Literal #5 + LiteralsBufferCtrl {length: u32:7, last: true}, + // Literal #6 LiteralsBufferCtrl {length: u32:8, last: false}, - LiteralsBufferCtrl {length: u32:4, last: false}, - LiteralsBufferCtrl {length: u32:10, last: true}, + LiteralsBufferCtrl {length: u32:1, last: true}, + // Literal #7 + LiteralsBufferCtrl {length: u32:1, last: true}, ]; -const TEST_EXPECTED_PACKETS: SequenceExecutorPacket[8] = [ +const TEST_EXPECTED_PACKETS: SequenceExecutorPacket[11] = [ + // Literal #0 SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::LITERAL, length: CopyOrMatchLength:2, @@ -1009,28 +1008,46 @@ const TEST_EXPECTED_PACKETS: SequenceExecutorPacket[8] = [ }, SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::LITERAL, - length: CopyOrMatchLength:8, - content: CopyOrMatchContent:0x2164_BBBB_BBBB_1234, - last: false + length: CopyOrMatchLength:2, + content: CopyOrMatchContent:0x1234, + last: true }, + // Literal #1 SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::LITERAL, - length: CopyOrMatchLength:5, - content: CopyOrMatchContent:0xDC_BA12_3443, - last: false + length: CopyOrMatchLength:4, + content: CopyOrMatchContent:0xBBBB_BBBB, + last: true }, + // Literal #2 + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:1, + content: CopyOrMatchContent:0x64, + last: true + }, + // Literal #3 SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::LITERAL, length: CopyOrMatchLength:8, - content: CopyOrMatchContent:0xCCDD_DD21_4365_ABCD, - last: false + content: CopyOrMatchContent:0xABCD_DCBA_1234_4321, + last: true }, + // Literal #4 SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::LITERAL, - length: CopyOrMatchLength:4, - content: CopyOrMatchContent:0xAABB_BBCC, - last: false + length: CopyOrMatchLength:3, + content: CopyOrMatchContent:0x21_4365, + last: true }, + // Literal #5 + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:7, + content: CopyOrMatchContent:0xAA_BBBB_CCCC_DDDD, + last: true + }, + // Literal #6 SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::LITERAL, length: CopyOrMatchLength:8, @@ -1039,10 +1056,17 @@ const TEST_EXPECTED_PACKETS: SequenceExecutorPacket[8] = [ }, SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::LITERAL, - length: CopyOrMatchLength:2, - content: CopyOrMatchContent:0x2678, + length: CopyOrMatchLength:1, + content: CopyOrMatchContent:0x78, last: true }, + // Literal #7 + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:1, + content: CopyOrMatchContent:0x26, + last: true + } ]; #[test_proc] diff --git a/xls/modules/zstd/literals_decoder.x b/xls/modules/zstd/literals_decoder.x index 24c0a05d2e..1cf493af93 100644 --- a/xls/modules/zstd/literals_decoder.x +++ b/xls/modules/zstd/literals_decoder.x @@ -18,47 +18,679 @@ import std; import xls.examples.ram; import xls.modules.zstd.common as common; +import xls.modules.zstd.literals_block_header_dec as literals_block_header_dec; import xls.modules.zstd.literals_buffer as literals_buffer; -import xls.modules.zstd.literals_dispatcher as literals_dispatcher; +import xls.modules.zstd.memory.axi as axi; +import xls.modules.zstd.memory.axi_ram; +import xls.modules.zstd.memory.mem_reader as mem_reader; 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; +import xls.modules.zstd.huffman_literals_dec as huffman_literals_dec; type CopyOrMatchContent = common::CopyOrMatchContent; type CopyOrMatchLength = common::CopyOrMatchLength; type LitData = common::LitData; type LitLength = common::LitLength; -type LiteralType = common::LiteralType; +type LiteralsBlockType = literals_block_header_dec::LiteralsBlockType; 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< +pub struct LiteralsDecoderCtrlReq { + addr: uN[AXI_ADDR_W], + literals_last: bool +} + +pub enum LiteralsDecoderCtrlStatus: u1 { + OKAY = 0, + ERROR = 1, +} + +pub struct LiteralsDecoderCtrlResp { + status: LiteralsDecoderCtrlStatus +} + +struct LiteralsDecoderCtrlState { + id: u32, + req: LiteralsDecoderCtrlReq, + req_valid: bool, + decoding_raw_literals: bool, + decoding_rle_literals: bool, + decoding_huffman_literals: bool, +} + +proc LiteralsDecoderCtrl { + type CtrlReq = LiteralsDecoderCtrlReq; + type CtrlResp = LiteralsDecoderCtrlResp; + type HeaderReq = literals_block_header_dec::LiteralsHeaderDecoderReq; + type HeaderResp = literals_block_header_dec::LiteralsHeaderDecoderResp; + type RawReq = raw_literals_dec::RawLiteralsDecoderReq; + type RawResp = raw_literals_dec::RawLiteralsDecoderResp; + type RawRespStatus = raw_literals_dec::RawLiteralsDecoderStatus; + type RleReq = rle_literals_dec::RleLiteralsDecoderReq; + type RleResp = rle_literals_dec::RleLiteralsDecoderResp; + type RleRespStatus = rle_literals_dec::RleLiteralsDecoderStatus; + type HuffmanReq = huffman_literals_dec::HuffmanLiteralsDecoderReq; + type HuffmanResp = huffman_literals_dec::HuffmanLiteralsDecoderResp; + type HuffmanRespStatus = huffman_literals_dec::HuffmanLiteralsDecoderStatus; + + type Status = LiteralsDecoderCtrlStatus; + type State = LiteralsDecoderCtrlState; + + // Literals Decoder control + lit_ctrl_req_r: chan in; + lit_ctrl_resp_s: chan out; + lit_ctrl_header_s: chan out; + + // Literals Header Decoder + lit_header_req_s: chan out; + lit_header_resp_r: chan in; + + // Raw Literals Decoder + raw_lit_req_s: chan out; + raw_lit_resp_r: chan in; + + // Rle Literals Decoder + rle_lit_req_s: chan out; + rle_lit_resp_r: chan in; + + // Huffman Literals Decoder + huffman_lit_req_s: chan out; + huffman_lit_resp_r: chan in; + + init { + zero!() + } + + config ( + // Literals Decoder control + lit_ctrl_req_r: chan in, + lit_ctrl_resp_s: chan out, + lit_ctrl_header_s: chan out, + + // Literals Header Decoder + lit_header_req_s: chan out, + lit_header_resp_r: chan in, + + // Raw Literals Decoder + raw_lit_req_s: chan out, + raw_lit_resp_r: chan in, + + // Rle Literals Decoder + rle_lit_req_s: chan out, + rle_lit_resp_r: chan in, + + // Huffman Literals Decoder + huffman_lit_req_s: chan out, + huffman_lit_resp_r: chan in + ) { + ( + lit_ctrl_req_r, lit_ctrl_resp_s, lit_ctrl_header_s, + lit_header_req_s, lit_header_resp_r, + raw_lit_req_s, raw_lit_resp_r, + rle_lit_req_s, rle_lit_resp_r, + huffman_lit_req_s, huffman_lit_resp_r + ) + } + + next (state: State) { + let tok = join(); + // Try receiving response from Raw-, Rle- and HuffmanLiteralsDecoder procs to free + // resources at the very begining of next() evaluation + let do_recv_raw_resp = state.decoding_raw_literals; + let (tok, raw_resp, raw_resp_valid) = recv_if_non_blocking(tok, raw_lit_resp_r, do_recv_raw_resp, zero!()); + let decoding_raw_literals = if (raw_resp_valid) { + trace_fmt!("received RawResp: {:#x}", raw_resp); + false + } else { + state.decoding_raw_literals + }; + + let do_recv_rle_resp = state.decoding_rle_literals; + let (tok, rle_resp, rle_resp_valid) = recv_if_non_blocking(tok, rle_lit_resp_r, do_recv_rle_resp, zero!()); + let decoding_rle_literals = if (rle_resp_valid) { + trace_fmt!("received RleResp: {:#x}", rle_resp); + false + } else { + state.decoding_rle_literals + }; + + let do_recv_huffman_resp = state.decoding_huffman_literals; + let (tok, huffman_resp, huffman_resp_valid) = recv_if_non_blocking(tok, huffman_lit_resp_r, do_recv_huffman_resp, zero!()); + let decoding_huffman_literals = if (huffman_resp_valid) { + trace_fmt!("received HuffmanResp: {:#x}", huffman_resp); + false + } else { + state.decoding_huffman_literals + }; + + // Receive new literals decoding request if previous was handled + let tok = join(); + let do_recv_ctrl_req = !state.req_valid; + let (tok, ctrl_req, ctrl_req_valid) = recv_if_non_blocking(tok, lit_ctrl_req_r, do_recv_ctrl_req, zero!()); + + if (ctrl_req_valid) { + trace_fmt!("received CtrlReq: {:#x}", ctrl_req); + } else {}; + + let (new_ctrl_req, new_ctrl_req_valid) = if (ctrl_req_valid) { + (ctrl_req, true) + } else { + (state.req, state.req_valid) + }; + + // There's no harm in trying to receive header decoding response in every next() evaluation + let (tok, header_resp, header_resp_valid) = recv_non_blocking(tok, lit_header_resp_r, zero!()); + if (header_resp_valid) { + trace_fmt!("received HeaderReq: {:#x}", header_resp); + } else {}; + + send_if(tok, lit_ctrl_header_s, header_resp_valid, header_resp); + + // Send literals header decoding request right after receiving CtrlRequest + let header_req = HeaderReq { + addr: new_ctrl_req.addr + }; + let do_send_header_req = ctrl_req_valid; + let tok = send_if(tok, lit_header_req_s, do_send_header_req, header_req); + if (do_send_header_req) { + trace_fmt!("send HeaderReq: {:#x}", header_req); + } else {}; + + // Address of the beginning of the actual literals in the Literals Section + let literals_addr = state.req.addr + header_resp.length as uN[AXI_ADDR_W]; + + // Send raw literals decoding request right after receiving decoded literals header + let raw_req = RawReq { + addr: literals_addr, + length: header_resp.header.regenerated_size as uN[AXI_ADDR_W], + id: state.id, + literals_last: state.req.literals_last + }; + let do_send_raw_req = header_resp_valid && (header_resp.header.literal_type == LiteralsBlockType::RAW) && !state.decoding_raw_literals; + let tok = send_if(tok, raw_lit_req_s, do_send_raw_req, raw_req); + let decoding_raw_literals = if (do_send_raw_req) { + trace_fmt!("send RawReq: {:#x}", raw_req); + true + } else { + decoding_raw_literals + }; + + // Send rle literals decoding request right after receiving decoded literals header + let rle_req = RleReq { + symbol: header_resp.symbol, + length: header_resp.header.regenerated_size, + id: state.id, + literals_last: state.req.literals_last + }; + let do_send_rle_req = header_resp_valid && (header_resp.header.literal_type == LiteralsBlockType::RLE) && !state.decoding_rle_literals; + let tok = send_if(tok, rle_lit_req_s, do_send_rle_req, rle_req); + let decoding_rle_literals = if (do_send_rle_req) { + trace_fmt!("send RleReq: {:#x}", rle_req); + true + } else { + decoding_rle_literals + }; + + // Send huffman literals decoding request right after receiving decoded literals header + let huffman_new_config = match(header_resp.header.literal_type) { + LiteralsBlockType::COMP => true, + LiteralsBlockType::COMP_4 => true, + LiteralsBlockType::TREELESS => false, + LiteralsBlockType::TREELESS_4 => false, + _ => false, + }; + let huffman_multi_stream = match(header_resp.header.literal_type) { + LiteralsBlockType::COMP => false, + LiteralsBlockType::COMP_4 => true, + LiteralsBlockType::TREELESS => false, + LiteralsBlockType::TREELESS_4 => true, + _ => false, + }; + let huffman_req = HuffmanReq { + base_addr: literals_addr, + len: header_resp.header.compressed_size as uN[AXI_ADDR_W], + new_config: huffman_new_config, + multi_stream: huffman_multi_stream, + id: state.id, + literals_last: state.req.literals_last + }; + let huffman_literals_type = header_resp.header.literal_type == LiteralsBlockType::COMP || + header_resp.header.literal_type == LiteralsBlockType::COMP_4 || + header_resp.header.literal_type == LiteralsBlockType::TREELESS || + header_resp.header.literal_type == LiteralsBlockType::TREELESS_4; + let do_send_huffman_req = header_resp_valid && huffman_literals_type && !state.decoding_huffman_literals; + let tok = send_if(tok, huffman_lit_req_s, do_send_huffman_req, huffman_req); + let decoding_huffman_literals = if (do_send_huffman_req) { + trace_fmt!("send HuffmanReq: {:#x}", huffman_req); + true + } else { + decoding_huffman_literals + }; + + // Handle response after literals were decoded + let do_send_resp = raw_resp_valid || + rle_resp_valid || + huffman_resp_valid; + let new_ctrl_req_valid = if (do_send_resp) { + false + } else { + new_ctrl_req_valid + }; + // ERROR status is coded by non-zero integer + // RleLiteralsDecoder cannot fail + // Invalid (not received) response defaults to OKAY + let resp = if (raw_resp.status == RawRespStatus::ERROR || + huffman_resp.status == HuffmanRespStatus::ERROR) { + CtrlResp { status: Status::ERROR } + } else { + CtrlResp { status: Status::OKAY } + }; + let tok = send_if(tok, lit_ctrl_resp_s, do_send_resp, resp); + + let new_id = if (do_send_resp) { + if (state.req_valid && state.req.literals_last) { + u32:0 + } else { + state.id + u32:1 + } + } else { + state.id + }; + + if (do_send_resp) { + trace_fmt!("send CtrlResp: {:#x}", resp); + } else {}; + + let next_state = State { + id: new_id, + req: new_ctrl_req, + req_valid: new_ctrl_req_valid, + decoding_raw_literals: decoding_raw_literals, + decoding_rle_literals: decoding_rle_literals, + decoding_huffman_literals: decoding_huffman_literals, + }; + + next_state + } +} + +const INST_AXI_ADDR_W = u32:16; +proc LiteralsDecoderCtrlInst { + type CtrlReq = LiteralsDecoderCtrlReq; + type CtrlResp = LiteralsDecoderCtrlResp; + type HeaderReq = literals_block_header_dec::LiteralsHeaderDecoderReq; + type HeaderResp = literals_block_header_dec::LiteralsHeaderDecoderResp; + type RawReq = raw_literals_dec::RawLiteralsDecoderReq; + type RawResp = raw_literals_dec::RawLiteralsDecoderResp; + type RleReq = rle_literals_dec::RleLiteralsDecoderReq; + type RleResp = rle_literals_dec::RleLiteralsDecoderResp; + type HuffmanReq = huffman_literals_dec::HuffmanLiteralsDecoderReq; + type HuffmanResp = huffman_literals_dec::HuffmanLiteralsDecoderResp; + + init {} + + config ( + // Literals Decoder control + lit_ctrl_req_r: chan in, + lit_ctrl_resp_s: chan out, + lit_ctrl_header_s: chan out, + + // Literals Header Decoder + lit_header_req_s: chan out, + lit_header_resp_r: chan in, + + // Raw Literals Decoder + raw_lit_req_s: chan out, + raw_lit_resp_r: chan in, + + // Rle Literals Decoder + rle_lit_req_s: chan out, + rle_lit_resp_r: chan in, + + // Huffman Literals Decoder + huffman_lit_req_s: chan out, + huffman_lit_resp_r: chan in + ) { + spawn LiteralsDecoderCtrl( + lit_ctrl_req_r, lit_ctrl_resp_s, lit_ctrl_header_s, + lit_header_req_s, lit_header_resp_r, + raw_lit_req_s, raw_lit_resp_r, + rle_lit_req_s, rle_lit_resp_r, + huffman_lit_req_s, huffman_lit_resp_r + ); + } + + next (state: ()) {} +} + +const TEST_AXI_ADDR_W = u32:16; +const TEST_AXI_DATA_W = u32:64; + +#[test_proc] +proc LiteralsDecoderCtrl_test { + type CtrlReq = LiteralsDecoderCtrlReq; + type CtrlResp = LiteralsDecoderCtrlResp; + type CtrlStatus = LiteralsDecoderCtrlStatus; + type HeaderReq = literals_block_header_dec::LiteralsHeaderDecoderReq; + type HeaderResp = literals_block_header_dec::LiteralsHeaderDecoderResp; + type HeaderStatus = literals_block_header_dec::LiteralsHeaderDecoderStatus; + type Header = literals_block_header_dec::LiteralsHeader; + type LiteralsBlockType = literals_block_header_dec::LiteralsBlockType; + type RawReq = raw_literals_dec::RawLiteralsDecoderReq; + type RawResp = raw_literals_dec::RawLiteralsDecoderResp; + type RawStatus = raw_literals_dec::RawLiteralsDecoderStatus; + type RleReq = rle_literals_dec::RleLiteralsDecoderReq; + type RleResp = rle_literals_dec::RleLiteralsDecoderResp; + type RleStatus = rle_literals_dec::RleLiteralsDecoderStatus; + type HuffmanReq = huffman_literals_dec::HuffmanLiteralsDecoderReq; + type HuffmanResp = huffman_literals_dec::HuffmanLiteralsDecoderResp; + type HuffmanStatus = huffman_literals_dec::HuffmanLiteralsDecoderStatus; + + type Addr = uN[TEST_AXI_ADDR_W]; + + terminator: chan out; + + // Literals Decoder control + lit_ctrl_req_s: chan out; + lit_ctrl_resp_r: chan in; + lit_ctrl_header_r: chan in; + + // Literals Header Decoder + lit_header_req_r: chan in; + lit_header_resp_s: chan out; + + // Raw Literals Decoder + raw_lit_req_r: chan in; + raw_lit_resp_s: chan out; + + // Rle Literals Decoder + rle_lit_req_r: chan in; + rle_lit_resp_s: chan out; + + // Huffman Literals Decoder + huffman_lit_req_r: chan in; + huffman_lit_resp_s: chan out; + + config (terminator: chan out) { + // Literals Decoder control + let (lit_ctrl_req_s, lit_ctrl_req_r) = chan("lit_ctrl_req"); + let (lit_ctrl_resp_s, lit_ctrl_resp_r) = chan("lit_ctrl_resp"); + let (lit_ctrl_header_s, lit_ctrl_header_r) = chan("lit_ctrl_resp"); + + // Literals Header Decoder + let (lit_header_req_s, lit_header_req_r) = chan("lit_header_req"); + let (lit_header_resp_s, lit_header_resp_r) = chan("lit_header_resp"); + + // Raw Literals Decoder + let (raw_lit_req_s, raw_lit_req_r) = chan("raw_lit_req"); + let (raw_lit_resp_s, raw_lit_resp_r) = chan("raw_lit_resp"); + + // Rle Literals Decoder + let (rle_lit_req_s, rle_lit_req_r) = chan("rle_lit_req"); + let (rle_lit_resp_s, rle_lit_resp_r) = chan("rle_lit_resp"); + + // Huffman Literals Decoder + let (huffman_lit_req_s, huffman_lit_req_r) = chan("huffman_lit_req"); + let (huffman_lit_resp_s, huffman_lit_resp_r) = chan("huffman_lit_resp"); + + spawn LiteralsDecoderCtrl( + lit_ctrl_req_r, lit_ctrl_resp_s, lit_ctrl_header_s, + lit_header_req_s, lit_header_resp_r, + raw_lit_req_s, raw_lit_resp_r, + rle_lit_req_s, rle_lit_resp_r, + huffman_lit_req_s, huffman_lit_resp_r + ); + + ( + terminator, + lit_ctrl_req_s, lit_ctrl_resp_r, lit_ctrl_header_r, + lit_header_req_r, lit_header_resp_s, + raw_lit_req_r, raw_lit_resp_s, + rle_lit_req_r, rle_lit_resp_s, + huffman_lit_req_r, huffman_lit_resp_s + ) + } + + init {} + + next (state: ()) { + let tok = join(); + + let lit_ctrl_reqs: CtrlReq[6] = [ + CtrlReq { addr: Addr:0x4, literals_last: false }, + CtrlReq { addr: Addr:0x34, literals_last: false }, + CtrlReq { addr: Addr:0x234, literals_last: true }, + CtrlReq { addr: Addr:0x1234, literals_last: false }, + CtrlReq { addr: Addr:0x2345, literals_last: false }, + CtrlReq { addr: Addr:0x3456, literals_last: true }, + ]; + + let lit_header_resps: HeaderResp[6] = [ + HeaderResp { + header: Header { + literal_type: LiteralsBlockType::RAW, + regenerated_size: u20:0x10, + compressed_size: u20:0x20 + }, + symbol: u8:0x00, + length: u3:5, + status: HeaderStatus::OKAY + }, + HeaderResp { + header: Header { + literal_type: LiteralsBlockType::RAW, + regenerated_size: u20:0x20, + compressed_size: u20:0x10 + }, + symbol: u8:0x00, + length: u3:3, + status: HeaderStatus::OKAY + }, + HeaderResp { + header: Header { + literal_type: LiteralsBlockType::RLE, + regenerated_size: u20:0x15, + compressed_size: u20:0x20 + }, + symbol: u8:0x5B, + length: u3:4, + status: HeaderStatus::OKAY + }, + HeaderResp { + header: Header { + literal_type: LiteralsBlockType::RAW, + regenerated_size: u20:0x10, + compressed_size: u20:0x20 + }, + symbol: u8:0x00, + length: u3:5, + status: HeaderStatus::OKAY + }, + HeaderResp { + header: Header { + literal_type: LiteralsBlockType::RLE, + regenerated_size: u20:0x35, + compressed_size: u20:0x20 + }, + symbol: u8:0x6C, + length: u3:3, + status: HeaderStatus::OKAY + }, + HeaderResp { + header: Header { + literal_type: LiteralsBlockType::RAW, + regenerated_size: u20:0x10, + compressed_size: u20:0x20 + }, + symbol: u8:0x00, + length: u3:5, + status: HeaderStatus::OKAY + } + ]; + + // IDs of decoding requests + // Should be zero after each ctrl request with literals_last == true + let req_ids: u32[6] = [ + u32:0, + u32:1, + u32:2, + u32:0, + u32:1, + u32:2, + ]; + + // Test logic + let tok = for (i, tok): (u32, token) in range(u32:0, u32:6) { + let lit_ctrl_req = lit_ctrl_reqs[i]; + let expected_lit_header_req = HeaderReq { addr: lit_ctrl_req.addr }; + let lit_header_resp = lit_header_resps[i]; + + let tok = send(tok, lit_ctrl_req_s, lit_ctrl_req); + trace_fmt!("Test: Sent CtrlReq: {:#x}", lit_ctrl_req); + + let (tok, lit_header_req) = recv(tok, lit_header_req_r); + trace_fmt!("Test: Received HeaderReq: {:#x}", lit_header_req); + assert_eq(lit_header_req, expected_lit_header_req); + let tok = send(tok, lit_header_resp_s, lit_header_resp); + trace_fmt!("Test: Sent HeaderResp: {:#x}", lit_header_resp); + + if (lit_header_resp.header.literal_type == LiteralsBlockType::RAW) { + let (tok, raw_lit_req) = recv(tok, raw_lit_req_r); + trace_fmt!("Test: Received RawReq: {:#x}", raw_lit_req); + let expected_raw_lit_req = RawReq { + id: req_ids[i], + addr: lit_ctrl_reqs[i].addr + lit_header_resps[i].length as Addr, + length: lit_header_resps[i].header.regenerated_size as Addr, + literals_last: lit_ctrl_reqs[i].literals_last + }; + assert_eq(raw_lit_req, expected_raw_lit_req); + let raw_lit_resp = RawResp { status: RawStatus::OKAY }; + let tok = send(tok, raw_lit_resp_s, raw_lit_resp); + trace_fmt!("Test: Sent RawResp: {:#x}", raw_lit_resp); + } else if (lit_header_resp.header.literal_type == LiteralsBlockType::RLE) { + let (tok, rle_lit_req) = recv(tok, rle_lit_req_r); + trace_fmt!("Test: Received RleReq: {:#x}", rle_lit_req); + let expected_rle_lit_req = RleReq { + id: req_ids[i], + symbol: lit_header_resps[i].symbol, + length: lit_header_resps[i].header.regenerated_size, + literals_last: lit_ctrl_reqs[i].literals_last + }; + assert_eq(rle_lit_req, expected_rle_lit_req); + let rle_lit_resp = RleResp { status: RleStatus::OKAY }; + let tok = send(tok, rle_lit_resp_s, rle_lit_resp); + trace_fmt!("Test: Sent RleResp: {:#x}", rle_lit_resp); + } else { + //let (tok, huffman_lit_req) = recv(tok, huffman_lit_req_r); + //trace_fmt!("Test: Received HuffmanReq: {:#x}", huffman_lit_req); + //let expected_huffman_lit_req = HuffmanReq { + //}; + //assert_eq(huffman_lit_req, expected_huffman_lit_req); + //let huffman_lit_resp = HuffmanResp { status: HuffmanStatus::OKAY }; + //let tok = send(tok, huffman_lit_resp_s, huffman_lit_resp); + //trace_fmt!("Test: Sent HuffmanResp: {:#x}", huffman_lit_resp); + }; + + let (tok, lit_ctrl_resp) = recv(tok, lit_ctrl_resp_r); + trace_fmt!("Test: Received CtrlResp: {:#x}", lit_ctrl_resp); + let expected_lit_ctrl_resp = CtrlResp { status: CtrlStatus::OKAY }; + assert_eq(lit_ctrl_resp, expected_lit_ctrl_resp); + + tok + }(tok); + + send(tok, terminator, true); + } +} + +pub 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)}, + // AXI parameters + AXI_DATA_W: u32, AXI_ADDR_W: u32, AXI_ID_W: u32, AXI_DEST_W: u32, + // Huffman weights memory parameters + HUFFMAN_WEIGHTS_RAM_ADDR_WIDTH: u32 = {huffman_literals_dec::WEIGHTS_ADDR_WIDTH}, + HUFFMAN_WEIGHTS_RAM_DATA_WIDTH: u32 = {huffman_literals_dec::WEIGHTS_DATA_WIDTH}, + HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS: u32 = {huffman_literals_dec::WEIGHTS_NUM_PARTITIONS}, + // Huffman prescan memory parameters + HUFFMAN_PRESCAN_RAM_ADDR_WIDTH: u32 = {huffman_literals_dec::PRESCAN_ADDR_WIDTH}, + HUFFMAN_PRESCAN_RAM_DATA_WIDTH: u32 = {huffman_literals_dec::PRESCAN_DATA_WIDTH}, + HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS: u32 = {huffman_literals_dec::PRESCAN_NUM_PARTITIONS}, + // Literals buffer memory parameters + LITERALS_BUFFER_RAM_ADDR_WIDTH: u32 = {parallel_rams::ram_addr_width(HISTORY_BUFFER_SIZE_KB)}, + LITERALS_BUFFER_RAM_SIZE: u32 = {parallel_rams::ram_size(HISTORY_BUFFER_SIZE_KB)}, + LITERALS_BUFFER_RAM_DATA_WIDTH: u32 = {literals_buffer::RAM_DATA_WIDTH}, + LITERALS_BUFFER_RAM_NUM_PARTITIONS: u32 = {literals_buffer::RAM_NUM_PARTITIONS}, > { - type ReadReq = ram::ReadReq; - type ReadResp = ram::ReadResp; - type WriteReq = ram::WriteReq; + 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; + type MemAxiAr = axi::AxiAr; + type MemAxiR = axi::AxiR; + + type CtrlReq = LiteralsDecoderCtrlReq; + type CtrlResp = LiteralsDecoderCtrlResp; + type BufferCtrl = common::LiteralsBufferCtrl; + type BufferOut = common::SequenceExecutorPacket; + + // TODO: make sure those can use the same parameters + type HuffmanWeightsReadReq = ram::ReadReq; + type HuffmanWeightsReadResp = ram::ReadResp; + type HuffmanWeightsWriteReq = ram::WriteReq; + type HuffmanWeightsWriteResp = ram::WriteResp; + type HuffmanPrescanReadReq = ram::ReadReq; + type HuffmanPrescanReadResp = ram::ReadResp; + type HuffmanPrescanWriteReq = ram::WriteReq; + type HuffmanPrescanWriteResp = ram::WriteResp; + + type HeaderResp = literals_block_header_dec::LiteralsHeaderDecoderResp; config ( - literals_ctrl_r: chan in, - literals_data_r: chan in, - literals_buf_ctrl_r: chan in, - literals_s: chan out, + // AXI Literals Header Decoder (manager) + lit_header_axi_ar_s: chan out, + lit_header_axi_r_r: chan in, + + // AXI Raw Literals Decoder (manager) + raw_lit_axi_ar_s: chan out, + raw_lit_axi_r_r: chan in, + + // AXI Huffman Literals Decoder (manager) + huffman_lit_axi_ar_s: chan out, + huffman_lit_axi_r_r: chan in, + + // AXI Huffman Jump Table Decoder (manager) + huffman_jump_table_axi_ar_s: chan out, + huffman_jump_table_axi_r_r: chan in, + + // AXI Huffman Weights Header Decoder (manager) + huffman_weights_header_axi_ar_s: chan out, + huffman_weights_header_axi_r_r: chan in, + + // AXI Huffman Weights RAW Decoder (manager) + huffman_weights_raw_axi_ar_s: chan out, + huffman_weights_raw_axi_r_r: chan in, + + // AXI Huffman Weights FSE Decoder (manager) + huffman_weights_fse_axi_ar_s: chan out, + huffman_weights_fse_axi_r_r: chan in, + + // Literals Decoder control + lit_ctrl_req_r: chan in, + lit_ctrl_resp_s: chan out, + lit_ctrl_header_s: chan out, + + // Literals Decoder output control + lit_buf_ctrl_r: chan in, + lit_buf_out_s: chan out, + + // Internal memory rd_req_m0_s: chan out, rd_req_m1_s: chan out, rd_req_m2_s: chan out, @@ -90,27 +722,105 @@ proc LiteralsDecoder< wr_resp_m4_r: chan in, wr_resp_m5_r: chan in, wr_resp_m6_r: chan in, - wr_resp_m7_r: chan in + wr_resp_m7_r: chan in, + + // Huffman weights memory + huffman_lit_weights_mem_rd_req_s: chan out, + huffman_lit_weights_mem_rd_resp_r: chan in, + huffman_lit_weights_mem_wr_req_s: chan out, + huffman_lit_weights_mem_wr_resp_r: chan in, + // Huffman prescan memory + huffman_lit_prescan_mem_rd_req_s: chan out, + huffman_lit_prescan_mem_rd_resp_r: chan in, + huffman_lit_prescan_mem_wr_req_s: chan out, + huffman_lit_prescan_mem_wr_resp_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"); + type HeaderReq = literals_block_header_dec::LiteralsHeaderDecoderReq; + type HeaderResp = literals_block_header_dec::LiteralsHeaderDecoderResp; + type RawReq = raw_literals_dec::RawLiteralsDecoderReq; + type RawResp = raw_literals_dec::RawLiteralsDecoderResp; + type RleReq = rle_literals_dec::RleLiteralsDecoderReq; + type RleResp = rle_literals_dec::RleLiteralsDecoderResp; + type HuffmanReq = huffman_literals_dec::HuffmanLiteralsDecoderReq; + type HuffmanResp = huffman_literals_dec::HuffmanLiteralsDecoderResp; + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + + const CHANNEL_DEPTH = u32:1; + // Literals Header Decoder + let (lit_header_mem_rd_req_s, lit_header_mem_rd_req_r) = chan("lit_header_mem_rd_req"); + let (lit_header_mem_rd_resp_s, lit_header_mem_rd_resp_r) = chan("lit_header_mem_rd_resp"); + + spawn mem_reader::MemReader( + lit_header_mem_rd_req_r, lit_header_mem_rd_resp_s, + lit_header_axi_ar_s, lit_header_axi_r_r + ); + + let (lit_header_req_s, lit_header_req_r) = chan("lit_header_req"); + let (lit_header_resp_s, lit_header_resp_r) = chan("lit_header_resp"); + + spawn literals_block_header_dec::LiteralsHeaderDecoder( + lit_header_req_r, lit_header_resp_s, + lit_header_mem_rd_req_s, lit_header_mem_rd_resp_r + ); - 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"); + // Raw Literals Decoder + let (raw_lit_mem_rd_req_s, raw_lit_mem_rd_req_r) = chan("raw_lit_mem_rd_req"); + let (raw_lit_mem_rd_resp_s, raw_lit_mem_rd_resp_r) = chan("raw_lit_mem_rd_resp"); - spawn literals_dispatcher::LiteralsDispatcher( - literals_ctrl_r, literals_data_r, - raw_literals_s, rle_literals_s, huff_literals_s, + spawn mem_reader::MemReader( + raw_lit_mem_rd_req_r, raw_lit_mem_rd_resp_s, + raw_lit_axi_ar_s, raw_lit_axi_r_r ); - spawn raw_literals_dec::RawLiteralsDecoder(raw_literals_r, decoded_raw_literals_s); + let (raw_lit_req_s, raw_lit_req_r) = chan("raw_lit_req"); + let (raw_lit_resp_s, raw_lit_resp_r) = chan("raw_lit_resp"); + let (raw_lit_output_s, raw_lit_output_r) = chan("raw_lit_output"); - spawn rle_literals_dec::RleLiteralsDecoder(rle_literals_r, decoded_rle_literals_s); + spawn raw_literals_dec::RawLiteralsDecoder( + raw_lit_req_r, raw_lit_resp_s, raw_lit_output_s, + raw_lit_mem_rd_req_s, raw_lit_mem_rd_resp_r + ); + + // Rle Literals Decoder + let (rle_lit_req_s, rle_lit_req_r) = chan("rle_lit_req"); + let (rle_lit_resp_s, rle_lit_resp_r) = chan("rle_lit_resp"); + let (rle_lit_output_s, rle_lit_output_r) = chan("rle_lit_output"); - spawn literals_buffer::LiteralsBuffer ( - decoded_raw_literals_r, decoded_rle_literals_r, huff_literals_r, - literals_buf_ctrl_r, literals_s, + spawn rle_literals_dec::RleLiteralsDecoder( + rle_lit_req_r, rle_lit_resp_s, rle_lit_output_s + ); + + // Huffman Literals Decoder + let (huffman_lit_req_s, huffman_lit_req_r) = chan("huffman_lit_req"); + let (huffman_lit_resp_s, huffman_lit_resp_r) = chan("huffman_lit_resp"); + let (huffman_lit_output_s, huffman_lit_output_r) = chan("huffman_lit_output"); + + spawn huffman_literals_dec::HuffmanLiteralsDecoder< + AXI_DATA_W, AXI_ADDR_W, AXI_ID_W, AXI_DEST_W, + HUFFMAN_WEIGHTS_RAM_ADDR_WIDTH, HUFFMAN_WEIGHTS_RAM_DATA_WIDTH, HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS, + HUFFMAN_PRESCAN_RAM_ADDR_WIDTH, HUFFMAN_PRESCAN_RAM_DATA_WIDTH, HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS + >( + huffman_lit_req_r, huffman_lit_resp_s, huffman_lit_output_s, + huffman_lit_axi_ar_s, huffman_lit_axi_r_r, + huffman_jump_table_axi_ar_s, huffman_jump_table_axi_r_r, + huffman_weights_header_axi_ar_s, huffman_weights_header_axi_r_r, + huffman_weights_raw_axi_ar_s, huffman_weights_raw_axi_r_r, + huffman_weights_fse_axi_ar_s, huffman_weights_fse_axi_r_r, + huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_resp_r, + huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_resp_r, + huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_resp_r, + huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_resp_r, + ); + + // Literals Buffer + spawn literals_buffer::LiteralsBuffer< + HISTORY_BUFFER_SIZE_KB, + LITERALS_BUFFER_RAM_SIZE, + LITERALS_BUFFER_RAM_ADDR_WIDTH + > ( + raw_lit_output_r, rle_lit_output_r, huffman_lit_output_r, + lit_buf_ctrl_r, lit_buf_out_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, @@ -121,10 +831,15 @@ proc LiteralsDecoder< 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, - ) + spawn LiteralsDecoderCtrl ( + lit_ctrl_req_r, lit_ctrl_resp_s, lit_ctrl_header_s, + lit_header_req_s, lit_header_resp_r, + raw_lit_req_s, raw_lit_resp_r, + rle_lit_req_s, rle_lit_resp_r, + huffman_lit_req_s, huffman_lit_resp_r, + ); + + () } init { } @@ -134,18 +849,79 @@ proc LiteralsDecoder< 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); +const INST_AXI_DATA_W:u32 = u32:64; +const INST_AXI_ID_W:u32 = u32:4; +const INST_AXI_DEST_W:u32 = u32:4; + +const INST_HUFFMAN_WEIGHTS_RAM_ADDR_WIDTH = huffman_literals_dec::INST_WEIGHTS_RAM_ADDR_WIDTH; +const INST_HUFFMAN_WEIGHTS_RAM_DATA_WIDTH = huffman_literals_dec::INST_WEIGHTS_RAM_DATA_WIDTH; +const INST_HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS = huffman_literals_dec::INST_WEIGHTS_RAM_NUM_PARTITIONS; +const INST_HUFFMAN_PRESCAN_RAM_ADDR_WIDTH = huffman_literals_dec::INST_PRESCAN_RAM_ADDR_WIDTH; +const INST_HUFFMAN_PRESCAN_RAM_DATA_WIDTH = huffman_literals_dec::INST_PRESCAN_RAM_DATA_WIDTH; +const INST_HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS = huffman_literals_dec::INST_PRESCAN_RAM_NUM_PARTITIONS; proc LiteralsDecoderInst { type ReadReq = ram::ReadReq; type ReadResp = ram::ReadResp; type WriteReq = ram::WriteReq; type WriteResp = ram::WriteResp; + type MemAxiAr = axi::AxiAr; + type MemAxiR = axi::AxiR; + + type CtrlReq = LiteralsDecoderCtrlReq; + type CtrlResp = LiteralsDecoderCtrlResp; + type BufferCtrl = common::LiteralsBufferCtrl; + type BufferOut = common::SequenceExecutorPacket; + + type HuffmanWeightsReadReq = ram::ReadReq; + type HuffmanWeightsReadResp = ram::ReadResp; + type HuffmanWeightsWriteReq = ram::WriteReq; + type HuffmanWeightsWriteResp = ram::WriteResp; + type HuffmanPrescanReadReq = ram::ReadReq; + type HuffmanPrescanReadResp = ram::ReadResp; + type HuffmanPrescanWriteReq = ram::WriteReq; + type HuffmanPrescanWriteResp = ram::WriteResp; + + type HeaderResp = literals_block_header_dec::LiteralsHeaderDecoderResp; config ( - literals_ctrl_r: chan in, - literals_data_r: chan in, - literals_buf_ctrl_r: chan in, - literals_s: chan out, + // AXI Literals Header Decoder (manager) + lit_header_axi_ar_s: chan out, + lit_header_axi_r_r: chan in, + + // AXI Raw Literals Decoder (manager) + raw_lit_axi_ar_s: chan out, + raw_lit_axi_r_r: chan in, + + // AXI Huffman Literals Decoder (manager) + huffman_lit_axi_ar_s: chan out, + huffman_lit_axi_r_r: chan in, + + // AXI Huffman Jump Table Decoder (manager) + huffman_jump_table_axi_ar_s: chan out, + huffman_jump_table_axi_r_r: chan in, + + // AXI Huffman Weights header Decoder (manager) + huffman_weights_header_axi_ar_s: chan out, + huffman_weights_header_axi_r_r: chan in, + + // AXI Huffman Weights RAW Decoder (manager) + huffman_weights_raw_axi_ar_s: chan out, + huffman_weights_raw_axi_r_r: chan in, + + // AXI Huffman Weights FSE Decoder (manager) + huffman_weights_fse_axi_ar_s: chan out, + huffman_weights_fse_axi_r_r: chan in, + + // Literals Decoder control + lit_ctrl_req_r: chan in, + lit_ctrl_resp_s: chan out, + lit_ctrl_header_s: chan out, + + // Literals Decoder output control + lit_buf_ctrl_r: chan in, + lit_buf_out_s: chan out, + rd_req_m0_s: chan out, rd_req_m1_s: chan out, rd_req_m2_s: chan out, @@ -177,11 +953,33 @@ proc LiteralsDecoderInst { wr_resp_m4_r: chan in, wr_resp_m5_r: chan in, wr_resp_m6_r: chan in, - wr_resp_m7_r: chan in + wr_resp_m7_r: chan in, + + // Huffman weights memory + huffman_lit_weights_mem_rd_req_s: chan out, + huffman_lit_weights_mem_rd_resp_r: chan in, + huffman_lit_weights_mem_wr_req_s: chan out, + huffman_lit_weights_mem_wr_resp_r: chan in, + // Huffman prescan memory + huffman_lit_prescan_mem_rd_req_s: chan out, + huffman_lit_prescan_mem_rd_resp_r: chan in, + huffman_lit_prescan_mem_wr_req_s: chan out, + huffman_lit_prescan_mem_wr_resp_r: chan in ) { - spawn LiteralsDecoder ( - literals_ctrl_r, literals_data_r, - literals_buf_ctrl_r, literals_s, + spawn LiteralsDecoder ( + lit_header_axi_ar_s, lit_header_axi_r_r, + raw_lit_axi_ar_s, raw_lit_axi_r_r, + huffman_lit_axi_ar_s, huffman_lit_axi_r_r, + huffman_jump_table_axi_ar_s, huffman_jump_table_axi_r_r, + huffman_weights_header_axi_ar_s, huffman_weights_header_axi_r_r, + huffman_weights_raw_axi_ar_s, huffman_weights_raw_axi_r_r, + huffman_weights_fse_axi_ar_s, huffman_weights_fse_axi_r_r, + lit_ctrl_req_r, lit_ctrl_resp_s, lit_ctrl_header_s, + lit_buf_ctrl_r, lit_buf_out_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, @@ -190,6 +988,10 @@ proc LiteralsDecoderInst { 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, + huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_resp_r, + huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_resp_r, + huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_resp_r, + huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_resp_r, ); } @@ -198,161 +1000,191 @@ proc LiteralsDecoderInst { 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: true}, - 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 - }, -]; +const TEST_HISTORY_BUFFER_SIZE_KB:u32 = u32:1; + +// Parameters for the AXI bus connecting LiteralsBlockHeaderDecoder, +// RawLiteralsDecoder and HuffmanLiteralsDecoder to the system memory +const TEST_AXI_RAM_ADDR_W:u32 = u32:32; +const TEST_AXI_RAM_DATA_W:u32 = u32:64; +const TEST_AXI_RAM_ID_W:u32 = u32:8; +const TEST_AXI_RAM_DEST_W:u32 = u32:8; + +// Parameters for RamModels used for mocking the system memory for +// the LiteralsBlockHeaderDecoder, RawLiteralsDecoder and HuffmanLiteralsDecoder +const TEST_AXI_RAM_MODEL_DATA_WIDTH:u32 = TEST_AXI_RAM_DATA_W; +const TEST_AXI_RAM_MODEL_SIZE:u32 = u32:1024; +const TEST_AXI_RAM_MODEL_ADDR_WIDTH:u32 = std::clog2(TEST_AXI_RAM_MODEL_SIZE); +const TEST_AXI_RAM_MODEL_WORD_PARTITION_SIZE:u32 = u32:8; +const TEST_AXI_RAM_MODEL_NUM_PARTITIONS:u32 = ram::num_partitions(TEST_AXI_RAM_MODEL_WORD_PARTITION_SIZE, TEST_AXI_RAM_MODEL_DATA_WIDTH); +const TEST_AXI_RAM_MODEL_BASE_ADDR:u32 = u32:0; +const TEST_AXI_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_AXI_RAM_MODEL_INITIALIZED = true; +const TEST_AXI_RAM_MODEL_ASSERT_VALID_READ = true; +const TEST_AXI_RAM_MODEL_NUM = u32:1; + +// Parameters for RamModels used for mocking the LiteralsBuffer internal memory +const TEST_LITERALS_BUFFER_RAM_MODEL_DATA_WIDTH:u32 = literals_buffer::RAM_DATA_WIDTH; +const TEST_LITERALS_BUFFER_RAM_MODEL_SIZE:u32 = parallel_rams::ram_size(TEST_HISTORY_BUFFER_SIZE_KB); +const TEST_LITERALS_BUFFER_RAM_MODEL_ADDR_WIDTH:u32 = parallel_rams::ram_addr_width(TEST_HISTORY_BUFFER_SIZE_KB); +const TEST_LITERALS_BUFFER_RAM_MODEL_WORD_PARTITION_SIZE:u32 = literals_buffer::RAM_WORD_PARTITION_SIZE; +const TEST_LITERALS_BUFFER_RAM_MODEL_NUM_PARTITIONS:u32 = literals_buffer::RAM_NUM_PARTITIONS; +const TEST_LITERALS_BUFFER_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_LITERALS_BUFFER_RAM_MODEL_INITIALIZED = true; +const TEST_LITERALS_BUFFER_RAM_MODEL_ASSERT_VALID_READ = true; +const TEST_LITERALS_BUFFER_RAM_MODEL_NUM = literals_buffer::RAM_NUM; + +// Parameters for RamModels used for mocking the HuffmanLiteralsDecoder prescan weights memory +const TEST_HUFFMAN_PRESCAN_RAM_MODEL_DATA_WIDTH:u32 = huffman_literals_dec::TEST_PRESCAN_RAM_DATA_WIDTH; +const TEST_HUFFMAN_PRESCAN_RAM_MODEL_SIZE:u32 = huffman_literals_dec::TEST_PRESCAN_RAM_SIZE; +const TEST_HUFFMAN_PRESCAN_RAM_MODEL_ADDR_WIDTH:u32 = huffman_literals_dec::TEST_PRESCAN_RAM_ADDR_WIDTH; +const TEST_HUFFMAN_PRESCAN_RAM_MODEL_WORD_PARTITION_SIZE:u32 = huffman_literals_dec::TEST_PRESCAN_WORD_PARTITION_SIZE; +const TEST_HUFFMAN_PRESCAN_RAM_MODEL_NUM_PARTITIONS:u32 = huffman_literals_dec::TEST_PRESCAN_RAM_NUM_PARTITIONS; +const TEST_HUFFMAN_PRESCAN_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_HUFFMAN_PRESCAN_RAM_MODEL_INITIALIZED = true; +const TEST_HUFFMAN_PRESCAN_RAM_MODEL_ASSERT_VALID_READ = true; + +// Parameters for RamModels used for mocking the HuffmanLiteralsDecoder internal weights memory +const TEST_HUFFMAN_WEIGHTS_RAM_MODEL_DATA_WIDTH:u32 = huffman_literals_dec::TEST_WEIGHTS_RAM_DATA_WIDTH; +const TEST_HUFFMAN_WEIGHTS_RAM_MODEL_SIZE:u32 = huffman_literals_dec::TEST_WEIGHTS_RAM_SIZE; +const TEST_HUFFMAN_WEIGHTS_RAM_MODEL_ADDR_WIDTH:u32 = huffman_literals_dec::TEST_WEIGHTS_RAM_ADDR_WIDTH; +const TEST_HUFFMAN_WEIGHTS_RAM_MODEL_WORD_PARTITION_SIZE:u32 = huffman_literals_dec::TEST_WEIGHTS_WORD_PARTITION_SIZE; +const TEST_HUFFMAN_WEIGHTS_RAM_MODEL_NUM_PARTITIONS:u32 = huffman_literals_dec::TEST_WEIGHTS_RAM_NUM_PARTITIONS; +const TEST_HUFFMAN_WEIGHTS_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_HUFFMAN_WEIGHTS_RAM_MODEL_INITIALIZED = true; +const TEST_HUFFMAN_WEIGHTS_RAM_MODEL_ASSERT_VALID_READ = true; #[test_proc] proc LiteralsDecoder_test { + // LiteralsBuffer internal memory + type LiteralsBufferRamRdReq = ram::ReadReq; + type LiteralsBufferRamRdResp = ram::ReadResp; + type LiteralsBufferRamWrReq = ram::WriteReq; + type LiteralsBufferRamWrResp = ram::WriteResp; + + // System bus + type MemAxiR = axi::AxiR; + type MemAxiAr = axi::AxiAr; + + // System bus external memory + type AxiRamRdReq = ram::ReadReq; + type AxiRamRdResp = ram::ReadResp; + type AxiRamWrReq = ram::WriteReq; + type AxiRamWrResp = ram::WriteResp; + + // Huffman weights internal memory + type HuffmanWeightsRamRdReq = ram::ReadReq; + type HuffmanWeightsRamRdResp = ram::ReadResp; + type HuffmanWeightsRamWrReq = ram::WriteReq; + type HuffmanWeightsRamWrResp = ram::WriteResp; + + // Huffman prescan internal memory + type HuffmanPrescanRamRdReq = ram::ReadReq; + type HuffmanPrescanRamRdResp = ram::ReadResp; + type HuffmanPrescanRamWrReq = ram::WriteReq; + type HuffmanPrescanRamWrResp = ram::WriteResp; + + // Control and output + type CtrlReq = LiteralsDecoderCtrlReq; + type CtrlResp = LiteralsDecoderCtrlResp; + type CtrlStatus = LiteralsDecoderCtrlStatus; + type BufferCtrl = common::LiteralsBufferCtrl; + type BufferOut = common::SequenceExecutorPacket; + + type AxiRamData = uN[TEST_AXI_RAM_MODEL_DATA_WIDTH]; + type AxiRamAddr = uN[TEST_AXI_RAM_MODEL_ADDR_WIDTH]; + type AxiRamMask = uN[TEST_AXI_RAM_MODEL_NUM_PARTITIONS]; + + type AxiAddr = uN[TEST_AXI_RAM_ADDR_W]; + + type HeaderResp = literals_block_header_dec::LiteralsHeaderDecoderResp; + terminator: chan out; - literals_ctrl_s: chan out; - literals_data_s: chan out; - literals_buf_ctrl_s: chan out; - literals_r: chan in; + // Literals Decoder control + ctrl_req_s: chan out; + ctrl_resp_r: chan in; + ctrl_header_r: chan in; + + // Output control + buf_ctrl_s: chan out; + buf_out_r: chan in; print_start_s: chan<()> out; print_finish_r: chan<()> in; + ram_wr_req_header_s : chan out; + ram_wr_resp_header_r : chan in; + ram_wr_req_raw_s : chan out; + ram_wr_resp_raw_r : chan in; + ram_wr_req_huffman_s : chan out; + ram_wr_resp_huffman_r : chan in; + ram_wr_req_huffman_jump_table_s : chan out; + ram_wr_resp_huffman_jump_table_r : chan in; + ram_wr_req_huffman_weights_header_s : chan out; + ram_wr_resp_huffman_weights_header_r : chan in; + ram_wr_req_huffman_weights_raw_s : chan out; + ram_wr_resp_huffman_weights_raw_r : chan in; + ram_wr_req_huffman_weights_fse_s : chan out; + ram_wr_resp_huffman_weights_fse_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 (lit_header_axi_ar_s, lit_header_axi_ar_r) = chan("lit_header_axi_ar"); + let (lit_header_axi_r_s, lit_header_axi_r_r) = chan("lit_header_axi_r"); + + let (raw_lit_axi_ar_s, raw_lit_axi_ar_r) = chan("raw_lit_axi_ar"); + let (raw_lit_axi_r_s, raw_lit_axi_r_r) = chan("raw_lit_axi_r"); + + let (huffman_lit_axi_ar_s, huffman_lit_axi_ar_r) = chan("huffman_lit_axi_ar"); + let (huffman_lit_axi_r_s, huffman_lit_axi_r_r) = chan("huffman_lit_axi_r"); + let (huffman_jump_table_axi_ar_s, huffman_jump_table_axi_ar_r) = chan("huffman_jump_table_axi_ar"); + let (huffman_jump_table_axi_r_s, huffman_jump_table_axi_r_r) = chan("huffman_jump_table_axi_r"); + let (huffman_weights_header_axi_ar_s, huffman_weights_header_axi_ar_r) = chan("huffman_weights_header_axi_ar"); + let (huffman_weights_header_axi_r_s, huffman_weights_header_axi_r_r) = chan("huffman_weights_header_axi_r"); + let (huffman_weights_raw_axi_ar_s, huffman_weights_raw_axi_ar_r) = chan("huffman_weights_raw_axi_ar"); + let (huffman_weights_raw_axi_r_s, huffman_weights_raw_axi_r_r) = chan("huffman_weights_raw_axi_r"); + let (huffman_weights_fse_axi_ar_s, huffman_weights_fse_axi_ar_r) = chan("huffman_weights_fse_axi_ar"); + let (huffman_weights_fse_axi_r_s, huffman_weights_fse_axi_r_r) = chan("huffman_weights_fse_axi_r"); + + let (ctrl_req_s, ctrl_req_r) = chan("ctrl_req"); + let (ctrl_resp_s, ctrl_resp_r) = chan("ctrl_resp"); + let (ctrl_header_s, ctrl_header_r) = chan("ctrl_header"); + let (buf_ctrl_s, buf_ctrl_r) = chan("buf_ctrl"); + let (buf_out_s, buf_out_r) = chan("buf_out"); 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, + 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"); + + let (huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_req_r) = chan("huffman_lit_weights_mem_rd_req"); + let (huffman_lit_weights_mem_rd_resp_s, huffman_lit_weights_mem_rd_resp_r) = chan("huffman_lit_weights_mem_rd_resp"); + let (huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_req_r) = chan("huffman_lit_weights_mem_wr_req"); + let (huffman_lit_weights_mem_wr_resp_s, huffman_lit_weights_mem_wr_resp_r) = chan("huffman_lit_weights_mem_wr_resp"); + + let (huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_req_r) = chan("huffman_lit_prescan_mem_rd_req"); + let (huffman_lit_prescan_mem_rd_resp_s, huffman_lit_prescan_mem_rd_resp_r) = chan("huffman_lit_prescan_mem_rd_resp"); + let (huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_req_r) = chan("huffman_lit_prescan_mem_wr_req"); + let (huffman_lit_prescan_mem_wr_resp_s, huffman_lit_prescan_mem_wr_resp_r) = chan("huffman_lit_prescan_mem_wr_resp"); + + spawn LiteralsDecoder< + TEST_HISTORY_BUFFER_SIZE_KB, + TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_DEST_W, + TEST_HUFFMAN_WEIGHTS_RAM_MODEL_ADDR_WIDTH, TEST_HUFFMAN_WEIGHTS_RAM_MODEL_DATA_WIDTH, TEST_HUFFMAN_WEIGHTS_RAM_MODEL_NUM_PARTITIONS, + TEST_HUFFMAN_PRESCAN_RAM_MODEL_ADDR_WIDTH, TEST_HUFFMAN_PRESCAN_RAM_MODEL_DATA_WIDTH, TEST_HUFFMAN_PRESCAN_RAM_MODEL_NUM_PARTITIONS, + TEST_LITERALS_BUFFER_RAM_MODEL_ADDR_WIDTH, TEST_LITERALS_BUFFER_RAM_MODEL_SIZE, TEST_LITERALS_BUFFER_RAM_MODEL_DATA_WIDTH, TEST_LITERALS_BUFFER_RAM_MODEL_NUM_PARTITIONS + > ( + lit_header_axi_ar_s, lit_header_axi_r_r, + raw_lit_axi_ar_s, raw_lit_axi_r_r, + huffman_lit_axi_ar_s, huffman_lit_axi_r_r, + huffman_jump_table_axi_ar_s, huffman_jump_table_axi_r_r, + huffman_weights_header_axi_ar_s, huffman_weights_header_axi_r_r, + huffman_weights_raw_axi_ar_s, huffman_weights_raw_axi_r_r, + huffman_weights_fse_axi_ar_s, huffman_weights_fse_axi_r_r, + ctrl_req_r, ctrl_resp_s, ctrl_header_s, + buf_ctrl_r, buf_out_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], @@ -360,91 +1192,591 @@ proc LiteralsDecoder_test { 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] + ram_wr_resp_r[4], ram_wr_resp_r[5], ram_wr_resp_r[6], ram_wr_resp_r[7], + huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_resp_r, + huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_resp_r, + huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_resp_r, + huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_resp_r, ); 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); + TEST_LITERALS_BUFFER_RAM_MODEL_DATA_WIDTH, + TEST_LITERALS_BUFFER_RAM_MODEL_SIZE, + TEST_LITERALS_BUFFER_RAM_MODEL_NUM_PARTITIONS, + TEST_LITERALS_BUFFER_RAM_MODEL_ADDR_WIDTH, + TEST_LITERALS_BUFFER_RAM_MODEL_NUM + > ( + print_start_r, print_finish_s, ram_rd_req_s, ram_rd_resp_r + ); + + spawn ram::RamModel< + TEST_LITERALS_BUFFER_RAM_MODEL_DATA_WIDTH, + TEST_LITERALS_BUFFER_RAM_MODEL_SIZE, + TEST_LITERALS_BUFFER_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_LITERALS_BUFFER_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_LITERALS_BUFFER_RAM_MODEL_INITIALIZED + > ( + ram_rd_req_r[0], ram_rd_resp_s[0], ram_wr_req_r[0], ram_wr_resp_s[0] + ); + spawn ram::RamModel< + TEST_LITERALS_BUFFER_RAM_MODEL_DATA_WIDTH, + TEST_LITERALS_BUFFER_RAM_MODEL_SIZE, + TEST_LITERALS_BUFFER_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_LITERALS_BUFFER_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_LITERALS_BUFFER_RAM_MODEL_INITIALIZED + > ( + ram_rd_req_r[1], ram_rd_resp_s[1], ram_wr_req_r[1], ram_wr_resp_s[1] + ); + spawn ram::RamModel< + TEST_LITERALS_BUFFER_RAM_MODEL_DATA_WIDTH, + TEST_LITERALS_BUFFER_RAM_MODEL_SIZE, + TEST_LITERALS_BUFFER_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_LITERALS_BUFFER_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_LITERALS_BUFFER_RAM_MODEL_INITIALIZED + > ( + ram_rd_req_r[2], ram_rd_resp_s[2], ram_wr_req_r[2], ram_wr_resp_s[2] + ); + spawn ram::RamModel< + TEST_LITERALS_BUFFER_RAM_MODEL_DATA_WIDTH, + TEST_LITERALS_BUFFER_RAM_MODEL_SIZE, + TEST_LITERALS_BUFFER_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_LITERALS_BUFFER_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_LITERALS_BUFFER_RAM_MODEL_INITIALIZED + > ( + ram_rd_req_r[3], ram_rd_resp_s[3], ram_wr_req_r[3], ram_wr_resp_s[3] + ); + spawn ram::RamModel< + TEST_LITERALS_BUFFER_RAM_MODEL_DATA_WIDTH, + TEST_LITERALS_BUFFER_RAM_MODEL_SIZE, + TEST_LITERALS_BUFFER_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_LITERALS_BUFFER_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_LITERALS_BUFFER_RAM_MODEL_INITIALIZED + > ( + ram_rd_req_r[4], ram_rd_resp_s[4], ram_wr_req_r[4], ram_wr_resp_s[4] + ); + spawn ram::RamModel< + TEST_LITERALS_BUFFER_RAM_MODEL_DATA_WIDTH, + TEST_LITERALS_BUFFER_RAM_MODEL_SIZE, + TEST_LITERALS_BUFFER_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_LITERALS_BUFFER_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_LITERALS_BUFFER_RAM_MODEL_INITIALIZED + > ( + ram_rd_req_r[5], ram_rd_resp_s[5], ram_wr_req_r[5], ram_wr_resp_s[5] + ); + spawn ram::RamModel< + TEST_LITERALS_BUFFER_RAM_MODEL_DATA_WIDTH, + TEST_LITERALS_BUFFER_RAM_MODEL_SIZE, + TEST_LITERALS_BUFFER_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_LITERALS_BUFFER_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_LITERALS_BUFFER_RAM_MODEL_INITIALIZED + > ( + ram_rd_req_r[6], ram_rd_resp_s[6], ram_wr_req_r[6], ram_wr_resp_s[6] + ); + spawn ram::RamModel< + TEST_LITERALS_BUFFER_RAM_MODEL_DATA_WIDTH, + TEST_LITERALS_BUFFER_RAM_MODEL_SIZE, + TEST_LITERALS_BUFFER_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_LITERALS_BUFFER_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_LITERALS_BUFFER_RAM_MODEL_INITIALIZED + > ( + ram_rd_req_r[7], ram_rd_resp_s[7], ram_wr_req_r[7], ram_wr_resp_s[7] + ); + + // Mock RAM for Literals Header MemReader + let (ram_rd_req_header_s, ram_rd_req_header_r) = chan("ram_rd_req_header"); + let (ram_rd_resp_header_s, ram_rd_resp_header_r) = chan("ram_rd_resp_header"); + let (ram_wr_req_header_s, ram_wr_req_header_r) = chan("ram_wr_req_header"); + let (ram_wr_resp_header_s, ram_wr_resp_header_r) = chan("ram_wr_resp_header"); 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]); + TEST_AXI_RAM_MODEL_DATA_WIDTH, + TEST_AXI_RAM_MODEL_SIZE, + TEST_AXI_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_AXI_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_AXI_RAM_MODEL_INITIALIZED, + TEST_AXI_RAM_MODEL_ASSERT_VALID_READ, + TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + ram_rd_req_header_r, ram_rd_resp_header_s, ram_wr_req_header_r, ram_wr_resp_header_s + ); + + spawn axi_ram::AxiRamReader< + TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, + TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + lit_header_axi_ar_r, lit_header_axi_r_s, + ram_rd_req_header_s, ram_rd_resp_header_r + ); + + // Mock RAM for RawLiterals MemReader + let (ram_rd_req_raw_s, ram_rd_req_raw_r) = chan("ram_rd_req_raw"); + let (ram_rd_resp_raw_s, ram_rd_resp_raw_r) = chan("ram_rd_resp_raw"); + let (ram_wr_req_raw_s, ram_wr_req_raw_r) = chan("ram_wr_req_raw"); + let (ram_wr_resp_raw_s, ram_wr_resp_raw_r) = chan("ram_wr_resp_raw"); + 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]); + TEST_AXI_RAM_MODEL_DATA_WIDTH, + TEST_AXI_RAM_MODEL_SIZE, + TEST_AXI_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_AXI_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_AXI_RAM_MODEL_INITIALIZED, + TEST_AXI_RAM_MODEL_ASSERT_VALID_READ, + TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + ram_rd_req_raw_r, ram_rd_resp_raw_s, ram_wr_req_raw_r, ram_wr_resp_raw_s + ); + + spawn axi_ram::AxiRamReader< + TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, + TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + raw_lit_axi_ar_r, raw_lit_axi_r_s, + ram_rd_req_raw_s, ram_rd_resp_raw_r + ); + + // Mock RAM for HuffmanLiteralsDecoder MemReader + let (ram_rd_req_huffman_s, ram_rd_req_huffman_r) = chan("ram_rd_req_huffman"); + let (ram_rd_resp_huffman_s, ram_rd_resp_huffman_r) = chan("ram_rd_resp_huffman"); + let (ram_wr_req_huffman_s, ram_wr_req_huffman_r) = chan("ram_wr_req_huffman"); + let (ram_wr_resp_huffman_s, ram_wr_resp_huffman_r) = chan("ram_wr_resp_huffman"); + 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]); + TEST_AXI_RAM_MODEL_DATA_WIDTH, + TEST_AXI_RAM_MODEL_SIZE, + TEST_AXI_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_AXI_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_AXI_RAM_MODEL_INITIALIZED, + TEST_AXI_RAM_MODEL_ASSERT_VALID_READ, + TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + ram_rd_req_huffman_r, ram_rd_resp_huffman_s, ram_wr_req_huffman_r, ram_wr_resp_huffman_s + ); + + spawn axi_ram::AxiRamReader< + TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, + TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + huffman_lit_axi_ar_r, huffman_lit_axi_r_s, + ram_rd_req_huffman_s, ram_rd_resp_huffman_r + ); + + // Mock RAM for Huffman Jump Table decoder MemReader + let (ram_rd_req_huffman_jump_table_s, ram_rd_req_huffman_jump_table_r) = chan("ram_rd_req_huffman_jump_table"); + let (ram_rd_resp_huffman_jump_table_s, ram_rd_resp_huffman_jump_table_r) = chan("ram_rd_resp_huffman_jump_table"); + let (ram_wr_req_huffman_jump_table_s, ram_wr_req_huffman_jump_table_r) = chan("ram_wr_req_huffman_jump_table"); + let (ram_wr_resp_huffman_jump_table_s, ram_wr_resp_huffman_jump_table_r) = chan("ram_wr_resp_huffman_jump_table"); + 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]); + TEST_AXI_RAM_MODEL_DATA_WIDTH, + TEST_AXI_RAM_MODEL_SIZE, + TEST_AXI_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_AXI_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_AXI_RAM_MODEL_INITIALIZED, + TEST_AXI_RAM_MODEL_ASSERT_VALID_READ, + TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + ram_rd_req_huffman_jump_table_r, ram_rd_resp_huffman_jump_table_s, ram_wr_req_huffman_jump_table_r, ram_wr_resp_huffman_jump_table_s + ); + + spawn axi_ram::AxiRamReader< + TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, + TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + huffman_jump_table_axi_ar_r, huffman_jump_table_axi_r_s, + ram_rd_req_huffman_jump_table_s, ram_rd_resp_huffman_jump_table_r + ); + + // Mock RAM for HuffmanWeights header decoder MemReader + let (ram_rd_req_huffman_weights_header_s, ram_rd_req_huffman_weights_header_r) = chan("ram_rd_req_huffman_weights_header"); + let (ram_rd_resp_huffman_weights_header_s, ram_rd_resp_huffman_weights_header_r) = chan("ram_rd_resp_huffman_weights_header"); + let (ram_wr_req_huffman_weights_header_s, ram_wr_req_huffman_weights_header_r) = chan("ram_wr_req_huffman_weights_header"); + let (ram_wr_resp_huffman_weights_header_s, ram_wr_resp_huffman_weights_header_r) = chan("ram_wr_resp_huffman_weights_header"); + 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]); + TEST_AXI_RAM_MODEL_DATA_WIDTH, + TEST_AXI_RAM_MODEL_SIZE, + TEST_AXI_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_AXI_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_AXI_RAM_MODEL_INITIALIZED, + TEST_AXI_RAM_MODEL_ASSERT_VALID_READ, + TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + ram_rd_req_huffman_weights_header_r, ram_rd_resp_huffman_weights_header_s, ram_wr_req_huffman_weights_header_r, ram_wr_resp_huffman_weights_header_s + ); + + spawn axi_ram::AxiRamReader< + TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, + TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + huffman_weights_header_axi_ar_r, huffman_weights_header_axi_r_s, + ram_rd_req_huffman_weights_header_s, ram_rd_resp_huffman_weights_header_r + ); + + // Mock RAM for HuffmanWeights raw decoder MemReader + let (ram_rd_req_huffman_weights_raw_s, ram_rd_req_huffman_weights_raw_r) = chan("ram_rd_req_huffman_weights_raw"); + let (ram_rd_resp_huffman_weights_raw_s, ram_rd_resp_huffman_weights_raw_r) = chan("ram_rd_resp_huffman_weights_raw"); + let (ram_wr_req_huffman_weights_raw_s, ram_wr_req_huffman_weights_raw_r) = chan("ram_wr_req_huffman_weights_raw"); + let (ram_wr_resp_huffman_weights_raw_s, ram_wr_resp_huffman_weights_raw_r) = chan("ram_wr_resp_huffman_weights_raw"); + + spawn ram::RamModel< + TEST_AXI_RAM_MODEL_DATA_WIDTH, + TEST_AXI_RAM_MODEL_SIZE, + TEST_AXI_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_AXI_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_AXI_RAM_MODEL_INITIALIZED, + TEST_AXI_RAM_MODEL_ASSERT_VALID_READ, + TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + ram_rd_req_huffman_weights_raw_r, ram_rd_resp_huffman_weights_raw_s, ram_wr_req_huffman_weights_raw_r, ram_wr_resp_huffman_weights_raw_s + ); + + spawn axi_ram::AxiRamReader< + TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, + TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + huffman_weights_raw_axi_ar_r, huffman_weights_raw_axi_r_s, + ram_rd_req_huffman_weights_raw_s, ram_rd_resp_huffman_weights_raw_r + ); + + // Mock RAM for HuffmanWeights fse decoder MemReader + let (ram_rd_req_huffman_weights_fse_s, ram_rd_req_huffman_weights_fse_r) = chan("ram_rd_req_huffman_weights_fse"); + let (ram_rd_resp_huffman_weights_fse_s, ram_rd_resp_huffman_weights_fse_r) = chan("ram_rd_resp_huffman_weights_fse"); + let (ram_wr_req_huffman_weights_fse_s, ram_wr_req_huffman_weights_fse_r) = chan("ram_wr_req_huffman_weights_fse"); + let (ram_wr_resp_huffman_weights_fse_s, ram_wr_resp_huffman_weights_fse_r) = chan("ram_wr_resp_huffman_weights_fse"); + 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]); + TEST_AXI_RAM_MODEL_DATA_WIDTH, + TEST_AXI_RAM_MODEL_SIZE, + TEST_AXI_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_AXI_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_AXI_RAM_MODEL_INITIALIZED, + TEST_AXI_RAM_MODEL_ASSERT_VALID_READ, + TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + ram_rd_req_huffman_weights_fse_r, ram_rd_resp_huffman_weights_fse_s, ram_wr_req_huffman_weights_fse_r, ram_wr_resp_huffman_weights_fse_s + ); + + spawn axi_ram::AxiRamReader< + TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, + TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + huffman_weights_fse_axi_ar_r, huffman_weights_fse_axi_r_s, + ram_rd_req_huffman_weights_fse_s, ram_rd_resp_huffman_weights_fse_r + ); + + // Huffman weigths memory 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]); + TEST_HUFFMAN_WEIGHTS_RAM_MODEL_DATA_WIDTH, + TEST_HUFFMAN_WEIGHTS_RAM_MODEL_SIZE, + TEST_HUFFMAN_WEIGHTS_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_HUFFMAN_WEIGHTS_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_HUFFMAN_WEIGHTS_RAM_MODEL_INITIALIZED, + TEST_HUFFMAN_WEIGHTS_RAM_MODEL_ASSERT_VALID_READ, + TEST_HUFFMAN_WEIGHTS_RAM_MODEL_ADDR_WIDTH, + > ( + huffman_lit_weights_mem_rd_req_r, huffman_lit_weights_mem_rd_resp_s, + huffman_lit_weights_mem_wr_req_r, huffman_lit_weights_mem_wr_resp_s + ); + + // Huffman prescan memory 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]); + TEST_HUFFMAN_PRESCAN_RAM_MODEL_DATA_WIDTH, + TEST_HUFFMAN_PRESCAN_RAM_MODEL_SIZE, + TEST_HUFFMAN_PRESCAN_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_HUFFMAN_PRESCAN_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_HUFFMAN_PRESCAN_RAM_MODEL_INITIALIZED, + TEST_HUFFMAN_PRESCAN_RAM_MODEL_ASSERT_VALID_READ, + TEST_HUFFMAN_PRESCAN_RAM_MODEL_ADDR_WIDTH + > ( + huffman_lit_prescan_mem_rd_req_r, huffman_lit_prescan_mem_rd_resp_s, + huffman_lit_prescan_mem_wr_req_r, huffman_lit_prescan_mem_wr_resp_s + ); ( terminator, - literals_ctrl_s, literals_data_s, - literals_buf_ctrl_s, literals_r, + ctrl_req_s, ctrl_resp_r, ctrl_header_r, + buf_ctrl_s, buf_out_r, print_start_s, print_finish_r, + ram_wr_req_header_s, ram_wr_resp_header_r, + ram_wr_req_raw_s, ram_wr_resp_raw_r, + ram_wr_req_huffman_s, ram_wr_resp_huffman_r, + ram_wr_req_huffman_jump_table_s, ram_wr_resp_huffman_jump_table_r, + ram_wr_req_huffman_weights_header_s, ram_wr_resp_huffman_weights_header_r, + ram_wr_req_huffman_weights_raw_s, ram_wr_resp_huffman_weights_raw_r, + ram_wr_req_huffman_weights_fse_s, ram_wr_resp_huffman_weights_fse_r ) } init { } next (state: ()) { + const TEST_MEMORY: AxiRamWrReq[21] = [ + // Literals #0 (RAW; 8 bytes) + // Header: 0x40 + AxiRamWrReq { addr: AxiRamAddr:0x0, data: AxiRamData:0x5734_65A6_DB5D_B040, mask: AxiRamMask:0xFF }, // AXI addr: 0x0 + AxiRamWrReq { addr: AxiRamAddr:0x1, data: AxiRamData:0x16, mask: AxiRamMask:0xFF }, // AXI addr: 0x8 + + // Literals #1 (RLE; 4 bytes) + // Header: 0x21 + AxiRamWrReq { addr: AxiRamAddr:0x2, data: AxiRamData:0x2321, mask: AxiRamMask:0xFF }, // AXI addr: 0x10 + + // Literals #2 (RLE; 2 bytes) + // Header: 0x11 + AxiRamWrReq { addr: AxiRamAddr:0x4, data: AxiRamData:0x3511, mask: AxiRamMask:0xFF }, // AXI addr: 0x20 + + // Literals #3 (RAW; 15 bytes) + // Header: 0x78 + AxiRamWrReq { addr: AxiRamAddr:0x6, data: AxiRamData:0xFB41_C67B_6053_7078, mask: AxiRamMask:0xFF }, // AXI addr: 0x30 + AxiRamWrReq { addr: AxiRamAddr:0x7, data: AxiRamData:0x9B0F_9CE1_BAA9_6D4C, mask: AxiRamMask:0xFF }, // AXI addr: 0x38 + + // Literals #4 (Huffman; 6 bytes) + // Header: 0x01_80_42 (0b0000000110_0000000100_00_10) + AxiRamWrReq { addr: AxiRamAddr:0x10, data: (u8:0b0000_0001 ++ u24:0x010234 ++ u8:0x85 ++ u24:0x01_80_42) as AxiRamData, mask: AxiRamMask:0xFF }, // AXI addr: 0x80 + AxiRamWrReq { addr: AxiRamAddr:0x11, data: u8:0b00001_1_01 as AxiRamData, mask: AxiRamMask:0xFF }, // AXI addr: 0x88 + + // Literals #5 (RLE; 12 bytes) + // Header: 0x61 + AxiRamWrReq { addr: AxiRamAddr:0x20, data: AxiRamData:0x5A61, mask: AxiRamMask:0xFF }, // AXI addr: 0x100 + + // Literals #6 (Huffman; 4 bytes) + // Header: 0x00_80_43 (0b0000000010_0000000100_00_11) + AxiRamWrReq { addr: AxiRamAddr:0x30, data: (u16:0b00001_0001_0000_01_1 ++ u24:0x00_80_43) as AxiRamData, mask: AxiRamMask:0xFF }, // AXI addr: 0x180 + + // Literals #7 (RLE; 0 bytes) + // Header: 0x01 + AxiRamWrReq { addr: AxiRamAddr:0x40, data: AxiRamData:0xFF01, mask: AxiRamMask:0xFF }, // AXI addr: 0x200 + + // Literals #8 (Huffman; 18 bytes) + // Header: 0x04_81_06 (0b0000010010_0000010010_01_10) + AxiRamWrReq { addr: AxiRamAddr:0x50, data: (u8:0x02 ++ u24:0x010234 ++ u8:0x85 ++ u24:0x04_81_06) as AxiRamData, mask: AxiRamMask:0xFF }, // AXI addr: 0x280 + AxiRamWrReq { addr: AxiRamAddr:0x51, data: (u8:0b0000_0001 ++ u16:0b00001_1_01_0000_0001 ++ u40:0x0002_0002_00) as AxiRamData, mask: AxiRamMask:0xFF }, // AXI addr: 0x288 + AxiRamWrReq { addr: AxiRamAddr:0x52, data: (u16:0b00001_1_01_0000_0001 ++ u16:0b00001_1_01_0000_0001 ++ u8:0b00001_1_01) as AxiRamData, mask: AxiRamMask:0xFF }, // AXI addr: 290 + + // Literals #9 (RAW; 31 bytes) + // Header: 0xF8 + AxiRamWrReq { addr: AxiRamAddr:0x60, data: AxiRamData:0x943E_9618_34C2_47F8, mask: AxiRamMask:0xFF }, // AXI addr: 0x300 + AxiRamWrReq { addr: AxiRamAddr:0x61, data: AxiRamData:0x02D0_E8D7_289A_BE60, mask: AxiRamMask:0xFF }, // AXI addr: 0x308 + AxiRamWrReq { addr: AxiRamAddr:0x62, data: AxiRamData:0x64C3_8BE1_FA8D_12BC, mask: AxiRamMask:0xFF }, // AXI addr: 0x310 + AxiRamWrReq { addr: AxiRamAddr:0x63, data: AxiRamData:0x1963_F1CE_21C2_94F8, mask: AxiRamMask:0xFF }, // AXI addr: 0x318 + + // Literals #10 (Huffman; 15 bytes) + // Header: 0x03_80_E7 (0b0000001110_0000001110_01_11) + AxiRamWrReq { addr: AxiRamAddr:0x70, data: (u40:0x02_0002_0002 ++ u24:0x03_80_E7) as AxiRamData, mask: AxiRamMask:0xFF }, // AXI addr: 0x350 + AxiRamWrReq { addr: AxiRamAddr:0x71, data: (u8:0b0000_0001 ++ u16:0b00001_1_01_0000_0001 ++ u16:0b00001_1_01_0000_0001 ++ u16:0b00001_1_01_0000_0001 ++ u8:0x00) as AxiRamData, mask: AxiRamMask:0xFF }, // AXI addr: 0x358 + AxiRamWrReq { addr: AxiRamAddr:0x72, data: u8:0b00001_1_01 as AxiRamData, mask: AxiRamMask:0xFF }, // AXI addr: 0x358 + ]; + + const TEST_CTRL: CtrlReq[11] = [ + CtrlReq {addr: AxiAddr:0x0, literals_last: false}, + CtrlReq {addr: AxiAddr:0x10, literals_last: false}, + CtrlReq {addr: AxiAddr:0x20, literals_last: false}, + CtrlReq {addr: AxiAddr:0x30, literals_last: true}, + CtrlReq {addr: AxiAddr:0x80, literals_last: true}, + CtrlReq {addr: AxiAddr:0x100, literals_last: false}, + CtrlReq {addr: AxiAddr:0x180, literals_last: true}, + CtrlReq {addr: AxiAddr:0x200, literals_last: false}, + CtrlReq {addr: AxiAddr:0x280, literals_last: true}, + CtrlReq {addr: AxiAddr:0x300, literals_last: true}, + CtrlReq {addr: AxiAddr:0x380, literals_last: true}, + ]; + + const TEST_EXPECTED_RESP: CtrlResp[11] = [ + CtrlResp {status: CtrlStatus::OKAY}, + CtrlResp {status: CtrlStatus::OKAY}, + CtrlResp {status: CtrlStatus::OKAY}, + CtrlResp {status: CtrlStatus::OKAY}, + CtrlResp {status: CtrlStatus::OKAY}, + CtrlResp {status: CtrlStatus::OKAY}, + CtrlResp {status: CtrlStatus::OKAY}, + CtrlResp {status: CtrlStatus::OKAY}, + CtrlResp {status: CtrlStatus::OKAY}, + CtrlResp {status: CtrlStatus::OKAY}, + CtrlResp {status: CtrlStatus::OKAY}, + ]; + + const TEST_BUF_CTRL: LiteralsBufferCtrl[10] = [ + LiteralsBufferCtrl {length: u32:8, last: false}, + LiteralsBufferCtrl {length: u32:4 , last: false}, + LiteralsBufferCtrl {length: u32:1 , last: false}, + LiteralsBufferCtrl {length: u32:16, last: true}, + LiteralsBufferCtrl {length: u32:4, last: true}, + LiteralsBufferCtrl {length: u32:12, last: false}, + LiteralsBufferCtrl {length: u32:4, last: true}, + LiteralsBufferCtrl {length: u32:16, last: true}, + LiteralsBufferCtrl {length: u32:31, last: true}, + LiteralsBufferCtrl {length: u32:16, last: true}, + ]; + + const TEST_EXPECTED_LITERALS: SequenceExecutorPacket[17] = [ + // Literals #0 (RAW) + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0x1657_3465_A6DB_5DB0, + last: true + }, + // Literals #1 (RLE) + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:4, + content: CopyOrMatchContent:0x2323_2323, + last: true + }, + // Literals #2 (RLE) + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:1, + content: CopyOrMatchContent:0x35, + last: false + }, + // Literals #3 (RAW) + 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 + }, + // Literals #4 (Huffman) + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:4, + content: CopyOrMatchContent:0x0504_0100, + last: true + }, + // Literals #5 (RLE) + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0x5A5A_5A5A_5A5A_5A5A, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:4, + content: CopyOrMatchContent:0x5A5A_5A5A, + last: true + }, + // Literals #6 (Huffman) + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:4, + content: CopyOrMatchContent:0x0001_0405, + last: true + }, + // Literals #7 (RLE) + // EMPTY + // Literals #8 (Huffman) + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0x0504_0100_0504_0100, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0x0504_0100_0504_0100, + last: true + }, + // Literals #9 (RAW) + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0x6094_3E96_1834_C247, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0xBC02_D0E8_D728_9ABE, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0xF864_C38B_E1FA_8D12, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:7, + content: CopyOrMatchContent:0x19_63F1_CE21_C294, + last: true + }, + // Literals #10 (Huffman) + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0x0504_0100_0504_0100, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0x0504_0100_0504_0100, + last: true + }, + ]; + 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); - trace_fmt!("Sent #{} literals data, {:#x}", i + u32:1, test_data); + + trace_fmt!("Filling system memory mock"); + let tok = for ((i, mem_req), tok):((u32, AxiRamWrReq), token) in enumerate(TEST_MEMORY) { + trace_fmt!("Sent memory write request #{}: {:#x}", i + u32:1, mem_req); + let tok = send(tok, ram_wr_req_header_s, mem_req); + let (tok, _) = recv(tok, ram_wr_resp_header_r); + let tok = send(tok, ram_wr_req_raw_s, mem_req); + let (tok, _) = recv(tok, ram_wr_resp_raw_r); + let tok = send(tok, ram_wr_req_huffman_s, mem_req); + let (tok, _) = recv(tok, ram_wr_resp_huffman_r); + let tok = send(tok, ram_wr_req_huffman_jump_table_s, mem_req); + let (tok, _) = recv(tok, ram_wr_resp_huffman_jump_table_r); + let tok = send(tok, ram_wr_req_huffman_weights_header_s, mem_req); + let (tok, _) = recv(tok, ram_wr_resp_huffman_weights_header_r); + let tok = send(tok, ram_wr_req_huffman_weights_raw_s, mem_req); + let (tok, _) = recv(tok, ram_wr_resp_huffman_weights_raw_r); + let tok = send(tok, ram_wr_req_huffman_weights_fse_s, mem_req); + let (tok, _) = recv(tok, ram_wr_resp_huffman_weights_fse_r); 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); + assert_eq(array_size(TEST_CTRL), array_size(TEST_EXPECTED_RESP)); + + trace_fmt!("Sending literals decoding requests"); + let tok = for ((i, test_ctrl), tok): ((u32, CtrlReq), token) in enumerate(TEST_CTRL) { + let tok = send(tok, ctrl_req_s, test_ctrl); + trace_fmt!("Sent #{} literals decoding request: {:#x}", i + u32:1, test_ctrl); + let (tok, resp) = recv(tok, ctrl_resp_r); + trace_fmt!("Received #{} literals decoding response {:#x}", i + u32:1, resp); + assert_eq(TEST_EXPECTED_RESP[i], resp); tok }(tok); - // send buffer ctrl + trace_fmt!("Sending literals buffer requests"); 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); + let tok = send(tok, buf_ctrl_s, test_buf_ctrl); + trace_fmt!("Sent #{} literals buffer request {:#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); + let (tok, literals) = recv(tok, buf_out_r); trace_fmt!("Received #{} literals packet {:#x}", i + u32:1, literals); + trace_fmt!("Expected {:#x}", test_exp_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); + //// print RAM content + //let tok = send(tok, print_start_s, ()); + //let (tok, _) = recv(tok, print_finish_r); send(tok, terminator, true); } @@ -572,7 +1904,6 @@ proc LiteralsDecoder_test { // 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"); @@ -582,7 +1913,7 @@ proc LiteralsDecoder_test { // 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_ctrl_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], diff --git a/xls/modules/zstd/literals_dispatcher.x b/xls/modules/zstd/literals_dispatcher.x deleted file mode 100644 index 39fe949321..0000000000 --- a/xls/modules/zstd/literals_dispatcher.x +++ /dev/null @@ -1,243 +0,0 @@ -// 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 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: DecompressedSize, - literals_id: LitID, -} - -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(state: LiteralsDispatcherState ) { - - 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 { - (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 is_empty = left_to_read == DecompressedSize:0 && !literals_data.last; - - let left_to_read = if (literals_type == LiteralType::RLE) { - DecompressedSize:0 - } else { - 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, - 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); - - 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!()); - - // 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, - } - } -} - -#[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(state: ()) { - let tok = join(); - let test_ctrl: LiteralsPathCtrl[6] = [ - 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 - 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: LiteralsDataWithSync[7] = [ - // 0. - LiteralsDataWithSync {data: LitData:0x1657_3465_A6DB_5DB0, length: LitLength:8, id: LitID:0, last: false, literals_last: true}, - // 3. - 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. - 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, id: LitID:1, last: false}, - // 2. - RleLiteralsData {data: RleLitData:0x35, repeat: RleLitRepeat:13, id: LitID:2, last: true}, - // 4. - 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); - 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, 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); - (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); - } -} diff --git a/xls/modules/zstd/memory/axi_ram.x b/xls/modules/zstd/memory/axi_ram.x index 9683e61fc6..688fe2c171 100644 --- a/xls/modules/zstd/memory/axi_ram.x +++ b/xls/modules/zstd/memory/axi_ram.x @@ -118,7 +118,7 @@ proc AxiRamReaderRequester< // send RAM read reqest let addr_valid = state.addr < ((RAM_SIZE * RAM_DATA_W_DIV8) as uN[AXI_ADDR_W]); - let addr = (state.addr / RAM_DATA_W_DIV8) as uN[RAM_ADDR_W]; + let addr = (state.addr / RAM_DATA_W_DIV8 as uN[AXI_ADDR_W]) as uN[RAM_ADDR_W]; let do_read_from_ram = ( (state.status == Status::READ_BURST) && @@ -152,7 +152,7 @@ proc AxiRamReaderRequester< } else { ( arsize_bits, - ((state.addr % RAM_DATA_W_DIV8) << u32:3) as uN[RAM_DATA_W_PLUS1_LOG2], + ((state.addr % RAM_DATA_W_DIV8 as uN[AXI_ADDR_W]) << u32:3) as uN[RAM_DATA_W_PLUS1_LOG2], ) }; @@ -190,7 +190,7 @@ proc AxiRamReaderRequester< let addr = match state.ar_bundle.burst { AxiAxBurst::FIXED => state.addr, AxiAxBurst::INCR => state.addr + incr, - AxiAxBurst::WRAP => if ((state.addr + incr) >= (RAM_SIZE * RAM_DATA_W_DIV8)) { + AxiAxBurst::WRAP => if ((state.addr + incr) as u32 >= (RAM_SIZE * RAM_DATA_W_DIV8)) { uN[AXI_ADDR_W]:0 } else { state.addr + incr @@ -424,7 +424,7 @@ proc AxiRamReaderInstWithEmptyWrites { ) } - next(state: ()) { + next(state: ()) { send_if(join(), wr_req_s, false, zero!()); recv_if(join(), wr_resp_r, false, zero!()); } diff --git a/xls/modules/zstd/raw_literals_dec.x b/xls/modules/zstd/raw_literals_dec.x index 1dbb971785..d53d5349f1 100644 --- a/xls/modules/zstd/raw_literals_dec.x +++ b/xls/modules/zstd/raw_literals_dec.x @@ -16,66 +16,283 @@ // Packets of 0 length are not passed further and a warning is log instead. import xls.modules.zstd.common; +import xls.modules.zstd.memory.mem_reader as mem_reader; type LiteralsDataWithSync = common::LiteralsDataWithSync; type LitData = common::LitData; type LitLength = common::LitLength; +type LitID = common::LitID; -pub proc RawLiteralsDecoder { - dispatcher_r: chan in; - buffer_s: chan out; +pub struct RawLiteralsDecoderReq { + id: u32, + addr: uN[ADDR_W], + length: uN[ADDR_W], + literals_last: bool, +} + +pub enum RawLiteralsDecoderStatus: u1 { + OKAY = 0, + ERROR = 1, +} + +pub struct RawLiteralsDecoderResp { + status: RawLiteralsDecoderStatus, +} + +struct RawLiteralsDecoderState { + id: u32, + literals_last: bool, +} - config(dispatcher_r: chan in, buffer_s: chan out) { - (dispatcher_r, buffer_s) +pub proc RawLiteralsDecoder { + type Req = RawLiteralsDecoderReq; + type Resp = RawLiteralsDecoderResp; + type Output = LiteralsDataWithSync; + type State = RawLiteralsDecoderState; + type Status = RawLiteralsDecoderStatus; + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + type MemReaderStatus = mem_reader::MemReaderStatus; + + // decoder input + req_r: chan in; + resp_s: chan out; + + // decoder output + output_s: chan out; + + // memory interface + mem_req_s: chan out; + mem_resp_r: chan in; + + init { zero!() } + + config( + req_r: chan in, + resp_s: chan out, + output_s: chan out, + + mem_req_s: chan out, + mem_resp_r: chan in, + ) { + ( + req_r, resp_s, output_s, + mem_req_s, mem_resp_r, + ) } - init { } + next(state: State) { + let tok0 = join(); - 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"); - false + // receive request + let (tok1_0, req, req_valid) = recv_non_blocking(tok0, req_r, zero!()); + + // update ID and last in state + let state = if req_valid { + State { id: req.id, literals_last: req.literals_last} + } else { state }; + + // send memory read request + let req = MemReaderReq { addr: req.addr, length: req.length }; + let tok2_0 = send_if(tok1_0, mem_req_s, req_valid, req); + + // receive memory read response + let (tok1_1, mem_resp, mem_resp_valid) = recv_non_blocking(tok0, mem_resp_r, zero!()); + let mem_resp_error = (mem_resp.status != MemReaderStatus::OKAY); + + // prepare output data, decoded RAW block is always a literal + let output_data = Output { + last: mem_resp.last, + literals_last: state.literals_last, + id: state.id as LitID, + data: checked_cast(mem_resp.data), + length: checked_cast(mem_resp.length), + }; + + // send output data + let mem_resp_correct = mem_resp_valid && !mem_resp_error; + let tok2_1 = send_if(tok1_1, output_s, mem_resp_correct, output_data); + + // send response after block end + let resp = if mem_resp_correct { + Resp { status: Status::OKAY } } else { - true + Resp { status: Status::ERROR } }; - let tok = send_if(tok, buffer_s, do_send, resp); + + let do_send_resp = mem_resp_valid && mem_resp.last; + let tok2_2 = send_if(tok1_1, resp_s, do_send_resp, resp); + + state + } +} + +const INST_DATA_W = u32:64; +const INST_ADDR_W = u32:16; + +pub proc RawLiteralsDecoderInst { + type Req = RawLiteralsDecoderReq; + type Resp = RawLiteralsDecoderResp; + type Output = LiteralsDataWithSync; + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + + init { () } + + config( + req_r: chan in, + resp_s: chan out, + output_s: chan out, + + mem_req_s: chan out, + mem_resp_r: chan in, + ) { + spawn RawLiteralsDecoder( + req_r, resp_s, output_s, mem_req_s, mem_resp_r + ); } + + next(state: ()) {} } +const TEST_DATA_W = u32:64; +const TEST_ADDR_W = u32:16; + #[test_proc] proc RawLiteralsDecoderTest { + type Req = RawLiteralsDecoderReq; + type Resp = RawLiteralsDecoderResp; + type Output = LiteralsDataWithSync; + type State = RawLiteralsDecoderState; + type Status = RawLiteralsDecoderStatus; + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + type MemReaderStatus = mem_reader::MemReaderStatus; + + type Data = uN[TEST_DATA_W]; + type Addr = uN[TEST_ADDR_W]; + type Length = uN[TEST_ADDR_W]; + terminator: chan out; - dispatcher_s: chan out; - buffer_r: chan in; + // decoder input + req_s: chan out; + resp_r: chan in; + + // decoder output + output_r: chan in; + + // memory interface + mem_req_r: chan in; + mem_resp_s: chan out; config(terminator: chan out) { - let (dispatcher_s, dispatcher_r) = chan("dispatcher"); - let (buffer_s, buffer_r) = chan("buffer"); + let (req_s, req_r) = chan("req"); + let (resp_s, resp_r) = chan("resp"); + let (output_s, output_r) = chan("output"); - spawn RawLiteralsDecoder(dispatcher_r, buffer_s); + let (mem_req_s, mem_req_r) = chan("mem_req"); + let (mem_resp_s, mem_resp_r) = chan("mem_resp"); - (terminator, dispatcher_s, buffer_r) + spawn RawLiteralsDecoder( + req_r, resp_s, output_s, mem_req_s, mem_resp_r + ); + + (terminator, req_s, resp_r, output_r, mem_req_r, mem_resp_s) } init { } 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 = LiteralsDataWithSync { data: LitData:0, length: LitLength:0, id: u32:0, last: true, literals_last: true }; - let tok = send(tok, dispatcher_s, empty_data); + // Test 0 + let req = Req { id: u32:0, literals_last: false, addr: Addr:0, length: Length:8 }; + let tok = send(tok, req_s, req); + + let (tok, mem_req) = recv(tok, mem_req_r); + assert_eq(mem_req, MemReaderReq { addr: Addr:0, length: Length:8 }); - // 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 mem_resp = MemReaderResp { + status: mem_reader::MemReaderStatus::OKAY, + data: Data:0x1122_3344, + length: Length:8, + last: true, + }; + let tok = send(tok, mem_resp_s, mem_resp); + let (tok, output) = recv(tok, output_r); + assert_eq(output, Output { + last: true, + literals_last: false, + id: u32:0, + data: Data:0x1122_3344, + length: LitLength:8, + }); + + // Test 1 + let req = Req { id: u32:1, literals_last: true, addr: Addr:0x1001, length: Length:15 }; + let tok = send(tok, req_s, req); + + let (tok, mem_req) = recv(tok, mem_req_r); + assert_eq(mem_req, MemReaderReq { addr: Addr:0x1001, length: Length:15 }); + + let mem_resp = MemReaderResp { + status: mem_reader::MemReaderStatus::OKAY, + data: Data:0x1122_3344_5566_7788, + length: Length:8, + last: false + }; + let tok = send(tok, mem_resp_s, mem_resp); + + let mem_resp = MemReaderResp { + status: mem_reader::MemReaderStatus::OKAY, + data: Data:0xAA_BBCC_DDEE_FF99, + length: Length:7, + last: true, + }; + let tok = send(tok, mem_resp_s, mem_resp); + + let (tok, output) = recv(tok, output_r); + assert_eq(output, Output { + last: false, + literals_last: true, + id: u32:1, + data: Data:0x1122_3344_5566_7788, + length: LitLength:8, + }); + + let (tok, output) = recv(tok, output_r); + assert_eq(output, Output { + last: true, + literals_last: true, + id: u32:1, + data: Data:0xAA_BBCC_DDEE_FF99, + length: LitLength:7, + }); + + // Test 2 + let req = Req {id: u32:2, literals_last: false, addr: Addr:0x2000, length: Length:0 }; + let tok = send(tok, req_s, req); + + let (tok, mem_req) = recv(tok, mem_req_r); + assert_eq(mem_req, MemReaderReq { addr: Addr:0x2000, length: Length:0 }); + + let mem_resp = MemReaderResp { + status: mem_reader::MemReaderStatus::OKAY, + data: Data:0x0, + length: Length:0, + last: true, + }; + let tok = send(tok, mem_resp_s, mem_resp); + let (tok, output) = recv(tok, output_r); + assert_eq(output, Output { + last: true, + literals_last: false, + id: u32:2, + data: Data:0x0, + length: LitLength:0, + }); - let tok = send(tok, terminator, true); + send(tok, terminator, true); } } diff --git a/xls/modules/zstd/rle_literals_dec.x b/xls/modules/zstd/rle_literals_dec.x index dda39f8208..7cff335387 100644 --- a/xls/modules/zstd/rle_literals_dec.x +++ b/xls/modules/zstd/rle_literals_dec.x @@ -19,390 +19,260 @@ 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 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, +pub enum RleLiteralsDecoderStatus: u1 { + OKAY = 0, } -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 { } +pub struct RleLiteralsDecoderReq { + id: u32, + symbol: u8, + length: RleLitRepeat, + literals_last: bool, +} - 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, id: input.id, last: input.last }; - let sync_tok = send_if(data_tok, sync_s, not_zero_repeat || input.last, sync_data); - } +pub struct RleLiteralsDecoderResp { + status: RleLiteralsDecoderStatus } -#[test_proc] -proc RleDataPacker_test { - terminator: chan out; - in_s: chan out; - out_r: chan in; - sync_r: chan in; +struct RleLiteralsDecoderState { + req: RleLiteralsDecoderReq, + req_valid: bool, +} - 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"); +pub proc RleLiteralsDecoder { + type Req = RleLiteralsDecoderReq; + type Resp = RleLiteralsDecoderResp; + type Output = LiteralsDataWithSync; - spawn RleDataPacker(in_r, out_s, sync_s); + type State = RleLiteralsDecoderState; - (terminator, in_s, out_r, sync_r) - } + req_r: chan in; + resp_s: chan out; + output_s: chan out; - init { } + config( req_r: chan in, + resp_s: chan out, + output_s: chan out, + ) { (req_r, resp_s, output_s) } - next(state: ()) { - let tok = join(); - let test_data: RleLiteralsData[5] = [ - 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}, - ]; + init { zero!() } - 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, - }; + next(state: State) { + const MAX_OUTPUT_SYMBOLS = (DATA_W / u32:8); + const MAX_LEN = MAX_OUTPUT_SYMBOLS as RleLitRepeat; - let expected_sync_out = LiteralsSyncData { - count: test_data.repeat, - id: test_data.id, - last: test_data.last, - }; + let tok0 = join(); - let tok = send(tok, in_s, test_data); - trace_fmt!("Send #{} rle literals data, {:#x}", counter + u32:1, test_data); + let (tok1, req) = recv_if(tok0, req_r, !state.req_valid, state.req); - 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 last = req.length <= MAX_LEN; + let length = if last { req.length } else { MAX_LEN }; + let data = unroll_for! (i, data): (u32, uN[DATA_W]) in range(u32:0, MAX_OUTPUT_SYMBOLS) { + bit_slice_update(data, i * u32:8, req.symbol) + }(uN[DATA_W]:0); - 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); + let output = Output { + last: last, + literals_last: req.literals_last, + id: req.id, + data: checked_cast(data), + length: checked_cast(length), + }; - (tok) - }(tok); + send_if(tok1, resp_s, last, zero!()); + send(tok1, output_s, output); - send(tok, terminator, true); + if last { + zero!() + } else { + let length = req.length - MAX_LEN; + State { + req: Req { length, ..req }, + req_valid: true, + } + } } } -struct BatchPackerState { - batch: LitData, - data_in_batch: LitLength, - count_left: RleLitRepeat, - sync_id: LitID, - 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); +const INST_DATA_W = u32:64; -proc BatchPacker { - rle_data_r: chan in; - sync_r: chan in; - literals_data_s: chan out; +pub proc RleLiteralsDecoderInst { + type Req = RleLiteralsDecoderReq; + type Resp = RleLiteralsDecoderResp; + type Output = LiteralsDataWithSync; config( - rle_data_r: chan in, - sync_r: chan in, - literals_data_s: chan out, + req_r: chan in, + resp_s: chan out, + output_s: chan out, ) { - (rle_data_r, sync_r, literals_data_s) - } - - init { zero!() } - - 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_id, sync_last) = if (no_count_left) { - (sync_data.count, sync_data.id, sync_data.last) - } else { - (state.count_left, state.sync_id, 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 = 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!() - } - } else { - BatchPackerState { - batch: batch, - data_in_batch: data_in_batch, - count_left: count_left - RleLitRepeat:1, - sync_id: sync_id, - sync_last: sync_last, - } - }; - - (literals_data, do_send_batch, state) - } else if (sync_data.last) { - // handle empty literal with last set - ( - 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 - }, - ) - } else { - // handle empty literal with last not set - ( - zero!(), - false, - BatchPackerState { - count_left: count_left, - sync_id: sync_id, - 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) + spawn RleLiteralsDecoder( + req_r, resp_s, output_s + ); } - init { } - - next(state: ()) { - let tok = join(); - let test_sync_data: LiteralsSyncData[4] = [ - 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 - 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: 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) { - 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, 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); - (tok) - }(tok); + init { () } - send(tok, terminator, true); - } + next(state: ()) {} } -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(state: ()) { } -} +const TEST_DATA_W = u32:64; #[test_proc] proc RleLiteralsDecoder_test { + type Req = RleLiteralsDecoderReq; + type Resp = RleLiteralsDecoderResp; + type Output = LiteralsDataWithSync; + type Status = RleLiteralsDecoderStatus; + terminator: chan out; - in_s: chan out; - out_r: chan in; + req_s: chan out; + resp_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 (req_s, req_r) = chan("req"); + let (resp_s, resp_r) = chan("resp"); + let (out_s, out_r) = chan("output"); - spawn RleLiteralsDecoder(in_r, out_s); + spawn RleLiteralsDecoder( + req_r, resp_s, out_s + ); - (terminator, in_s, out_r) + (terminator, req_s, resp_r, out_r) } - init { } + init { } next(state: ()) { let tok = join(); - let test_rle_data: RleLiteralsData[7] = [ - 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_rle_req: Req[6] = [ + Req {symbol: RleLitData:0x11, length: RleLitRepeat:11, id: LitID:0, literals_last: false}, + Req {symbol: RleLitData:0x22, length: RleLitRepeat:3, id: LitID:1, literals_last: false}, + Req {symbol: RleLitData:0x33, length: RleLitRepeat:16, id: LitID:2, literals_last: false}, + Req {symbol: RleLitData:0x55, length: RleLitRepeat:2, id: LitID:3, literals_last: false}, + Req {symbol: RleLitData:0x66, length: RleLitRepeat:20, id: LitID:4, literals_last: false}, + Req {symbol: RleLitData:0x00, length: RleLitRepeat:0, id: LitID:5, literals_last: true}, + ]; + let test_rle_resp: Resp[6] = [ + Resp {status: Status::OKAY}, + Resp {status: Status::OKAY}, + Resp {status: Status::OKAY}, + Resp {status: Status::OKAY}, + Resp {status: Status::OKAY}, + Resp {status: Status::OKAY}, ]; let test_out_data: LiteralsDataWithSync[10] = [ // 1st literal - 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}, + LiteralsDataWithSync {data: LitData:0x1111_1111_1111_1111, length: LitLength:8, id: LitID:0, last: false, literals_last: false}, + LiteralsDataWithSync {data: LitData:0x1111_1111_1111_1111, length: LitLength:3, id: LitID:0, last: true, literals_last: false}, // 2nd literal - LiteralsDataWithSync {data: LitData:0x0000_0000_0022_2222, length: LitLength:3, last: false, id: LitID:1, literals_last: true}, + LiteralsDataWithSync {data: LitData:0x2222_2222_2222_2222, length: LitLength:3, id: LitID:1, last: true, literals_last: false}, // 3rd literal - 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) + LiteralsDataWithSync {data: LitData:0x3333_3333_3333_3333, length: LitLength:8, id: LitID:2, last: false, literals_last: false}, + LiteralsDataWithSync {data: LitData:0x3333_3333_3333_3333, length: LitLength:8, id: LitID:2, last: true, literals_last: false}, // 5th literal - LiteralsDataWithSync {data: LitData:0x0000_0000_0000_5555, length: LitLength:2, last: false, id: LitID:3, literals_last: true}, + LiteralsDataWithSync {data: LitData:0x5555_5555_5555_5555, length: LitLength:2, id: LitID:3, last: true, literals_last: false}, // 6th literal - 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}, + LiteralsDataWithSync {data: LitData:0x6666_6666_6666_6666, length: LitLength:8, id: LitID:4, last: false, literals_last: false}, + LiteralsDataWithSync {data: LitData:0x6666_6666_6666_6666, length: LitLength:8, id: LitID:4, last: false, literals_last: false}, + LiteralsDataWithSync {data: LitData:0x6666_6666_6666_6666, length: LitLength:4, id: LitID:4, last: true, literals_last: false}, // 7th literal - LiteralsDataWithSync {data: LitData:0x0000_0000_0000_0000, length: LitLength:0, last: true, id: LitID:5, literals_last: true}, + LiteralsDataWithSync {data: LitData:0x0000_0000_0000_0000, length: LitLength:0, id: LitID:5, last: true, literals_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, 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); - (tok) - }(tok); + // Test #0 + let req = test_rle_req[0]; + let resp = test_rle_resp[0]; + let tok = send(tok, req_s, req); + trace_fmt!("Sent req: {:#x}", req); + let (tok, out_data) = recv(tok, out_r); + trace_fmt!("Received batched data: {:#x}", out_data); + assert_eq(out_data, test_out_data[0]); + let (tok, out_data) = recv(tok, out_r); + trace_fmt!("Received batched data: {:#x}", out_data); + assert_eq(out_data, test_out_data[1]); + let (tok, rle_resp) = recv(tok, resp_r); + trace_fmt!("Received resp: {:#x}", rle_resp); + assert_eq(rle_resp, resp); + + // Test #1 + let req = test_rle_req[1]; + let resp = test_rle_resp[1]; + let tok = send(tok, req_s, req); + trace_fmt!("Sent req: {:#x}", req); + let (tok, out_data) = recv(tok, out_r); + trace_fmt!("Received batched data: {:#x}", out_data); + assert_eq(out_data, test_out_data[2]); + let (tok, rle_resp) = recv(tok, resp_r); + trace_fmt!("Received resp: {:#x}", rle_resp); + assert_eq(rle_resp, resp); + + // Test #2 + let req = test_rle_req[2]; + let resp = test_rle_resp[2]; + let tok = send(tok, req_s, req); + trace_fmt!("Sent req: {:#x}", req); + let (tok, out_data) = recv(tok, out_r); + trace_fmt!("Received batched data: {:#x}", out_data); + assert_eq(out_data, test_out_data[3]); + let (tok, out_data) = recv(tok, out_r); + trace_fmt!("Received batched data: {:#x}", out_data); + assert_eq(out_data, test_out_data[4]); + let (tok, rle_resp) = recv(tok, resp_r); + trace_fmt!("Received resp: {:#x}", rle_resp); + assert_eq(rle_resp, resp); + + // Test #3 + let req = test_rle_req[3]; + let resp = test_rle_resp[3]; + let tok = send(tok, req_s, req); + trace_fmt!("Sent req: {:#x}", req); + let (tok, out_data) = recv(tok, out_r); + trace_fmt!("Received batched data: {:#x}", out_data); + assert_eq(out_data, test_out_data[5]); + let (tok, rle_resp) = recv(tok, resp_r); + trace_fmt!("Received resp: {:#x}", rle_resp); + assert_eq(rle_resp, resp); + + // Test #4 + let req = test_rle_req[4]; + let resp = test_rle_resp[4]; + let tok = send(tok, req_s, req); + trace_fmt!("Sent req: {:#x}", req); + let (tok, out_data) = recv(tok, out_r); + trace_fmt!("Received batched data: {:#x}", out_data); + assert_eq(out_data, test_out_data[6]); + let (tok, out_data) = recv(tok, out_r); + trace_fmt!("Received batched data: {:#x}", out_data); + assert_eq(out_data, test_out_data[7]); + let (tok, out_data) = recv(tok, out_r); + trace_fmt!("Received batched data: {:#x}", out_data); + assert_eq(out_data, test_out_data[8]); + let (tok, rle_resp) = recv(tok, resp_r); + trace_fmt!("Received resp: {:#x}", rle_resp); + assert_eq(rle_resp, resp); + + // Test #5 + let req = test_rle_req[5]; + let resp = test_rle_resp[5]; + let tok = send(tok, req_s, req); + trace_fmt!("Sent req: {:#x}", req); + let (tok, out_data) = recv(tok, out_r); + trace_fmt!("Received batched data: {:#x}", out_data); + assert_eq(out_data, test_out_data[9]); + let (tok, rle_resp) = recv(tok, resp_r); + trace_fmt!("Received resp: {:#x}", rle_resp); + assert_eq(rle_resp, resp); send(tok, terminator, true); } From dae442573777e51970cfeeddc85bdca643ac6495 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 14 Jan 2025 15:58:37 +0100 Subject: [PATCH 45/85] modules/zstd/BUILD: tag targets as manual Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 103 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 81826fe9a4..0c60cced69 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -169,6 +169,7 @@ cc_library( "@com_google_absl//absl/time", "@com_google_absl//absl/types:span", ], + tags = ["manual"], ) xls_dslx_library( @@ -614,6 +615,7 @@ xls_dslx_library( xls_dslx_test( name = "parallel_rams_dslx_test", library = ":parallel_rams_dslx", + tags = ["manual"], ) xls_dslx_library( @@ -895,6 +897,7 @@ xls_dslx_library( xls_dslx_test( name = "ram_wr_handler_dslx_test", library = ":ram_wr_handler_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -912,6 +915,7 @@ xls_dslx_verilog( "top": "__ram_wr_handler__RamWrRespHandlerInst__RamWrRespHandler_0__32_next", }, verilog_file = "ram_rw_handler.v", + tags = ["manual"], ) xls_benchmark_ir( @@ -921,6 +925,7 @@ xls_benchmark_ir( "pipeline_stages": "10", "delay_model": "asap7", }, + tags = ["manual"], ) verilog_library( @@ -928,6 +933,7 @@ verilog_library( srcs = [ ":ram_rw_handler.v", ], + tags = ["manual"], ) synthesize_rtl( @@ -937,11 +943,13 @@ synthesize_rtl( deps = [ ":ram_rw_handler_verilog_lib", ], + tags = ["manual"], ) benchmark_synth( name = "ram_rw_handler_benchmark_synth", synth_target = ":ram_rw_handler_synth_asap7", + tags = ["manual"], ) place_and_route( @@ -953,6 +961,7 @@ place_and_route( stop_after_step = "global_routing", synthesized_rtl = ":ram_rw_handler_synth_asap7", target_die_utilization_percentage = "10", + tags = ["manual"], ) xls_dslx_library( @@ -970,6 +979,7 @@ xls_dslx_library( xls_dslx_test( name = "fse_proba_freq_dec_dslx_test", library = ":fse_proba_freq_dec_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -997,6 +1007,7 @@ xls_dslx_verilog( "top": "__fse_proba_freq_dec__FseProbaFreqDecoderInst__FseProbaFreqDecoder_0__64_7_8_10_1_next", }, verilog_file = "fse_proba_freq_dec.v", + tags = ["manual"], ) xls_benchmark_ir( @@ -1016,11 +1027,13 @@ xls_benchmark_ir( # wr_resp = "fse_proba_freq_dec__wr_resp_r", #), }, + tags = ["manual"], ) xls_benchmark_verilog( name = "fse_proba_freq_dec_verilog_benchmark", verilog_target = "fse_proba_freq_dec_verilog", + tags = ["manual"], ) verilog_library( @@ -1028,6 +1041,7 @@ verilog_library( srcs = [ ":fse_proba_freq_dec.v", ], + tags = ["manual"], ) synthesize_rtl( @@ -1037,11 +1051,13 @@ synthesize_rtl( deps = [ ":fse_proba_freq_dec_lib", ], + tags = ["manual"], ) benchmark_synth( name = "fse_proba_freq_dec_benchmark_synth", synth_target = ":fse_proba_freq_dec_asap7", + tags = ["manual"], ) place_and_route( @@ -1053,6 +1069,7 @@ place_and_route( stop_after_step = "detailed_routing", synthesized_rtl = ":fse_proba_freq_dec_asap7", target_die_utilization_percentage = "10", + tags = ["manual"], ) xls_dslx_library( @@ -1067,6 +1084,7 @@ xls_dslx_test( name = "literals_block_header_dec_dslx_test", dslx_test_args = {"compare": "jit"}, library = ":literals_block_header_dec_dslx", + tags = ["manual"], ) @@ -1103,6 +1121,7 @@ xls_dslx_test( name = "sequence_conf_dec_dslx_test", dslx_test_args = {"compare": "jit"}, library = ":sequence_conf_dec_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -1154,6 +1173,7 @@ xls_dslx_test( name = "refilling_shift_buffer_dslx_test", dslx_test_args = {"compare": "jit"}, library = ":refilling_shift_buffer_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -1338,6 +1358,7 @@ xls_dslx_library( xls_dslx_test( name = "fse_lookup_dec_dslx_test", library = ":fse_lookup_dec_dslx", + tags = ["manual"], ) xls_dslx_library( @@ -1351,6 +1372,7 @@ xls_dslx_library( xls_dslx_test( name = "fse_table_iterator_dslx_test", library = ":fse_table_iterator_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -1365,6 +1387,7 @@ xls_dslx_verilog( dslx_top = "FseTableIterator", library = ":fse_table_iterator_dslx", verilog_file = "fse_table_iterator.v", + tags = ["manual"], ) xls_benchmark_ir( @@ -1374,6 +1397,7 @@ xls_benchmark_ir( "pipeline_stages": "10", "delay_model": "asap7", }, + tags = ["manual"], ) verilog_library( @@ -1381,6 +1405,7 @@ verilog_library( srcs = [ ":fse_table_iterator.v", ], + tags = ["manual"], ) synthesize_rtl( @@ -1390,11 +1415,13 @@ synthesize_rtl( deps = [ ":fse_table_iterator_verilog_lib", ], + tags = ["manual"], ) benchmark_synth( name = "fse_table_iterator_benchmark_synth", synth_target = ":fse_table_iterator_synth_asap7", + tags = ["manual"], ) place_and_route( @@ -1406,6 +1433,7 @@ place_and_route( stop_after_step = "global_routing", synthesized_rtl = ":fse_table_iterator_synth_asap7", target_die_utilization_percentage = "10", + tags = ["manual"], ) xls_dslx_library( @@ -1422,6 +1450,7 @@ xls_dslx_library( xls_dslx_test( name = "fse_table_creator_dslx_test", library = ":fse_table_creator_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -1450,11 +1479,13 @@ xls_benchmark_ir( "pipeline_stages": "10", "delay_model": "asap7", }, + tags = ["manual"], ) xls_benchmark_verilog( name = "fse_table_creator_verilog_benchmark", verilog_target = "fse_table_creator_verilog", + tags = ["manual"], ) verilog_library( @@ -1462,6 +1493,7 @@ verilog_library( srcs = [ ":fse_table_creator.v", ], + tags = ["manual"], ) synthesize_rtl( @@ -1471,11 +1503,13 @@ synthesize_rtl( deps = [ ":fse_table_creator_lib", ], + tags = ["manual"], ) benchmark_synth( name = "fse_table_creator_benchmark_synth", synth_target = ":fse_table_creator_asap7", + tags = ["manual"], ) place_and_route( @@ -1487,6 +1521,7 @@ place_and_route( stop_after_step = "global_routing", synthesized_rtl = ":fse_table_creator_asap7", target_die_utilization_percentage = "10", + tags = ["manual"], ) xls_dslx_library( @@ -1501,6 +1536,7 @@ xls_dslx_test( name = "command_constructor_dslx_test", dslx_test_args = {"compare": "none"}, library = ":command_constructor_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -1515,6 +1551,7 @@ xls_dslx_verilog( dslx_top = "CommandConstructor", library = ":command_constructor_dslx", verilog_file = "command_constructor.v", + tags = ["manual"], ) xls_benchmark_ir( @@ -1524,11 +1561,13 @@ xls_benchmark_ir( "pipeline_stages": "8", "delay_model": "asap7", }, + tags = ["manual"], ) xls_benchmark_verilog( name = "command_constructor_verilog_benchmark", verilog_target = "command_constructor_verilog", + tags = ["manual"], ) verilog_library( @@ -1536,6 +1575,7 @@ verilog_library( srcs = [ ":command_constructor.v", ], + tags = ["manual"], ) synthesize_rtl( @@ -1545,11 +1585,13 @@ synthesize_rtl( deps = [ ":command_constructor_lib", ], + tags = ["manual"], ) benchmark_synth( name = "command_constructor_benchmark_synth", synth_target = ":command_constructor_asap7", + tags = ["manual"], ) place_and_route( @@ -1561,6 +1603,7 @@ place_and_route( stop_after_step = "global_routing", synthesized_rtl = ":command_constructor_asap7", target_die_utilization_percentage = "10", + tags = ["manual"], ) xls_dslx_library( @@ -1575,6 +1618,7 @@ xls_dslx_test( name = "ram_demux_dslx_test", dslx_test_args = {"compare": "none"}, library = ":ram_demux_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -1605,6 +1649,7 @@ xls_dslx_verilog( "top": "__ram_demux__RamDemuxInst__RamDemux_0__5_8_0_8_5_next", }, verilog_file = "ram_demux.v", + tags = ["manual"], ) xls_benchmark_ir( @@ -1616,6 +1661,7 @@ xls_benchmark_ir( codegen_args = { "pipeline_stages": "10", }, + tags = ["manual"], ) verilog_library( @@ -1623,6 +1669,7 @@ verilog_library( srcs = [ ":ram_demux.v", ], + tags = ["manual"], ) synthesize_rtl( @@ -1632,11 +1679,13 @@ synthesize_rtl( deps = [ ":ram_demux_verilog_lib", ], + tags = ["manual"], ) benchmark_synth( name = "ram_demux_benchmark_synth", synth_target = ":ram_demux_synth_asap7", + tags = ["manual"], ) place_and_route( @@ -1648,6 +1697,7 @@ place_and_route( stop_after_step = "global_routing", synthesized_rtl = ":ram_demux_synth_asap7", target_die_utilization_percentage = "5", + tags = ["manual"], ) xls_dslx_verilog( @@ -1677,6 +1727,7 @@ xls_dslx_verilog( "top": "__ram_demux__RamDemuxNaiveInst__RamDemuxNaive_0__5_8_0_8_next", }, verilog_file = "ram_demux_naive.v", + tags = ["manual"], ) xls_benchmark_ir( @@ -1688,6 +1739,7 @@ xls_benchmark_ir( codegen_args = { "pipeline_stages": "10", }, + tags = ["manual"], ) verilog_library( @@ -1695,6 +1747,7 @@ verilog_library( srcs = [ ":ram_demux_naive.v", ], + tags = ["manual"], ) synthesize_rtl( @@ -1704,11 +1757,13 @@ synthesize_rtl( deps = [ ":ram_demux_naive_verilog_lib", ], + tags = ["manual"], ) benchmark_synth( name = "ram_demux_naive_benchmark_synth", synth_target = ":ram_demux_naive_synth_asap7", + tags = ["manual"], ) place_and_route( @@ -1720,6 +1775,7 @@ place_and_route( stop_after_step = "global_routing", synthesized_rtl = ":ram_demux_naive_synth_asap7", target_die_utilization_percentage = "5", + tags = ["manual"], ) xls_dslx_library( @@ -1818,6 +1874,7 @@ xls_dslx_library( xls_dslx_test( name = "ram_demux3_dslx_test", library = ":ram_demux3_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -1845,6 +1902,7 @@ xls_dslx_verilog( dslx_top = "RamDemux3Inst", library = ":ram_demux3_dslx", verilog_file = "ram_demux3.v", + tags = ["manual"], ) xls_benchmark_ir( @@ -1920,6 +1978,7 @@ xls_dslx_library( xls_dslx_test( name = "sequence_dec_dslx_test", library = ":sequence_dec_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -1939,6 +1998,7 @@ xls_dslx_verilog( dslx_top = "FseLookupCtrlInst", library = ":sequence_dec_dslx", verilog_file = "fse_lookup_ctrl.v", + tags = ["manual"], ) xls_benchmark_ir( @@ -1966,6 +2026,7 @@ xls_dslx_library( xls_dslx_test( name = "rle_literals_dec_dslx_test", library = ":rle_literals_dec_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -1995,6 +2056,7 @@ xls_benchmark_ir( "pipeline_stages": "2", "delay_model": "asap7", }, + tags = ["manual"], ) verilog_library( @@ -2002,6 +2064,7 @@ verilog_library( srcs = [ ":rle_literals_dec.v", ], + tags = ["manual"], ) synthesize_rtl( @@ -2011,11 +2074,13 @@ synthesize_rtl( deps = [ ":rle_literals_dec_verilog_lib", ], + tags = ["manual"], ) benchmark_synth( name = "rle_literals_dec_benchmark_synth", synth_target = ":rle_literals_dec_synth_asap7", + tags = ["manual"], ) place_and_route( @@ -2027,6 +2092,7 @@ place_and_route( stop_after_step = "global_routing", synthesized_rtl = ":rle_literals_dec_synth_asap7", target_die_utilization_percentage = "10", + tags = ["manual"], ) xls_dslx_library( @@ -2041,6 +2107,7 @@ xls_dslx_library( xls_dslx_test( name = "raw_literals_dec_dslx_test", library = ":raw_literals_dec_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -2083,6 +2150,7 @@ verilog_library( srcs = [ ":raw_literals_dec.v", ], + tags = ["manual"], ) synthesize_rtl( @@ -2129,6 +2197,7 @@ xls_dslx_library( xls_dslx_test( name = "literals_buffer_dslx_test", library = ":literals_buffer_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -2159,6 +2228,7 @@ xls_dslx_verilog( "top": "__literals_buffer__LiteralsBufferInst__LiteralsBuffer_0__LiteralsBufferReader_0__64_0_0_0_13_8192_65536_next" }, verilog_file = "literals_buffer.v", + tags = ["manual"], ) xls_benchmark_ir( @@ -2168,11 +2238,13 @@ xls_benchmark_ir( "pipeline_stages": "6", "delay_model": "asap7", }, + tags = ["manual"], ) xls_benchmark_verilog( name = "literals_buffer_verilog_benchmark", verilog_target = "literals_buffer_verilog", + tags = ["manual"], ) verilog_library( @@ -2180,6 +2252,7 @@ verilog_library( srcs = [ ":literals_buffer.v", ], + tags = ["manual"], ) synthesize_rtl( @@ -2189,11 +2262,13 @@ synthesize_rtl( deps = [ ":literals_buffer_verilog_lib", ], + tags = ["manual"], ) benchmark_synth( name = "literals_buffer_benchmark_synth", synth_target = ":literals_buffer_synth_asap7", + tags = ["manual"], ) place_and_route( @@ -2205,6 +2280,7 @@ place_and_route( stop_after_step = "global_routing", synthesized_rtl = ":literals_buffer_synth_asap7", target_die_utilization_percentage = "10", + tags = ["manual"], ) xls_dslx_library( @@ -2231,6 +2307,7 @@ xls_dslx_library( xls_dslx_test( name = "literals_decoder_dslx_test", library = ":literals_decoder_dslx", + tags = ["manual"], ) literals_decoder_ctrl_codegen_args = common_codegen_args | { @@ -2257,6 +2334,7 @@ xls_benchmark_ir( "multi_proc": "true", "inline_procs": "false", }, + tags = ["manual"], ) verilog_library( @@ -2264,6 +2342,7 @@ verilog_library( srcs = [ ":literals_decoder_ctrl.v", ], + tags = ["manual"], ) synthesize_rtl( @@ -2273,11 +2352,13 @@ synthesize_rtl( deps = [ ":literals_decoder_ctrl_verilog_lib", ], + tags = ["manual"], ) benchmark_synth( name = "literals_decoder_ctrl_benchmark_synth", synth_target = ":literals_decoder_ctrl_synth_asap7", + tags = ["manual"], ) place_and_route( @@ -2289,6 +2370,7 @@ place_and_route( stop_after_step = "global_routing", synthesized_rtl = ":literals_decoder_ctrl_synth_asap7", target_die_utilization_percentage = "10", + tags = ["manual"], ) xls_dslx_verilog( @@ -2319,6 +2401,7 @@ xls_dslx_verilog( "top": "__xls_modules_zstd_literals_buffer__LiteralsDecoderInst__LiteralsDecoder_0__LiteralsBuffer_0__LiteralsBufferReader_0__64_0_0_0_13_8192_65536_next", }, verilog_file = "literals_decoder.v", + tags = ["manual"], ) xls_benchmark_ir( @@ -2345,6 +2428,7 @@ verilog_library( srcs = [ ":literals_decoder.v", ], + tags = ["manual"], ) synthesize_rtl( @@ -2354,11 +2438,13 @@ synthesize_rtl( deps = [ ":literals_decoder_verilog_lib", ], + tags = ["manual"], ) benchmark_synth( name = "literals_decoder_benchmark_synth", synth_target = ":literals_decoder_synth_asap7", + tags = ["manual"], ) place_and_route( @@ -2370,6 +2456,7 @@ place_and_route( stop_after_step = "global_routing", synthesized_rtl = ":literals_decoder_synth_asap7", target_die_utilization_percentage = "10", + tags = ["manual"], ) xls_dslx_library( @@ -2396,6 +2483,7 @@ xls_dslx_library( xls_dslx_test( name = "huffman_prescan_dslx_test", library = ":huffman_prescan_dslx", + tags = ["manual"], ) prescan_codegen_args = common_codegen_args | { @@ -2418,17 +2506,20 @@ xls_dslx_verilog( dslx_top = "WeightPreScan", library = ":huffman_prescan_dslx", verilog_file = "huffman_prescan.v", + tags = ["manual"], ) xls_benchmark_ir( name = "huffman_prescan_opt_ir_benchmark", src = ":huffman_prescan_verilog.opt.ir", benchmark_ir_args = prescan_codegen_args, + tags = ["manual"], ) xls_benchmark_verilog( name = "huffman_prescan_verilog_benchmark", verilog_target = "huffman_prescan_verilog", + tags = ["manual"], ) verilog_library( @@ -2437,6 +2528,7 @@ verilog_library( ":huffman_prescan.v", "fifo.v" ], + tags = ["manual"], ) synthesize_rtl( @@ -2446,11 +2538,13 @@ synthesize_rtl( deps = [ ":huffman_prescan_verilog_lib", ], + tags = ["manual"], ) benchmark_synth( name = "huffman_prescan_benchmark_synth", synth_target = ":huffman_prescan_synth_asap7", + tags = ["manual"], ) place_and_route( @@ -2461,6 +2555,7 @@ place_and_route( placement_density = "0.30", synthesized_rtl = ":huffman_prescan_synth_asap7", target_die_utilization_percentage = "10", + tags = ["manual"], ) xls_dslx_library( @@ -2479,6 +2574,7 @@ xls_dslx_library( xls_dslx_test( name = "huffman_code_builder_dslx_test", library = ":huffman_code_builder_dslx", + tags = ["manual"], ) huffman_code_builder_codegen_args = common_codegen_args | { @@ -2496,17 +2592,20 @@ xls_dslx_verilog( dslx_top = "WeightCodeBuilder", library = ":huffman_code_builder_dslx", verilog_file = "huffman_code_builder.v", + tags = ["manual"], ) xls_benchmark_ir( name = "huffman_code_builder_opt_ir_benchmark", src = ":huffman_code_builder_verilog.opt.ir", benchmark_ir_args = huffman_code_builder_codegen_args, + tags = ["manual"], ) xls_benchmark_verilog( name = "huffman_code_builder_verilog_benchmark", verilog_target = "huffman_code_builder_verilog", + tags = ["manual"], ) verilog_library( @@ -2515,6 +2614,7 @@ verilog_library( ":huffman_code_builder.v", "fifo.v" ], + tags = ["manual"], ) synthesize_rtl( @@ -2524,11 +2624,13 @@ synthesize_rtl( deps = [ ":huffman_code_builder_verilog_lib", ], + tags = ["manual"], ) benchmark_synth( name = "huffman_code_builder_benchmark_synth", synth_target = ":huffman_code_builder_synth_asap7", + tags = ["manual"], ) place_and_route( @@ -2539,6 +2641,7 @@ place_and_route( placement_density = "0.30", synthesized_rtl = ":huffman_code_builder_synth_asap7", target_die_utilization_percentage = "10", + tags = ["manual"], ) xls_dslx_library( From 7f87db193b1dee0c07f4f06ff4a148259f6ebfc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Ob=C5=82onczek?= Date: Tue, 14 Jan 2025 15:12:33 +0100 Subject: [PATCH 46/85] modules/zstd: Enable CompressedBlockDecoder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert Winkler Signed-off-by: Robert Winkler Signed-off-by: Krzysztof Obłonczek --- xls/modules/zstd/BUILD | 49 +- xls/modules/zstd/comp_block_dec.x | 1386 ++++++++++++++++++++++++++--- 2 files changed, 1273 insertions(+), 162 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 0c60cced69..5e485baf41 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1143,23 +1143,6 @@ xls_benchmark_ir( tags = ["manual"], ) -#xls_dslx_library( -# name = "comp_block_dec_dslx", -# srcs = ["comp_block_dec.x"], -# deps = [ -# "//xls/examples:ram_dslx", -# "//xls/modules/zstd/memory:mem_reader_dslx", -# "//xls/modules/zstd/memory:axi_ram_dslx", -# ":fse_proba_freq_dec_dslx", -# ":literals_block_header_dec_dslx", -# ], -#) -# -#xls_dslx_test( -# name = "comp_block_dec_dslx_test", -# library = ":comp_block_dec_dslx", -#) - xls_dslx_library( name = "refilling_shift_buffer_dslx", srcs = ["refilling_shift_buffer.x"], @@ -1233,14 +1216,30 @@ place_and_route( target_die_utilization_percentage = "10", ) -#xls_dslx_verilog( -# name = "refilling_shift_buffer_verilog", -# codegen_args = common_codegen_args, -# dslx_top = "RefillingShiftBufferInst", -# library = ":refilling_shift_buffer_dslx", -# tags = ["manual"], -# verilog_file = "refilling_shift_buffer.v", -#) +xls_dslx_library( + name = "comp_block_dec_dslx", + srcs = ["comp_block_dec.x"], + deps = [ + "//xls/examples:ram_dslx", + "//xls/modules/zstd/memory:mem_reader_dslx", + "//xls/modules/zstd/memory:axi_ram_dslx", + ":fse_proba_freq_dec_dslx", + ":literals_block_header_dec_dslx", + ":common_dslx", + ":huffman_literals_dec_dslx", + ":parallel_rams_dslx", + ":literals_buffer_dslx", + ":sequence_dec_dslx", + ":literals_decoder_dslx", + ":command_constructor_dslx", + ], +) + +xls_dslx_test( + name = "comp_block_dec_dslx_test", + library = ":comp_block_dec_dslx", + tags = ["manual"], +) zstd_dec_deps = [ ":axi_csr_accessor_dslx", diff --git a/xls/modules/zstd/comp_block_dec.x b/xls/modules/zstd/comp_block_dec.x index 877998336d..9a47caffd1 100644 --- a/xls/modules/zstd/comp_block_dec.x +++ b/xls/modules/zstd/comp_block_dec.x @@ -14,73 +14,81 @@ import std; import xls.examples.ram; +import xls.modules.zstd.memory.axi_ram; +import xls.modules.zstd.common; +import xls.modules.zstd.huffman_literals_dec; +import xls.modules.zstd.parallel_rams; +import xls.modules.zstd.literals_buffer; +import xls.modules.zstd.sequence_dec; +import xls.modules.zstd.literals_block_header_dec; +import xls.modules.zstd.literals_decoder; +import xls.modules.zstd.command_constructor; import xls.modules.zstd.memory.axi; import xls.modules.zstd.memory.mem_reader; import xls.modules.zstd.fse_proba_freq_dec; -import xls.modules.zstd.comp_block_header_dec; +import xls.modules.zstd.fse_table_creator; +import xls.modules.zstd.ram_mux; +type SequenceExecutorPacket = common::SequenceExecutorPacket; +type ExtendedPacket = common::ExtendedBlockDataPacket; +type SequenceExecutorMessageType = common::SequenceExecutorMessageType; +type BlockDataPacket = common::BlockDataPacket; +type BlockSize = common::BlockSize; +type BlockSyncData = common::BlockSyncData; -struct CompressBlockDecoderReq { } -struct CompressBlockDecoderResp { } - -struct CompressBlockDecoderControlState { } - -proc CompressBlockDecoderControl { - type Req = CompressBlockDecoderReq; - type Resp = CompressBlockDecoderResp; - type State = CompressBlockDecoderControlState; - - type HeaderDecoderReq = comp_block_header_dec::CompressBlockHeaderDecoderReq; - type HeaderDecoderResp = comp_block_header_dec::CompressBlockHeaderDecoderResp; - - req_r: chan in; - resp_s: chan out; - cmph_req_s: chan out; - cmph_resp_r: chan in; - - init { zero!() } - - config( - req_r: chan in, - resp_s: chan out, - cmph_req_s: chan out, - cmph_resp_r: chan in, - ) { - (req_r, resp_s, cmph_req_s, cmph_resp_r) - } - - next (state: State) { - let tok = join(); - - send_if(tok, resp_s, false, zero!()); - send_if(tok, cmph_req_s, false, zero!()); - - recv_if(tok, req_r, false, zero!()); - recv_if(tok, cmph_resp_r, false, zero!()); +pub enum CompressBlockDecoderStatus: u1 { + OK = 0, + ERROR = 1, +} - state - } +pub struct CompressBlockDecoderReq { + addr: uN[AXI_ADDR_W], + length: BlockSize, + id: u32, + last_block: bool, +} +pub struct CompressBlockDecoderResp { + status: CompressBlockDecoderStatus } -proc CompressBlockDecoder< +pub proc CompressBlockDecoder< // AXI parameters AXI_DATA_W: u32, AXI_ADDR_W: u32, AXI_ID_W: u32, AXI_DEST_W: u32, + // FSE lookup table RAMs + DPD_RAM_ADDR_W: u32, DPD_RAM_DATA_W: u32, DPD_RAM_NUM_PARTITIONS: u32, + TMP_RAM_ADDR_W: u32, TMP_RAM_DATA_W: u32, TMP_RAM_NUM_PARTITIONS: u32, + FSE_RAM_ADDR_W: u32, FSE_RAM_DATA_W: u32, FSE_RAM_NUM_PARTITIONS: u32, + + // for literals decoder + HISTORY_BUFFER_SIZE_KB: u32 = {common::HISTORY_BUFFER_SIZE_KB}, + // FSE proba FSE_PROBA_DIST_W: u32 = {u32:16}, FSE_PROBA_MAX_DISTS: u32 = {u32:256}, // constants - FSE_DEF_PROBA_DIST_RAM_DATA_W: u32 = {FSE_PROBA_DIST_W}, - FSE_DEF_PROBA_DIST_RAM_SIZE: u32 = {FSE_PROBA_MAX_DISTS}, - FSE_DEF_PROBA_DIST_ADDR_W: u32 = {std::clog2(FSE_DEF_PROBA_DIST_RAM_SIZE)}, - FSE_DEF_PROBA_DIST_RAM_WORD_PARTITION_SIZE: u32 = { FSE_DEF_PROBA_DIST_RAM_DATA_W }, - FSE_DEF_PROBA_DIST_RAM_NUM_PARTITIONS: u32 = { ram::num_partitions(FSE_DEF_PROBA_DIST_RAM_WORD_PARTITION_SIZE, FSE_DEF_PROBA_DIST_RAM_DATA_W) }, AXI_DATA_W_DIV8: u32 = {AXI_DATA_W / u32:8}, + + // Huffman weights memory parameters + HUFFMAN_WEIGHTS_RAM_ADDR_WIDTH: u32 = {huffman_literals_dec::WEIGHTS_ADDR_WIDTH}, + HUFFMAN_WEIGHTS_RAM_DATA_WIDTH: u32 = {huffman_literals_dec::WEIGHTS_DATA_WIDTH}, + HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS: u32 = {huffman_literals_dec::WEIGHTS_NUM_PARTITIONS}, + // Huffman prescan memory parameters + HUFFMAN_PRESCAN_RAM_ADDR_WIDTH: u32 = {huffman_literals_dec::PRESCAN_ADDR_WIDTH}, + HUFFMAN_PRESCAN_RAM_DATA_WIDTH: u32 = {huffman_literals_dec::PRESCAN_DATA_WIDTH}, + HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS: u32 = {huffman_literals_dec::PRESCAN_NUM_PARTITIONS}, + // Literals buffer memory parameters + LITERALS_BUFFER_RAM_ADDR_WIDTH: u32 = {parallel_rams::ram_addr_width(HISTORY_BUFFER_SIZE_KB)}, + LITERALS_BUFFER_RAM_DATA_WIDTH: u32 = {literals_buffer::RAM_DATA_WIDTH}, + LITERALS_BUFFER_RAM_NUM_PARTITIONS: u32 = {literals_buffer::RAM_NUM_PARTITIONS}, > { - type Req = CompressBlockDecoderReq; + type Req = CompressBlockDecoderReq; type Resp = CompressBlockDecoderResp; + type SequenceDecReq = sequence_dec::SequenceDecoderReq; + type SequenceDecResp = sequence_dec::SequenceDecoderResp; + type MemReaderReq = mem_reader::MemReaderReq; type MemReaderResp = mem_reader::MemReaderResp; @@ -90,154 +98,1258 @@ proc CompressBlockDecoder< type MemAxiW = axi::AxiW; type MemAxiB = axi::AxiB; - type HeaderDecoderReq = comp_block_header_dec::CompressBlockHeaderDecoderReq; - type HeaderDecoderResp = comp_block_header_dec::CompressBlockHeaderDecoderResp; + type DpdRamRdReq = ram::ReadReq; + type DpdRamRdResp = ram::ReadResp; + type DpdRamWrReq = ram::WriteReq; + type DpdRamWrResp = ram::WriteResp; - //type ShiftBufferInput = shift_buffer::ShiftBufferInput; - //type ShiftBufferCtrl = shift_buffer::ShiftBufferCtrl; - //type ShiftBufferOutput = shift_buffer::ShiftBufferOutput; + type TmpRamRdReq = ram::ReadReq; + type TmpRamRdResp = ram::ReadResp; + type TmpRamWrReq = ram::WriteReq; + type TmpRamWrResp = ram::WriteResp; - type DpdRamRdReq = ram::ReadReq; - type DpdRamRdResp = ram::ReadResp; - type DpdRamWrReq = ram::WriteReq; - type DpdRamWrResp = ram::WriteResp; + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; + type FseRamWrReq = ram::WriteReq; + type FseRamWrResp = ram::WriteResp; + + type LiteralsHeaderDecoderResp = literals_block_header_dec::LiteralsHeaderDecoderResp; + type LiteralsBlockType = literals_block_header_dec::LiteralsBlockType; + type LiteralsDecReq = literals_decoder::LiteralsDecoderCtrlReq; + type LiteralsDecResp = literals_decoder::LiteralsDecoderCtrlResp; + type LiteralsBufCtrl = common::LiteralsBufferCtrl; + type CommandConstructorData = common::CommandConstructorData; + + type HuffmanWeightsReadReq = ram::ReadReq; + type HuffmanWeightsReadResp = ram::ReadResp; + type HuffmanWeightsWriteReq = ram::WriteReq; + type HuffmanWeightsWriteResp = ram::WriteResp; + type HuffmanPrescanReadReq = ram::ReadReq; + type HuffmanPrescanReadResp = ram::ReadResp; + type HuffmanPrescanWriteReq = ram::WriteReq; + type HuffmanPrescanWriteResp = ram::WriteResp; + + type LitBufRamRdReq = ram::ReadReq; + type LitBufRamRdResp = ram::ReadResp; + type LitBufRamWrReq = ram::WriteReq; + type LitBufRamWrResp = ram::WriteResp; + + type AxiAddrW = uN[AXI_ADDR_W]; + + req_r: chan in; + resp_s: chan out; + + lit_ctrl_req_s: chan out; + lit_ctrl_resp_r: chan in; + lit_ctrl_header_r: chan in; + + seq_dec_req_s: chan out; + seq_dec_resp_r: chan in; init {} - next(state: ()) {} config( req_r: chan in, resp_s: chan out, - // AXI Compressed Block Header Decoder (manager) - cmph_axi_ar_s: chan out, - cmph_axi_r_r: chan in, + // output from Command constructor to Sequence executor + cmd_constr_out_s: chan out, - // AXI Fse Probability Frequence Decoder (manager) - //pfd_axi_ar_s: chan out, - //pfd_axi_r_r: chan in, + // Sequence Decoder channels - ) { + // Sequence Conf Decoder (manager) + scd_axi_ar_s: chan out, + scd_axi_r_r: chan in, + + // Fse Lookup Decoder (manager) + fld_axi_ar_s: chan out, + fld_axi_r_r: chan in, + + // FSE decoder (manager) + fd_axi_ar_s: chan out, + fd_axi_r_r: chan in, + + // RAMs for FSE decoder + dpd_rd_req_s: chan out, + dpd_rd_resp_r: chan in, + dpd_wr_req_s: chan out, + dpd_wr_resp_r: chan in, + tmp_rd_req_s: chan out, + tmp_rd_resp_r: chan in, + tmp_wr_req_s: chan out, + tmp_wr_resp_r: chan in, + + ll_def_fse_rd_req_s: chan out, + ll_def_fse_rd_resp_r: chan in, + ll_def_fse_wr_req_s: chan out, + ll_def_fse_wr_resp_r: chan in, + + ll_fse_rd_req_s: chan out, + ll_fse_rd_resp_r: chan in, + ll_fse_wr_req_s: chan out, + ll_fse_wr_resp_r: chan in, + + ml_def_fse_rd_req_s: chan out, + ml_def_fse_rd_resp_r: chan in, + ml_def_fse_wr_req_s: chan out, + ml_def_fse_wr_resp_r: chan in, + + ml_fse_rd_req_s: chan out, + ml_fse_rd_resp_r: chan in, + ml_fse_wr_req_s: chan out, + ml_fse_wr_resp_r: chan in, + + of_def_fse_rd_req_s: chan out, + of_def_fse_rd_resp_r: chan in, + of_def_fse_wr_req_s: chan out, + of_def_fse_wr_resp_r: chan in, + + of_fse_rd_req_s: chan out, + of_fse_rd_resp_r: chan in, + of_fse_wr_req_s: chan out, + of_fse_wr_resp_r: chan in, + + // Literals decoder channels + + // AXI Literals Header Decoder (manager) + lit_header_axi_ar_s: chan out, + lit_header_axi_r_r: chan in, + + // AXI Raw Literals Decoder (manager) + raw_lit_axi_ar_s: chan out, + raw_lit_axi_r_r: chan in, + + // AXI Huffman Literals Decoder (manager) + huffman_lit_axi_ar_s: chan out, + huffman_lit_axi_r_r: chan in, + + // AXI Huffman Jump Table Decoder (manager) + huffman_jump_table_axi_ar_s: chan out, + huffman_jump_table_axi_r_r: chan in, + + // AXI Huffman Weights Header Decoder (manager) + huffman_weights_header_axi_ar_s: chan out, + huffman_weights_header_axi_r_r: chan in, + + // AXI Huffman Weights RAW Decoder (manager) + huffman_weights_raw_axi_ar_s: chan out, + huffman_weights_raw_axi_r_r: chan in, + + // AXI Huffman Weights FSE Decoder (manager) + huffman_weights_fse_axi_ar_s: chan out, + huffman_weights_fse_axi_r_r: chan in, + + // Literals buffer internal memory + 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, + + // Huffman weights memory + huffman_lit_weights_mem_rd_req_s: chan out, + huffman_lit_weights_mem_rd_resp_r: chan in, + huffman_lit_weights_mem_wr_req_s: chan out, + huffman_lit_weights_mem_wr_resp_r: chan in, + + // Huffman prescan memory + huffman_lit_prescan_mem_rd_req_s: chan out, + huffman_lit_prescan_mem_rd_resp_r: chan in, + huffman_lit_prescan_mem_wr_req_s: chan out, + huffman_lit_prescan_mem_wr_resp_r: chan in, + + ) { + // TODO: for consistency all MemReaders should be in toplevel ZSTD decoder + // so we should move them up in the hierarchy from LiteralsDecoder + // and SequenceDecoder to the toplevel const CHANNEL_DEPTH = u32:1; - // Compress Block Header Memory Reader + let (lit_ctrl_req_s, lit_ctrl_req_r) = chan("lit_ctrl_req"); + let (lit_ctrl_resp_s, lit_ctrl_resp_r) = chan("lit_ctrl_resp"); + let (lit_ctrl_header_s, lit_ctrl_header_r) = chan("lit_header"); - let (cmph_mem_rd_req_s, cmph_mem_rd_req_r) = chan("cmph_mem_rd_req"); - let (cmph_mem_rd_resp_s, cmph_mem_rd_resp_r) = chan("cmph_mem_rd_resp"); + let (lit_buf_ctrl_s, lit_buf_ctrl_r) = chan("lit_buf_ctrl"); + let (lit_buf_out_s, lit_buf_out_r) = chan("lit_buf_out"); - spawn mem_reader::MemReader( - cmph_mem_rd_req_r, cmph_mem_rd_resp_s, - cmph_axi_ar_s, cmph_axi_r_r, + spawn literals_decoder::LiteralsDecoder< + HISTORY_BUFFER_SIZE_KB, + AXI_DATA_W, AXI_ADDR_W, AXI_ID_W, AXI_DEST_W + >( + lit_header_axi_ar_s, lit_header_axi_r_r, + raw_lit_axi_ar_s, raw_lit_axi_r_r, + huffman_lit_axi_ar_s, huffman_lit_axi_r_r, + huffman_jump_table_axi_ar_s, huffman_jump_table_axi_r_r, + huffman_weights_header_axi_ar_s, huffman_weights_header_axi_r_r, + huffman_weights_raw_axi_ar_s, huffman_weights_raw_axi_r_r, + huffman_weights_fse_axi_ar_s, huffman_weights_fse_axi_r_r, + lit_ctrl_req_r, lit_ctrl_resp_s, lit_ctrl_header_s, + lit_buf_ctrl_r, lit_buf_out_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, + huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_resp_r, + huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_resp_r, + huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_resp_r, + huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_resp_r, ); - let (cmph_req_s, cmph_req_r) = chan("cmph_dec_req"); - let (cmph_resp_s, cmph_resp_r) = chan("cmph_dec_resp"); + let (seq_dec_req_s, seq_dec_req_r) = chan("seq_dec_req"); + let (seq_dec_resp_s, seq_dec_resp_r) = chan("seq_dec_resp"); + let (seq_dec_command_s, seq_dec_command_r) = chan("seq_dec_command"); - spawn comp_block_header_dec::CompressBlockHeaderDecoder( - cmph_mem_rd_req_s, cmph_mem_rd_resp_r, - cmph_req_r, cmph_resp_s + spawn sequence_dec::SequenceDecoder< + AXI_ADDR_W, AXI_DATA_W, AXI_DEST_W, AXI_ID_W, + DPD_RAM_ADDR_W, DPD_RAM_DATA_W, DPD_RAM_NUM_PARTITIONS, + TMP_RAM_ADDR_W, TMP_RAM_DATA_W, TMP_RAM_NUM_PARTITIONS, + FSE_RAM_ADDR_W, FSE_RAM_DATA_W, FSE_RAM_NUM_PARTITIONS, + >( + scd_axi_ar_s, scd_axi_r_r, + fld_axi_ar_s, fld_axi_r_r, + fd_axi_ar_s, fd_axi_r_r, + seq_dec_req_r, seq_dec_resp_s, + seq_dec_command_s, + dpd_rd_req_s, dpd_rd_resp_r, dpd_wr_req_s, dpd_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + ll_def_fse_rd_req_s, ll_def_fse_rd_resp_r, ll_def_fse_wr_req_s, ll_def_fse_wr_resp_r, + ll_fse_rd_req_s, ll_fse_rd_resp_r, ll_fse_wr_req_s, ll_fse_wr_resp_r, + ml_def_fse_rd_req_s, ml_def_fse_rd_resp_r, ml_def_fse_wr_req_s, ml_def_fse_wr_resp_r, + ml_fse_rd_req_s, ml_fse_rd_resp_r, ml_fse_wr_req_s, ml_fse_wr_resp_r, + of_def_fse_rd_req_s, of_def_fse_rd_resp_r, of_def_fse_wr_req_s, of_def_fse_wr_resp_r, + of_fse_rd_req_s, of_fse_rd_resp_r, of_fse_wr_req_s, of_fse_wr_resp_r, ); - // FseProbaFreqDecoder channels - // - // This part of the decoder is used to prepare FSE lookups if they - // are provided in the encoded ZSTD bitstream (FSE_Compressed_Mode). - // This part of the design sould take the input from the input buffer - // located on the system bus, and decode the probability frequencies - // into probability distribution. The outpud data should be stored in - // a local RAM. One chalange related to this part of the code is how - // to us a single instance of the proc to prepare three different FSE tables - // (for Offsets, Matches, and Literals Lenghts). - - // spawn mem_reader::MemReader< - // AXI_DATA_W, AXI_ADDR_W, AXI_DEST_W, AXI_ID_W, CHANNEL_DEPTH - // >( - // pfd_mem_rd_req_r, pfd_mem_rd_resp_s, - // pfd_axi_ar_s, pfd_axi_r_r, - // ); - // - // spawn shift_buffer::ShiftBuffer< - // SHIFT_BUFFER_DATA_W, SHIFT_BUFFER_LENGTH_W - // >(buff_data_r, ctrl_r, out_s); - // - // - // spawn FseProbaFreqDecoder< - // FSE_DEF_PROBA_DIST_RAM_DATA_W, - // FSE_DEF_PROBA_DIST_RAM_SIZE, - // FSE_DEF_PROBA_DIST_RAM_WORD_PARTITION_SIZE - // >( - // req_r, resp_s, - // buff_in_ctrl_s, buff_out_data_r, - // rd_req_s, rd_resp_r, wr_req_s, wr_resp_r - // ); - // - // let (dpd_rd_req_r, dpd_rd_req_s) = chan("rd_req"); - // let (dpd_rd_req_r, dpd_rd_req_s) = chan("rd_resp"); - // let (dpd_wr_req_r, dpd_wr_req_s) = chan("wr_req"); - // let (dpd_wr_resp_r, dpd_wr_resp_s) = chan("wr_resp"); - // - // spawn ram::RamModel< - // FSE_PROBA_DATA_W, - // FSE_PROBA_RAM_SIZE, - // FSE_PROBA_RAM_WORD_PARTITION_SIZE - // >( - // rd_req_r, rd_resp_s, wr_req_r, wr_resp_s - // ); - - //spawn CompressBlockDecoderControl< - //>( - // req_r, resp_s - // cmph_req_s, cmph_resp_r, - //); - - //spawn fse_proba_freq_dec::FseProbaFreqDecoder(); - () + spawn command_constructor::CommandConstructor( + seq_dec_command_r, + cmd_constr_out_s, + lit_buf_out_r, + lit_buf_ctrl_s, + ); + + ( + req_r, resp_s, + lit_ctrl_req_s, lit_ctrl_resp_r, lit_ctrl_header_r, + seq_dec_req_s, seq_dec_resp_r, + ) + } + + next(_: ()) { + let tok = join(); + + let (tok_req, req) = recv(tok, req_r); + trace_fmt!("[CompressBlockDecoder] Received request: {:#x}", req); + + let lit_ctrl_req = LiteralsDecReq { + addr: req.addr, + literals_last: req.last_block, + }; + let tok_lit1 = send(tok_req, lit_ctrl_req_s, lit_ctrl_req); + trace_fmt!("[CompressBlockDecoder] Sent lit_ctrl_req: {:#x}", lit_ctrl_req); + + let (tok_lit2, lit_header) = recv(tok_lit1, lit_ctrl_header_r); + trace_fmt!("[CompressBlockDecoder] Received lit_header: {:#x}", lit_header); + + let seq_section_offset = lit_header.length as AxiAddrW + match (lit_header.header.literal_type) { + LiteralsBlockType::RAW => lit_header.header.regenerated_size, + LiteralsBlockType::RLE => u20:1, + LiteralsBlockType::COMP | LiteralsBlockType::COMP_4 => lit_header.header.compressed_size, + LiteralsBlockType::TREELESS | LiteralsBlockType::TREELESS_4 => lit_header.header.compressed_size, + _ => fail!("comp_block_dec_unreachable", u20:0), + } as AxiAddrW; + + let seq_section_start = req.addr + seq_section_offset; + let seq_section_end = req.addr + req.length as AxiAddrW; + + let (tok_fin_lit, lit_resp) = recv(tok_lit1, lit_ctrl_resp_r); + trace_fmt!("[CompressBlockDecoder] Received lit_ctrl_resp: {:#x}", lit_resp); + + let seq_req = SequenceDecReq { + start_addr: seq_section_start, + end_addr: seq_section_end, + sync: BlockSyncData { + id: req.id, + last_block: req.last_block, + }, + literals_count: lit_header.header.regenerated_size, + }; + + trace_fmt!("[CompressBlockDecoder] Sending sequence req: {:#x}", seq_req); + let tok_seq = send(tok_fin_lit, seq_dec_req_s, seq_req); + + let (tok_fin_seq, seq_resp) = recv(tok_seq, seq_dec_resp_r); + trace_fmt!("[CompressBlockDecoder] Received sequence resp: {:#x}", seq_resp); + + let tok_finish = join(tok_fin_lit, tok_fin_seq); + send(tok_finish, resp_s, Resp { + status: CompressBlockDecoderStatus::OK + }); } } +const TEST_CASE_RAM_DATA_WIDTH = u32:64; +const TEST_CASE_RAM_SIZE = u32:256; +const TEST_CASE_RAM_ADDR_WIDTH = std::clog2(TEST_CASE_RAM_SIZE); +const TEST_CASE_RAM_WORD_PARTITION_SIZE = TEST_CASE_RAM_DATA_WIDTH / u32:8; +const TEST_CASE_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_CASE_RAM_WORD_PARTITION_SIZE, TEST_CASE_RAM_DATA_WIDTH); +const TEST_CASE_RAM_BASE_ADDR = u32:0; + const TEST_AXI_DATA_W = u32:64; const TEST_AXI_ADDR_W = u32:32; const TEST_AXI_ID_W = u32:4; const TEST_AXI_DEST_W = u32:4; +const TEST_AXI_DATA_W_DIV8 = TEST_AXI_DATA_W / u32:8; + +const TEST_DPD_RAM_DATA_W = u32:16; +const TEST_DPD_RAM_SIZE = u32:256; +const TEST_DPD_RAM_ADDR_W = std::clog2(TEST_DPD_RAM_SIZE); +const TEST_DPD_RAM_WORD_PARTITION_SIZE = TEST_DPD_RAM_DATA_W; +const TEST_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_DPD_RAM_WORD_PARTITION_SIZE, TEST_DPD_RAM_DATA_W); + +const TEST_FSE_RAM_DATA_W = u32:32; +const TEST_FSE_RAM_SIZE = u32:256; +const TEST_FSE_RAM_ADDR_W = std::clog2(TEST_FSE_RAM_SIZE); +const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_W / u32:3; +const TEST_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_FSE_RAM_WORD_PARTITION_SIZE, TEST_FSE_RAM_DATA_W); + +const TEST_TMP_RAM_DATA_W = u32:16; +const TEST_TMP_RAM_SIZE = u32:256; +const TEST_TMP_RAM_ADDR_W = std::clog2(TEST_TMP_RAM_SIZE); +const TEST_TMP_RAM_WORD_PARTITION_SIZE = TEST_TMP_RAM_DATA_W; +const TEST_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_TMP_RAM_WORD_PARTITION_SIZE, TEST_TMP_RAM_DATA_W); + +const TEST_RAM_SIM_RW_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_RAM_INITIALIZED = true; + +const HISTORY_BUFFER_SIZE_KB = common::HISTORY_BUFFER_SIZE_KB; + +const HUFFMAN_WEIGHTS_RAM_ADDR_WIDTH: u32 = huffman_literals_dec::WEIGHTS_ADDR_WIDTH; +const HUFFMAN_WEIGHTS_RAM_DATA_WIDTH: u32 = huffman_literals_dec::WEIGHTS_DATA_WIDTH; +const HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::WEIGHTS_NUM_PARTITIONS; +// Huffman prescan memory parameters +const HUFFMAN_PRESCAN_RAM_ADDR_WIDTH: u32 = huffman_literals_dec::PRESCAN_ADDR_WIDTH; +const HUFFMAN_PRESCAN_RAM_DATA_WIDTH: u32 = huffman_literals_dec::PRESCAN_DATA_WIDTH; +const HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::PRESCAN_NUM_PARTITIONS; +// Literals buffer memory parameters +const LITERALS_BUFFER_RAM_ADDR_WIDTH: u32 = parallel_rams::ram_addr_width(HISTORY_BUFFER_SIZE_KB); +const LITERALS_BUFFER_RAM_SIZE: u32 = parallel_rams::ram_size(HISTORY_BUFFER_SIZE_KB); +const LITERALS_BUFFER_RAM_DATA_WIDTH: u32 = literals_buffer::RAM_DATA_WIDTH; +const LITERALS_BUFFER_RAM_NUM_PARTITIONS: u32 = literals_buffer::RAM_NUM_PARTITIONS; +const LITERALS_BUFFER_RAM_WORD_PARTITION_SIZE: u32 = LITERALS_BUFFER_RAM_DATA_WIDTH; + +const AXI_CHAN_N = u32:10; + + +// testcase format: +// - block length (without block header, essentially length of sequences + literals sections), +// - literals and sequences sections as they appear in memory +// - expected output size +// - expected output +const COMP_BLOCK_DEC_TESTCASES: (u32, u64[64], u32, ExtendedPacket[128])[7] = [ + // RAW + ( + // Test case 0 + // raw literals (18) + sequences with 3 predefined tables (2) + // + // last block generated with: + // ./decodecorpus -pdata2.out -odata2.in -s7110 --block-type=2 --content-size iliteral-type=0 --max-block-size-log=5 + u32:0x1C, + u64[64]:[ + u64:0x0, u64:0x0, // 0x000 + u64:0x0, u64:0x0, // 0x010 + u64:0x0, u64:0x0, // 0x020 + u64:0x0, u64:0x0, // 0x030 + u64:0x0, u64:0x0, // 0x040 + u64:0x0, u64:0x0, // 0x050 + u64:0x0, u64:0x0, // 0x060 + u64:0x0, u64:0x0, // 0x070 + u64:0x0, u64:0x0, // 0x080 + u64:0x0, u64:0x0, // 0x090 + u64:0x0, u64:0x0, // 0x0A0 + u64:0x0, u64:0x0, // 0x0B0 + u64:0x0, u64:0x0, // 0x0C0 + u64:0x0, u64:0x0, // 0x0D0 + u64:0x0, u64:0x0, // 0x0E0 + u64:0x0, u64:0x0, // 0x0F0 + u64:0x1fba7f9f15523990, + u64:0x43e75b86b1dfe343, + u64:0xc0423000200d6c6, + u64:0x252c492, + u64:0, ... + ], + u32:6, + ExtendedPacket[128]:[ + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x431fba7f9f155239, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xe75b86b1dfe3, length: u32:6 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x192, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xd6c643, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x223, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: true, last_block: false, id: u32:1234, data: u64:0x00, length: u32:1 } + }, + zero!(), ... + ] + ), + ( + // Test case 1 + // raw literals (64) + sequences with 3 predefined tables (1) + // + // last block generated with: + // ./decodecorpus -pdata2.out -odata2.in -s35304 --block-type=2 --content-size --literal-type=0 --max-block-size-log=7 + u32:0x48, + u64[64]:[ + u64:0x0, u64:0x0, // 0x000 + u64:0x0, u64:0x0, // 0x010 + u64:0x0, u64:0x0, // 0x020 + u64:0x0, u64:0x0, // 0x030 + u64:0x0, u64:0x0, // 0x040 + u64:0x0, u64:0x0, // 0x050 + u64:0x0, u64:0x0, // 0x060 + u64:0x0, u64:0x0, // 0x070 + u64:0x0, u64:0x0, // 0x080 + u64:0x0, u64:0x0, // 0x090 + u64:0x0, u64:0x0, // 0x0A0 + u64:0x0, u64:0x0, // 0x0B0 + u64:0x0, u64:0x0, // 0x0C0 + u64:0x0, u64:0x0, // 0x0D0 + u64:0x0, u64:0x0, // 0x0E0 + u64:0x0, u64:0x0, // 0x0F0 + u64:0xc792801500520404, + u64:0x9be2210a8b13a2bb, + u64:0x291994532c422e15, + u64:0x1c37a8940c112bcd, + u64:0xc95f959fa34764de, + u64:0x57c1079b679780bb, + u64:0x7a819dd90c2f2b97, + u64:0x5a829f58ba369e42, + u64:0x13d608b30001d27d, + u64:0, ... + ], + u32:10, + ExtendedPacket[128]:[ + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xa2bbc79280150052, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x2e159be2210a8b13, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x2bcd291994532c42, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x64de1c37a8940c11, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x9fa347, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x116, length: u32:4 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x9b679780bbc95f95, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xd90c2f2b9757c107, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x58ba369e427a819d, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: true, last_block: false, id: u32:1234, data: u64:0xd27d5a829f, length: u32:5 } + }, + zero!(), ... + ] + ), + // RLE + ( + // Test case 2 + // RLE literals (13) + sequences with 3 predefined tables (15) + // + // last block generated with: + // ./decodecorpus -pdata2.out -odata2.in -s52123 --block-type=2 --content-size --literal-type=1 --max-block-size-log=7 + u32:0x35, + u64[64]:[ + u64:0x0, u64:0x0, // 0x000 + u64:0x0, u64:0x0, // 0x010 + u64:0x0, u64:0x0, // 0x020 + u64:0x0, u64:0x0, // 0x030 + u64:0x0, u64:0x0, // 0x040 + u64:0x0, u64:0x0, // 0x050 + u64:0x0, u64:0x0, // 0x060 + u64:0x0, u64:0x0, // 0x070 + u64:0x0, u64:0x0, // 0x080 + u64:0x0, u64:0x0, // 0x090 + u64:0x0, u64:0x0, // 0x0A0 + u64:0x0, u64:0x0, // 0x0B0 + u64:0x0, u64:0x0, // 0x0C0 + u64:0x0, u64:0x0, // 0x0D0 + u64:0x0, u64:0x0, // 0x0E0 + u64:0x0, u64:0x0, // 0x0F0 + u64:0xf006ace2000f7669, + u64:0xdd540313be00074e, + u64:0xb005607a005e2056, + u64:0xa8e58056222e0c33, + u64:0x5404c001f64c80a, + u64:0x834002e100f7dce, + u64:0x40381ea080, + u64:0, ... + ], + u32:21, + ExtendedPacket[128]:[ + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x1f50, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x76767676, length: u32:4 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x21a, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x2, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x1bee, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x76, length: u32:1 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x2026, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x1d93, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x76, length: u32:1 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x2a39, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x76, length: u32:1 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x3111, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x76, length: u32:1 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xe76, length: u32:4 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x303d, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x3, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x36ea, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: true, last_block: false, id: u32:1234, data: u64:0x7676767676, length: u32:5 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x53be, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x14ef, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: true, last_block: false, id: u32:1234, data: u64:0x2ce2, length: u32:4 } + }, + zero!(), ... + ], + ), + ( + // Test case 3 + // RLE literals (102) + sequences with 3 predefined tables (2) + // + // last block generated with: + // ./decodecorpus -pdata2.out -odata2.in -s52352 --block-type=2 --content-size --literal-type=1 --max-block-size-log=7 + u32:0xa, + u64[64]:[ + u64:0x0, u64:0x0, // 0x000 + u64:0x0, u64:0x0, // 0x010 + u64:0x0, u64:0x0, // 0x020 + u64:0x0, u64:0x0, // 0x030 + u64:0x0, u64:0x0, // 0x040 + u64:0x0, u64:0x0, // 0x050 + u64:0x0, u64:0x0, // 0x060 + u64:0x0, u64:0x0, // 0x070 + u64:0x0, u64:0x0, // 0x080 + u64:0x0, u64:0x0, // 0x090 + u64:0x0, u64:0x0, // 0x0A0 + u64:0x0, u64:0x0, // 0x0B0 + u64:0x0, u64:0x0, // 0x0C0 + u64:0x0, u64:0x0, // 0x0D0 + u64:0x0, u64:0x0, // 0x0E0 + u64:0x0, u64:0x0, // 0x0F0 + u64:0x42184c0002f50665, + u64:0x9570, + u64:0, ... + ], + u32:16, + ExtendedPacket[128]:[ + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5, length: u32:6 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x2, length: u32:4 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5, length: u32:4 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x4c, length: u32:6 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: true, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5, length: u32:4 } + }, + zero!(), ... + ] + ), + // Corner cases + ( + // Test case 4 + // RLE literals (0) + sequences with 3 predefined tables (0) + // + // last block generated with: + // ./decodecorpus -pdata2.out -odata2.in -s10761 --block-type=2 --content-size --literal-type=1 --max-block-size-log=7 + u32:0x3, + u64[64]:[ + u64:0x0, u64:0x0, // 0x000 + u64:0x0, u64:0x0, // 0x010 + u64:0x0, u64:0x0, // 0x020 + u64:0x0, u64:0x0, // 0x030 + u64:0x0, u64:0x0, // 0x040 + u64:0x0, u64:0x0, // 0x050 + u64:0x0, u64:0x0, // 0x060 + u64:0x0, u64:0x0, // 0x070 + u64:0x0, u64:0x0, // 0x080 + u64:0x0, u64:0x0, // 0x090 + u64:0x0, u64:0x0, // 0x0A0 + u64:0x0, u64:0x0, // 0x0B0 + u64:0x0, u64:0x0, // 0x0C0 + u64:0x0, u64:0x0, // 0x0D0 + u64:0x0, u64:0x0, // 0x0E0 + u64:0x0, u64:0x0, // 0x0F0 + u64:0x1501, + u64:0, ... + ], + u32:0, + ExtendedPacket[128]:[ + zero!(), ... + ] + ), + ( + // Test case 5 + // RLE literals (0) + sequences with 3 predefined tables (2) + // last block generated with: + //./decodecorpus -pdata2.out -odata2.in -s7294 --block-type=2 --content-size --literal-type=1 --max-block-size-log=7 + u32:0xc, + u64[64]:[ + u64:0x0, u64:0x0, // 0x000 + u64:0x0, u64:0x0, // 0x010 + u64:0x0, u64:0x0, // 0x020 + u64:0x0, u64:0x0, // 0x030 + u64:0x0, u64:0x0, // 0x040 + u64:0x0, u64:0x0, // 0x050 + u64:0x0, u64:0x0, // 0x060 + u64:0x0, u64:0x0, // 0x070 + u64:0x0, u64:0x0, // 0x080 + u64:0x0, u64:0x0, // 0x090 + u64:0x0, u64:0x0, // 0x0A0 + u64:0x0, u64:0x0, // 0x0B0 + u64:0x0, u64:0x0, // 0x0C0 + u64:0x0, u64:0x0, // 0x0D0 + u64:0x0, u64:0x0, // 0x0E0 + u64:0x0, u64:0x0, // 0x0F0 + u64:0x6006ab770002fa01, + u64:0x1020070, + u64:0, ... + ], + u32:2, + ExtendedPacket[128]:[ + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf06, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + packet: BlockDataPacket { last: true, last_block: false, id: u32:1234, data: u64:0x2b77, length: u32:4 } + }, + zero!(), ... + ], + ), + ( + // Test case 6 + // RAW literals (2) + sequences with 3 predefined tables (0) + // last block generated with: + //./decodecorpus -pdata2.out -odata2.in -s38193 --block-type=2 --content-size --literal-type=0 --max-block-size-log=7 + u32:0x4, + u64[64]:[ + u64:0x0, u64:0x0, // 0x000 + u64:0x0, u64:0x0, // 0x010 + u64:0x0, u64:0x0, // 0x020 + u64:0x0, u64:0x0, // 0x030 + u64:0x0, u64:0x0, // 0x040 + u64:0x0, u64:0x0, // 0x050 + u64:0x0, u64:0x0, // 0x060 + u64:0x0, u64:0x0, // 0x070 + u64:0x0, u64:0x0, // 0x080 + u64:0x0, u64:0x0, // 0x090 + u64:0x0, u64:0x0, // 0x0A0 + u64:0x0, u64:0x0, // 0x0B0 + u64:0x0, u64:0x0, // 0x0C0 + u64:0x0, u64:0x0, // 0x0D0 + u64:0x0, u64:0x0, // 0x0E0 + u64:0x0, u64:0x0, // 0x0F0 + u64:0x215a10, + u64:0, ... + ], + u32:1, + ExtendedPacket[128]:[ + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: true, last_block: false, id: u32:1234, data: u64:0x215a, length: u32:2 } + }, + zero!(), ... + ], + ) +]; #[test_proc] proc CompressBlockDecoderTest { - type Req = CompressBlockDecoderReq; + type Req = CompressBlockDecoderReq; type Resp = CompressBlockDecoderResp; + type SequenceDecReq = sequence_dec::SequenceDecoderReq; + type SequenceDecResp = sequence_dec::SequenceDecoderResp; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + type MemAxiAr = axi::AxiAr; type MemAxiR = axi::AxiR; + type MemAxiAw = axi::AxiAw; + type MemAxiW = axi::AxiW; + type MemAxiB = axi::AxiB; + + type DpdRamRdReq = ram::ReadReq; + type DpdRamRdResp = ram::ReadResp; + type DpdRamWrReq = ram::WriteReq; + type DpdRamWrResp = ram::WriteResp; + + type TmpRamRdReq = ram::ReadReq; + type TmpRamRdResp = ram::ReadResp; + type TmpRamWrReq = ram::WriteReq; + type TmpRamWrResp = ram::WriteResp; + + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; + type FseRamWrReq = ram::WriteReq; + type FseRamWrResp = ram::WriteResp; + + type LiteralsHeaderDecoderResp = literals_block_header_dec::LiteralsHeaderDecoderResp; + type LiteralsBlockType = literals_block_header_dec::LiteralsBlockType; + type LiteralsDecReq = literals_decoder::LiteralsDecoderCtrlReq; + type LiteralsDecResp = literals_decoder::LiteralsDecoderCtrlResp; + type LiteralsBufCtrl = common::LiteralsBufferCtrl; + type SequenceExecutorPacket = common::SequenceExecutorPacket; + type CommandConstructorData = common::CommandConstructorData; + + type HuffmanWeightsReadReq = ram::ReadReq; + type HuffmanWeightsReadResp = ram::ReadResp; + type HuffmanWeightsWriteReq = ram::WriteReq; + type HuffmanWeightsWriteResp = ram::WriteResp; + type HuffmanPrescanReadReq = ram::ReadReq; + type HuffmanPrescanReadResp = ram::ReadResp; + type HuffmanPrescanWriteReq = ram::WriteReq; + type HuffmanPrescanWriteResp = ram::WriteResp; + + type LitBufRamRdReq = ram::ReadReq; + type LitBufRamRdResp = ram::ReadResp; + type LitBufRamWrReq = ram::WriteReq; + type LitBufRamWrResp = ram::WriteResp; + + type TestcaseRamRdReq = ram::ReadReq; + type TestcaseRamRdResp = ram::ReadResp; + type TestcaseRamWrReq = ram::WriteReq; + type TestcaseRamWrResp = ram::WriteResp; terminator: chan out; req_s: chan out; resp_r: chan in; - cmph_axi_ar_r: chan in; - cmph_axi_r_s: chan out; + cmd_constr_out_r: chan in; + axi_ram_wr_req_s: chan[AXI_CHAN_N] out; + axi_ram_wr_resp_r: chan[AXI_CHAN_N] in; + + ll_sel_test_s: chan out; + ll_def_test_rd_req_s: chan out; + ll_def_test_rd_resp_r: chan in; + ll_def_test_wr_req_s: chan out; + ll_def_test_wr_resp_r: chan in; + + ml_sel_test_s: chan out; + ml_def_test_rd_req_s: chan out; + ml_def_test_rd_resp_r: chan in; + ml_def_test_wr_req_s: chan out; + ml_def_test_wr_resp_r: chan in; + + of_sel_test_s: chan out; + of_def_test_rd_req_s: chan out; + of_def_test_rd_resp_r: chan in; + of_def_test_wr_req_s: chan out; + of_def_test_wr_resp_r: chan in; init {} config(terminator: chan out) { let (req_s, req_r) = chan("req"); let (resp_s, resp_r) = chan("resp"); - let (cmph_axi_ar_s, cmph_axi_ar_r) = chan("cmph_axi_ar"); - let (cmph_axi_r_s, cmph_axi_r_r) = chan("cmph_axi_r"); + // output from Command constructor to Sequence executor + let (cmd_constr_out_s, cmd_constr_out_r) = chan("cmd_constr_out"); + + // Huffman weights memory + let (huffman_lit_weights_mem_rd_req_s, _huffman_lit_weights_mem_rd_req_r) = chan("huffman_lit_weights_mem_rd_req"); + let (_huffman_lit_weights_mem_rd_resp_s, huffman_lit_weights_mem_rd_resp_r) = chan("huffman_lit_weights_mem_rd_resp"); + let (huffman_lit_weights_mem_wr_req_s, _huffman_lit_weights_mem_wr_req_r) = chan("huffman_lit_weights_mem_wr_req"); + let (_huffman_lit_weights_mem_wr_resp_s, huffman_lit_weights_mem_wr_resp_r) = chan("huffman_lit_weights_mem_wr_resp"); + + // Huffman prescan memory + let (huffman_lit_prescan_mem_rd_req_s, _huffman_lit_prescan_mem_rd_req_r) = chan("huffman_lit_prescan_mem_rd_req"); + let (_huffman_lit_prescan_mem_rd_resp_s, huffman_lit_prescan_mem_rd_resp_r) = chan("huffman_lit_prescan_mem_rd_resp"); + let (huffman_lit_prescan_mem_wr_req_s, _huffman_lit_prescan_mem_wr_req_r) = chan("huffman_lit_prescan_mem_wr_req"); + let (_huffman_lit_prescan_mem_wr_resp_s, huffman_lit_prescan_mem_wr_resp_r) = chan("huffman_lit_prescan_mem_wr_resp"); + + // AXI channels for various blocks + let (axi_ram_rd_req_s, axi_ram_rd_req_r) = chan[AXI_CHAN_N]("axi_ram_rd_req"); + let (axi_ram_rd_resp_s, axi_ram_rd_resp_r) = chan[AXI_CHAN_N]("axi_ram_rd_resp"); + let (axi_ram_wr_req_s, axi_ram_wr_req_r) = chan[AXI_CHAN_N]("axi_ram_wr_req"); + let (axi_ram_wr_resp_s, axi_ram_wr_resp_r) = chan[AXI_CHAN_N]("axi_ram_wr_resp"); + let (axi_ram_ar_s, axi_ram_ar_r) = chan[AXI_CHAN_N]("axi_ram_ar"); + let (axi_ram_r_s, axi_ram_r_r) = chan[AXI_CHAN_N]("axi_ram_r"); + unroll_for! (i, ()): (u32, ()) in range(u32:0, AXI_CHAN_N) { + spawn ram::RamModel< + TEST_CASE_RAM_DATA_WIDTH, TEST_CASE_RAM_SIZE, TEST_CASE_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIM_RW_BEHAVIOR, TEST_RAM_INITIALIZED + >( + axi_ram_rd_req_r[i], axi_ram_rd_resp_s[i], axi_ram_wr_req_r[i], axi_ram_wr_resp_s[i] + ); + spawn axi_ram::AxiRamReader< + TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, TEST_CASE_RAM_SIZE, + TEST_CASE_RAM_BASE_ADDR, TEST_CASE_RAM_DATA_WIDTH, TEST_CASE_RAM_ADDR_WIDTH + >( + axi_ram_ar_r[i], axi_ram_r_s[i], axi_ram_rd_req_s[i], axi_ram_rd_resp_r[i] + ); + }(()); + + // Literals buffer RAMs + let (litbuf_rd_req_s, litbuf_rd_req_r) = chan[u32:8]("litbuf_rd_req"); + let (litbuf_rd_resp_s, litbuf_rd_resp_r) = chan[u32:8]("litbuf_rd_resp"); + let (litbuf_wr_req_s, litbuf_wr_req_r) = chan[u32:8]("litbuf_wr_req"); + let (litbuf_wr_resp_s, litbuf_wr_resp_r) = chan[u32:8]("litbuf_wr_resp"); + unroll_for! (i, ()): (u32, ()) in range(u32:0, u32:8) { + spawn ram::RamModel< + LITERALS_BUFFER_RAM_DATA_WIDTH, LITERALS_BUFFER_RAM_SIZE, LITERALS_BUFFER_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIM_RW_BEHAVIOR, TEST_RAM_INITIALIZED + >( + litbuf_rd_req_r[i], litbuf_rd_resp_s[i], litbuf_wr_req_r[i], litbuf_wr_resp_s[i] + ); + }(()); + + // RAMs for FSE decoder + // DPD RAM + let (dpd_rd_req_s, dpd_rd_req_r) = chan("dpd_rd_req"); + let (dpd_rd_resp_s, dpd_rd_resp_r) = chan("dpd_rd_resp"); + let (dpd_wr_req_s, dpd_wr_req_r) = chan("dpd_wr_req"); + let (dpd_wr_resp_s, dpd_wr_resp_r) = chan("dpd_wr_resp"); + spawn ram::RamModel( + dpd_rd_req_r, dpd_rd_resp_s, dpd_wr_req_r, dpd_wr_resp_s, + ); + + // TMP RAM + let (tmp_rd_req_s, tmp_rd_req_r) = chan("tmp_rd_req"); + let (tmp_rd_resp_s, tmp_rd_resp_r) = chan("tmp_rd_resp"); + let (tmp_wr_req_s, tmp_wr_req_r) = chan("tmp_wr_req"); + let (tmp_wr_resp_s, tmp_wr_resp_r) = chan("tmp_wr_resp"); + spawn ram::RamModel< + TEST_TMP_RAM_DATA_W, TEST_TMP_RAM_SIZE, TEST_TMP_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIM_RW_BEHAVIOR, TEST_RAM_INITIALIZED + >( + tmp_rd_req_r, tmp_rd_resp_s, tmp_wr_req_r, tmp_wr_resp_s, + ); + + // FSE RAMs + let (fse_rd_req_s, fse_rd_req_r) = chan[u32:6]("tmp_rd_req"); + let (fse_rd_resp_s, fse_rd_resp_r) = chan[u32:6]("tmp_rd_resp"); + let (fse_wr_req_s, fse_wr_req_r) = chan[u32:6]("tmp_wr_req"); + let (fse_wr_resp_s, fse_wr_resp_r) = chan[u32:6]("tmp_wr_resp"); + unroll_for! (i, ()): (u32, ()) in range(u32:0, u32:6) { + spawn ram::RamModel< + TEST_FSE_RAM_DATA_W, TEST_FSE_RAM_SIZE, TEST_FSE_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIM_RW_BEHAVIOR, TEST_RAM_INITIALIZED + >( + fse_rd_req_r[i], fse_rd_resp_s[i], fse_wr_req_r[i], fse_wr_resp_s[i] + ); + }(()); + + // Default LL + + let (ll_sel_test_s, ll_sel_test_r) = chan("ll_sel_test"); + + let (ll_def_test_rd_req_s, ll_def_test_rd_req_r) = chan("ll_def_test_rd_req"); + let (ll_def_test_rd_resp_s, ll_def_test_rd_resp_r) = chan("ll_def_test_rd_resp"); + let (ll_def_test_wr_req_s, ll_def_test_wr_req_r) = chan("ll_def_test_wr_req"); + let (ll_def_test_wr_resp_s, ll_def_test_wr_resp_r) = chan("ll_def_test_wr_resp"); + + let (ll_def_fse_rd_req_s, ll_def_fse_rd_req_r) = chan("ll_def_fse_rd_req"); + let (ll_def_fse_rd_resp_s, ll_def_fse_rd_resp_r) = chan("ll_def_fse_rd_resp"); + let (ll_def_fse_wr_req_s, ll_def_fse_wr_req_r) = chan("ll_def_fse_wr_req"); + let (ll_def_fse_wr_resp_s, ll_def_fse_wr_resp_r) = chan("ll_def_fse_wr_resp"); + + spawn ram_mux::RamMux< + TEST_FSE_RAM_ADDR_W, + TEST_FSE_RAM_DATA_W, + TEST_FSE_RAM_NUM_PARTITIONS, + >( + ll_sel_test_r, + ll_def_test_rd_req_r, ll_def_test_rd_resp_s, ll_def_test_wr_req_r, ll_def_test_wr_resp_s, + ll_def_fse_rd_req_r, ll_def_fse_rd_resp_s, ll_def_fse_wr_req_r, ll_def_fse_wr_resp_s, + fse_rd_req_s[0], fse_rd_resp_r[0], fse_wr_req_s[0], fse_wr_resp_r[0], + ); + + // Default ML + + let (ml_sel_test_s, ml_sel_test_r) = chan("ml_sel_test"); + + let (ml_def_test_rd_req_s, ml_def_test_rd_req_r) = chan("ml_def_test_rd_req"); + let (ml_def_test_rd_resp_s, ml_def_test_rd_resp_r) = chan("ml_def_test_rd_resp"); + let (ml_def_test_wr_req_s, ml_def_test_wr_req_r) = chan("ml_def_test_wr_req"); + let (ml_def_test_wr_resp_s, ml_def_test_wr_resp_r) = chan("ml_def_test_wr_resp"); + + let (ml_def_fse_rd_req_s, ml_def_fse_rd_req_r) = chan("ml_def_fse_rd_req"); + let (ml_def_fse_rd_resp_s, ml_def_fse_rd_resp_r) = chan("ml_def_fse_rd_resp"); + let (ml_def_fse_wr_req_s, ml_def_fse_wr_req_r) = chan("ml_def_fse_wr_req"); + let (ml_def_fse_wr_resp_s, ml_def_fse_wr_resp_r) = chan("ml_def_fse_wr_resp"); + + spawn ram_mux::RamMux< + TEST_FSE_RAM_ADDR_W, + TEST_FSE_RAM_DATA_W, + TEST_FSE_RAM_NUM_PARTITIONS, + >( + ml_sel_test_r, + ml_def_test_rd_req_r, ml_def_test_rd_resp_s, ml_def_test_wr_req_r, ml_def_test_wr_resp_s, + ml_def_fse_rd_req_r, ml_def_fse_rd_resp_s, ml_def_fse_wr_req_r, ml_def_fse_wr_resp_s, + fse_rd_req_s[2], fse_rd_resp_r[2], fse_wr_req_s[2], fse_wr_resp_r[2], + ); + + // Default OF + + let (of_sel_test_s, of_sel_test_r) = chan("of_sel_test"); + + let (of_def_test_rd_req_s, of_def_test_rd_req_r) = chan("of_def_test_rd_req"); + let (of_def_test_rd_resp_s, of_def_test_rd_resp_r) = chan("of_def_test_rd_resp"); + let (of_def_test_wr_req_s, of_def_test_wr_req_r) = chan("of_def_test_wr_req"); + let (of_def_test_wr_resp_s, of_def_test_wr_resp_r) = chan("of_def_test_wr_resp"); + + let (of_def_fse_rd_req_s, of_def_fse_rd_req_r) = chan("of_def_fse_rd_req"); + let (of_def_fse_rd_resp_s, of_def_fse_rd_resp_r) = chan("of_def_fse_rd_resp"); + let (of_def_fse_wr_req_s, of_def_fse_wr_req_r) = chan("of_def_fse_wr_req"); + let (of_def_fse_wr_resp_s, of_def_fse_wr_resp_r) = chan("of_def_fse_wr_resp"); + + spawn ram_mux::RamMux< + TEST_FSE_RAM_ADDR_W, + TEST_FSE_RAM_DATA_W, + TEST_FSE_RAM_NUM_PARTITIONS, + >( + of_sel_test_r, + of_def_test_rd_req_r, of_def_test_rd_resp_s, of_def_test_wr_req_r, of_def_test_wr_resp_s, + of_def_fse_rd_req_r, of_def_fse_rd_resp_s, of_def_fse_wr_req_r, of_def_fse_wr_resp_s, + fse_rd_req_s[4], fse_rd_resp_r[4], fse_wr_req_s[4], fse_wr_resp_r[4], + ); spawn CompressBlockDecoder< - TEST_AXI_DATA_W, TEST_AXI_ADDR_W, TEST_AXI_ID_W, TEST_AXI_DEST_W + TEST_AXI_DATA_W, TEST_AXI_ADDR_W, TEST_AXI_ID_W, TEST_AXI_DEST_W, + // FSE lookup table RAMs + TEST_DPD_RAM_ADDR_W, TEST_DPD_RAM_DATA_W, TEST_DPD_RAM_NUM_PARTITIONS, + TEST_TMP_RAM_ADDR_W, TEST_TMP_RAM_DATA_W, TEST_TMP_RAM_NUM_PARTITIONS, + TEST_FSE_RAM_ADDR_W, TEST_FSE_RAM_DATA_W, TEST_FSE_RAM_NUM_PARTITIONS, >( req_r, resp_s, - cmph_axi_ar_s, cmph_axi_r_r); + cmd_constr_out_s, + axi_ram_ar_s[0], axi_ram_r_r[0], + axi_ram_ar_s[1], axi_ram_r_r[1], + axi_ram_ar_s[2], axi_ram_r_r[2], + dpd_rd_req_s, dpd_rd_resp_r, dpd_wr_req_s, dpd_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + ll_def_fse_rd_req_s, ll_def_fse_rd_resp_r, ll_def_fse_wr_req_s, ll_def_fse_wr_resp_r, + fse_rd_req_s[1], fse_rd_resp_r[1], fse_wr_req_s[1], fse_wr_resp_r[1], + ml_def_fse_rd_req_s, ml_def_fse_rd_resp_r, ml_def_fse_wr_req_s, ml_def_fse_wr_resp_r, + fse_rd_req_s[3], fse_rd_resp_r[3], fse_wr_req_s[3], fse_wr_resp_r[3], + of_def_fse_rd_req_s, of_def_fse_rd_resp_r, of_def_fse_wr_req_s, of_def_fse_wr_resp_r, + fse_rd_req_s[5], fse_rd_resp_r[5], fse_wr_req_s[5], fse_wr_resp_r[5], + axi_ram_ar_s[3], axi_ram_r_r[3], + axi_ram_ar_s[4], axi_ram_r_r[4], + axi_ram_ar_s[5], axi_ram_r_r[5], + axi_ram_ar_s[6], axi_ram_r_r[6], + axi_ram_ar_s[7], axi_ram_r_r[7], + axi_ram_ar_s[8], axi_ram_r_r[8], + axi_ram_ar_s[9], axi_ram_r_r[9], + litbuf_rd_req_s[0], litbuf_rd_req_s[1], litbuf_rd_req_s[2], litbuf_rd_req_s[3], + litbuf_rd_req_s[4], litbuf_rd_req_s[5], litbuf_rd_req_s[6], litbuf_rd_req_s[7], + litbuf_rd_resp_r[0], litbuf_rd_resp_r[1], litbuf_rd_resp_r[2], litbuf_rd_resp_r[3], + litbuf_rd_resp_r[4], litbuf_rd_resp_r[5], litbuf_rd_resp_r[6], litbuf_rd_resp_r[7], + litbuf_wr_req_s[0], litbuf_wr_req_s[1], litbuf_wr_req_s[2], litbuf_wr_req_s[3], + litbuf_wr_req_s[4], litbuf_wr_req_s[5], litbuf_wr_req_s[6], litbuf_wr_req_s[7], + litbuf_wr_resp_r[0], litbuf_wr_resp_r[1], litbuf_wr_resp_r[2], litbuf_wr_resp_r[3], + litbuf_wr_resp_r[4], litbuf_wr_resp_r[5], litbuf_wr_resp_r[6], litbuf_wr_resp_r[7], + huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_resp_r, + huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_resp_r, + huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_resp_r, + huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_resp_r, + ); ( terminator, req_s, resp_r, - cmph_axi_ar_r, cmph_axi_r_s + cmd_constr_out_r, + axi_ram_wr_req_s, axi_ram_wr_resp_r, + + ll_sel_test_s, + ll_def_test_rd_req_s, ll_def_test_rd_resp_r, ll_def_test_wr_req_s, ll_def_test_wr_resp_r, + + ml_sel_test_s, + ml_def_test_rd_req_s, ml_def_test_rd_resp_r, ml_def_test_wr_req_s, ml_def_test_wr_resp_r, + + of_sel_test_s, + of_def_test_rd_req_s, of_def_test_rd_resp_r, of_def_test_wr_req_s, of_def_test_wr_resp_r, ) } next(state: ()) { let tok = join(); + + // FILL THE LL DEFAULT RAM + trace_fmt!("Filling LL default FSE table"); + let tok = send(tok, ll_sel_test_s, u1:0); + let tok = unroll_for! (i, tok): (u32, token) in range(u32:0, array_size(sequence_dec::DEFAULT_LL_TABLE)) { + let req = FseRamWrReq { + addr: i as uN[TEST_FSE_RAM_ADDR_W], + data: fse_table_creator::fse_record_to_bits(sequence_dec::DEFAULT_LL_TABLE[i]), + mask: !uN[TEST_FSE_RAM_NUM_PARTITIONS]:0, + }; + let tok = send(tok, ll_def_test_wr_req_s, req); + let (tok, _) = recv(tok, ll_def_test_wr_resp_r); + tok + }(tok); + let tok = send(tok, ll_sel_test_s, u1:1); + + // FILL THE OF DEFAULT RAM + trace_fmt!("Filling OF default FSE table"); + let tok = send(tok, of_sel_test_s, u1:0); + let tok = unroll_for! (i, tok): (u32, token) in range(u32:0, array_size(sequence_dec::DEFAULT_OF_TABLE)) { + let req = FseRamWrReq { + addr: i as uN[TEST_FSE_RAM_ADDR_W], + data: fse_table_creator::fse_record_to_bits(sequence_dec::DEFAULT_OF_TABLE[i]), + mask: !uN[TEST_FSE_RAM_NUM_PARTITIONS]:0, + }; + let tok = send(tok, of_def_test_wr_req_s, req); + let (tok, _) = recv(tok, of_def_test_wr_resp_r); + tok + }(tok); + let tok = send(tok, of_sel_test_s, u1:1); + + // FILL THE ML DEFAULT RAM + trace_fmt!("Filling ML default FSE table"); + let tok = send(tok, ml_sel_test_s, u1:0); + let tok = unroll_for! (i, tok): (u32, token) in range(u32:0, array_size(sequence_dec::DEFAULT_ML_TABLE)) { + let req = FseRamWrReq { + addr: i as uN[TEST_FSE_RAM_ADDR_W], + data: fse_table_creator::fse_record_to_bits(sequence_dec::DEFAULT_ML_TABLE[i]), + mask: !uN[TEST_FSE_RAM_NUM_PARTITIONS]:0, + }; + let tok = send(tok, ml_def_test_wr_req_s, req); + let (tok, _) = recv(tok, ml_def_test_wr_resp_r); + tok + }(tok); + let tok = send(tok, ml_sel_test_s, u1:1); + + let tok = unroll_for!(test_i, tok): (u32, token) in range(u32:6, array_size(COMP_BLOCK_DEC_TESTCASES)) { + let (input_length, input, output_length, output) = COMP_BLOCK_DEC_TESTCASES[test_i]; + + trace_fmt!("Loading testcase {}", test_i); + let tok = for ((i, input_data), tok): ((u32, u64), token) in enumerate(input) { + let req = TestcaseRamWrReq { + addr: i as uN[TEST_CASE_RAM_ADDR_WIDTH], + data: input_data as uN[TEST_CASE_RAM_DATA_WIDTH], + mask: !uN[TEST_CASE_RAM_NUM_PARTITIONS]:0 + }; + // Write to all RAMs + let tok = unroll_for! (j, tok): (u32, token) in range(u32:0, AXI_CHAN_N) { + let tok = send(tok, axi_ram_wr_req_s[j], req); + let (tok, _) = recv(tok, axi_ram_wr_resp_r[j]); + tok + }(tok); + tok + }(tok); + + trace_fmt!("Starting processing testcase {}", test_i); + + let req = Req { + addr: uN[TEST_AXI_ADDR_W]:0x100, + length: input_length as BlockSize, + id: u32:1234, + last_block: false, + }; + + trace_fmt!("Sending request to compressed block decoder: {}", req); + let tok = send(tok, req_s, req); + + let tok = for (i, tok): (u32, token) in range(u32:0, output_length) { + let expected_packet = output[i]; + let (tok, recvd_packet) = recv(tok, cmd_constr_out_r); + trace_fmt!("Received {} command constructor packet: {:#x}", i, recvd_packet); + assert_eq(expected_packet, recvd_packet); + tok + }(tok); + + let (tok, _) = recv(tok, resp_r); + trace_fmt!("Finished processing testcase {}", test_i); + tok + }(tok); + send(tok, terminator, true); } } From 81fa10d56342f02b433dd4cc15df77bfe8d3fe54 Mon Sep 17 00:00:00 2001 From: Krzysztof Oblonczek Date: Mon, 12 May 2025 20:02:31 +0200 Subject: [PATCH 47/85] modules/zstd: Enable CompressedBlockDecoder in ZstdDecoder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert Winkler Signed-off-by: Robert Winkler Signed-off-by: Krzysztof Obłonczek --- xls/modules/zstd/BUILD | 7 +- xls/modules/zstd/zstd_dec.x | 424 +++++++++++++++++++++++-- xls/modules/zstd/zstd_dec_test.x | 529 ++++++++++++++++++++++++------- 3 files changed, 835 insertions(+), 125 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 5e485baf41..3cc6c22d56 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1251,7 +1251,11 @@ zstd_dec_deps = [ ":frame_header_dec_dslx", ":raw_block_dec_dslx", ":rle_block_dec_dslx", + ":comp_block_dec_dslx", ":sequence_executor_dslx", + ":huffman_literals_dec_dslx", + ":literals_buffer_dslx", + ":parallel_rams_dslx", "//xls/examples:ram_dslx", "//xls/modules/zstd/memory:mem_reader_dslx", "//xls/modules/zstd/memory:mem_writer_dslx", @@ -1271,7 +1275,8 @@ xls_dslx_test( srcs = [ "zstd_dec.x", "zstd_dec_test.x", - "zstd_frame_testcases.x", + # "zstd_frame_testcases.x", + "comp_frame.x", ], tags = ["manual"], deps = zstd_dec_deps, diff --git a/xls/modules/zstd/zstd_dec.x b/xls/modules/zstd/zstd_dec.x index 4657e9845a..f88227e88e 100644 --- a/xls/modules/zstd/zstd_dec.x +++ b/xls/modules/zstd/zstd_dec.x @@ -29,8 +29,12 @@ import xls.modules.zstd.block_header; import xls.modules.zstd.block_header_dec; import xls.modules.zstd.raw_block_dec; import xls.modules.zstd.rle_block_dec; +import xls.modules.zstd.comp_block_dec; import xls.modules.zstd.dec_mux; import xls.modules.zstd.sequence_executor; +import xls.modules.zstd.huffman_literals_dec; +import xls.modules.zstd.literals_buffer; +import xls.modules.zstd.parallel_rams; type BlockSize = common::BlockSize; type BlockType = common::BlockType; @@ -64,6 +68,7 @@ enum ZstdDecoderStatus: u5 { RAW_BLOCK_OK = 9, RAW_BLOCK_ERROR = 10, RLE_BLOCK_OK = 11, + CMP_BLOCK_OK = 12, } pub enum Csr: u3 { @@ -148,6 +153,10 @@ proc ZstdDecoderInternal< type RleBlockDecoderReq = rle_block_dec::RleBlockDecoderReq; type RleBlockDecoderResp = rle_block_dec::RleBlockDecoderResp; + type CompressBlockDecoderStatus = comp_block_dec::CompressBlockDecoderStatus; + type CompressBlockDecoderReq = comp_block_dec::CompressBlockDecoderReq; + type CompressBlockDecoderResp = comp_block_dec::CompressBlockDecoderResp; + // CsrConfig csr_rd_req_s: chan out; csr_rd_resp_r: chan in; @@ -171,6 +180,9 @@ proc ZstdDecoderInternal< rle_req_s: chan out; rle_resp_r: chan in; + comp_block_req_s: chan out; + comp_block_resp_r: chan in; + // Output MemWriter output_mem_wr_req_s: chan out; output_mem_wr_resp_r: chan in; @@ -205,6 +217,10 @@ proc ZstdDecoderInternal< rle_req_s: chan out, rle_resp_r: chan in, + // MemReader + CompressedBlockDecoder + comp_block_req_s: chan out, + comp_block_resp_r: chan in, + // Output MemWriter output_mem_wr_req_s: chan out, output_mem_wr_resp_r: chan in, @@ -218,6 +234,7 @@ proc ZstdDecoderInternal< bh_req_s, bh_resp_r, raw_req_s, raw_resp_r, rle_req_s, rle_resp_r, + comp_block_req_s, comp_block_resp_r, output_mem_wr_req_s, output_mem_wr_resp_r, notify_s, reset_s, ) @@ -342,9 +359,27 @@ proc ZstdDecoderInternal< trace_fmt!("[DECODE_RLE_BLOCK]: Received RAW {:#x}", raw_resp); } else {}; + let do_send_cmp_req = (state.fsm == Fsm::DECODE_COMPRESSED_BLOCK) && !state.req_sent; + let cmp_req = CompressBlockDecoderReq { + addr: state.block_addr, + length: checked_cast(state.block_length), + id: state.block_id, + last_block: state.block_last, + }; + let tok1_8 = send_if(tok0, comp_block_req_s, do_send_cmp_req, cmp_req); + if do_send_cmp_req { + trace_fmt!("[DECODE_COMPRESSED_BLOCK]: Sending COMPRESSED request: {:#x}", cmp_req); + } else {}; + + let do_recv_cmp_resp = (state.fsm == Fsm::DECODE_COMPRESSED_BLOCK); + let (tok1_9, cmp_resp, cmp_resp_valid) = recv_if_non_blocking(tok0, comp_block_resp_r, do_recv_cmp_resp, zero!()); + if cmp_resp_valid { + trace_fmt!("[DECODE_COMPRESSED_BLOCK]: Received COMPRESSED {:#x}", cmp_resp); + } else {}; + let new_state = match (state.fsm) { Fsm::IDLE => { - trace_fmt!("[IDLE]"); + // trace_fmt!("[IDLE]"); if is_start { let status = ZstdDecoderStatus::RUNNING; @@ -359,7 +394,7 @@ proc ZstdDecoderInternal< }, Fsm::READ_CONFIG => { - trace_fmt!("[READ_CONFIG]"); + // trace_fmt!("[READ_CONFIG]"); let is_input_buffer_csr = (csr_data.csr == csr(Csr::INPUT_BUFFER)); let input_buffer = if csr_data_valid && is_input_buffer_csr { checked_cast(csr_data.value) } else { state.input_buffer }; let input_buffer_valid = if csr_data_valid && is_input_buffer_csr { true } else { state.input_buffer_valid }; @@ -392,7 +427,7 @@ proc ZstdDecoderInternal< }, Fsm::DECODE_FRAME_HEADER => { - trace_fmt!("[DECODE_FRAME_HEADER]"); + // trace_fmt!("[DECODE_FRAME_HEADER]"); let error = (fh_resp.status != FrameHeaderDecoderStatus::OKAY); let status = match(fh_resp_valid, fh_resp.status) { @@ -420,7 +455,7 @@ proc ZstdDecoderInternal< }, Fsm::DECODE_BLOCK_HEADER => { - trace_fmt!("[DECODE_BLOCK_HEADER]"); + // trace_fmt!("[DECODE_BLOCK_HEADER]"); let error = (bh_resp.status != BlockHeaderDecoderStatus::OKAY); let status = match(bh_resp_valid, bh_resp.status) { @@ -439,7 +474,7 @@ proc ZstdDecoderInternal< let fsm = match (bh_resp_valid, error, bh_resp.header.btype) { ( true, false, BlockType::RAW ) => Fsm::DECODE_RAW_BLOCK, ( true, false, BlockType::RLE ) => Fsm::DECODE_RLE_BLOCK, - ( true, false, BlockType::COMPRESSED) => Fsm::ERROR, + ( true, false, BlockType::COMPRESSED) => Fsm::DECODE_COMPRESSED_BLOCK, ( true, true, _) => Fsm::ERROR, ( _, _, _) => Fsm::DECODE_BLOCK_HEADER, }; @@ -471,7 +506,7 @@ proc ZstdDecoderInternal< }, Fsm::DECODE_RAW_BLOCK => { - trace_fmt!("[DECODE_RAW_BLOCK]"); + // trace_fmt!("[DECODE_RAW_BLOCK]"); let error = (raw_resp.status != RawBlockDecoderStatus::OKAY); @@ -506,7 +541,7 @@ proc ZstdDecoderInternal< }, Fsm::DECODE_RLE_BLOCK => { - trace_fmt!("[DECODE_RLE_BLOCK]"); + // trace_fmt!("[DECODE_RLE_BLOCK]"); let error = (rle_resp.status != RleBlockDecoderStatus::OKAY); let status = match(rle_resp_valid, rle_resp.status) { @@ -538,14 +573,47 @@ proc ZstdDecoderInternal< state }, + Fsm::DECODE_COMPRESSED_BLOCK => { + // trace_fmt!("[DECODE_COMPRESSED_BLOCK]"); + let error = (cmp_resp.status != CompressBlockDecoderStatus::OK); + + let status = match(cmp_resp_valid, cmp_resp.status) { + (true, CompressBlockDecoderStatus::OK) => ZstdDecoderStatus::CMP_BLOCK_OK, + (_, _) => ZstdDecoderStatus::RUNNING, + }; + + let csr_wr_req_valid = (cmp_resp_valid); + let csr_wr_req = CsrWrReq { + csr: csr(Csr::STATUS), + value: status as Data, + }; + + let fsm = match (cmp_resp_valid, error, state.block_last) { + (true, false, false) => Fsm::DECODE_BLOCK_HEADER, + (true, false, true) => Fsm::DECODE_CHECKSUM, + (true, true, _) => Fsm::ERROR, + ( _, _, _) => Fsm::DECODE_COMPRESSED_BLOCK, + }; + + let req_sent = if !raw_resp_valid && !error { true } else { false }; + let block_id = if raw_resp_valid { state.block_id + u32:1} else {state.block_id }; + + let state = State {fsm, block_id, csr_wr_req, csr_wr_req_valid, req_sent, ..state}; + if fsm == Fsm::DECODE_BLOCK_HEADER { + trace_fmt!("Going to decode block header: {:#x}", state); + } else {}; + + state + }, + Fsm::DECODE_CHECKSUM => { - trace_fmt!("[DECODE_CHECKSUM]"); + // trace_fmt!("[DECODE_CHECKSUM]"); State {fsm: Fsm::WRITE_OUTPUT, ..zero!() } }, Fsm::WRITE_OUTPUT => { - trace_fmt!("[WRITE_OUTPUT]"); + // trace_fmt!("[WRITE_OUTPUT]"); let error = (output_write_resp.status != mem_writer::MemWriterRespStatus::OKAY); let fsm = match (output_write_done, error) { (true, false) => Fsm::FINISH, @@ -557,12 +625,12 @@ proc ZstdDecoderInternal< }, Fsm::ERROR => { - trace_fmt!("[ERROR]"); + // trace_fmt!("[ERROR]"); State { fsm: Fsm::IDLE, ..zero!() } }, Fsm::FINISH => { - trace_fmt!("[FINISH]"); + // trace_fmt!("[FINISH]"); let csr_wr_req_valid = true; let csr_wr_req = CsrWrReq { csr: csr(Csr::STATUS), @@ -620,6 +688,10 @@ proc ZstdDecoderInternalTest { type RleBlockDecoderResp = rle_block_dec::RleBlockDecoderResp; type RleBlockDecoderStatus = rle_block_dec::RleBlockDecoderStatus; + type CompressBlockDecoderReq = comp_block_dec::CompressBlockDecoderReq; + type CompressBlockDecoderResp = comp_block_dec::CompressBlockDecoderResp; + type CompressBlockDecoderStatus = comp_block_dec::CompressBlockDecoderStatus; + type MemWriterReq = mem_writer::MemWriterReq; type MemWriterResp = mem_writer::MemWriterResp; @@ -643,6 +715,9 @@ proc ZstdDecoderInternalTest { rle_req_r: chan in; rle_resp_s: chan out; + comp_block_req_r: chan in; + comp_block_resp_s: chan out; + output_mem_wr_req_r: chan in; output_mem_wr_resp_s: chan out; @@ -670,6 +745,9 @@ proc ZstdDecoderInternalTest { let (rle_req_s, rle_req_r) = chan("rle_req"); let (rle_resp_s, rle_resp_r) = chan("rle_resp"); + let (comp_block_req_s, comp_block_req_r) = chan("comp_block_req"); + let (comp_block_resp_s, comp_block_resp_r) = chan("comp_block_resp"); + let (output_mem_wr_req_s, output_mem_wr_req_r) = chan("output_mem_wr_req"); let (output_mem_wr_resp_s, output_mem_wr_resp_r) = chan("output_mem_wr_resp"); @@ -682,6 +760,7 @@ proc ZstdDecoderInternalTest { bh_req_s, bh_resp_r, raw_req_s, raw_resp_r, rle_req_s, rle_resp_r, + comp_block_req_s, comp_block_resp_r, output_mem_wr_req_s, output_mem_wr_resp_r, notify_s, reset_s, ); @@ -693,6 +772,7 @@ proc ZstdDecoderInternalTest { bh_req_r, bh_resp_s, raw_req_r, raw_resp_s, rle_req_r, rle_resp_s, + comp_block_req_r, comp_block_resp_s, output_mem_wr_req_r, output_mem_wr_resp_s, notify_r, reset_r, ) @@ -865,11 +945,32 @@ pub proc ZstdDecoder< // decoder parameters REGS_N: u32, WINDOW_LOG_MAX: u32, HB_ADDR_W: u32, HB_DATA_W: u32, HB_NUM_PARTITIONS: u32, HB_SIZE_KB: u32, + + DPD_RAM_ADDR_W: u32, DPD_RAM_DATA_W: u32, DPD_RAM_NUM_PARTITIONS: u32, + TMP_RAM_ADDR_W: u32, TMP_RAM_DATA_W: u32, TMP_RAM_NUM_PARTITIONS: u32, + FSE_RAM_ADDR_W: u32, FSE_RAM_DATA_W: u32, FSE_RAM_NUM_PARTITIONS: u32, + + HISTORY_BUFFER_SIZE_KB: u32, + AXI_CHAN_N: u32, + // calculated parameters AXI_DATA_W_DIV8: u32 = {AXI_DATA_W / u32:8}, LOG2_REGS_N: u32 = {std::clog2(REGS_N)}, HB_RAM_N: u32 = {u32:8}, MEM_WRITER_ID: u32 = {u32:0}, + HUFFMAN_WEIGHTS_RAM_ADDR_W: u32 = {huffman_literals_dec::WEIGHTS_ADDR_WIDTH}, + HUFFMAN_WEIGHTS_RAM_DATA_W: u32 = {huffman_literals_dec::WEIGHTS_DATA_WIDTH}, + HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS: u32 = {huffman_literals_dec::WEIGHTS_NUM_PARTITIONS}, + // Huffman prescan memory parameters + HUFFMAN_PRESCAN_RAM_ADDR_W: u32 = {huffman_literals_dec::PRESCAN_ADDR_WIDTH}, + HUFFMAN_PRESCAN_RAM_DATA_W: u32 = {huffman_literals_dec::PRESCAN_DATA_WIDTH}, + HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS: u32 = {huffman_literals_dec::PRESCAN_NUM_PARTITIONS}, + // Literals buffer memory parameters + LITERALS_BUFFER_RAM_ADDR_W: u32 = {parallel_rams::ram_addr_width(HISTORY_BUFFER_SIZE_KB)}, + LITERALS_BUFFER_RAM_SIZE: u32 = {parallel_rams::ram_size(HISTORY_BUFFER_SIZE_KB)}, + LITERALS_BUFFER_RAM_DATA_W: u32 = {literals_buffer::RAM_DATA_WIDTH}, + LITERALS_BUFFER_RAM_NUM_PARTITIONS: u32 = {literals_buffer::RAM_NUM_PARTITIONS}, + LITERALS_BUFFER_RAM_WORD_PARTITION_SIZE: u32 = {LITERALS_BUFFER_RAM_DATA_W}, > { type CsrAxiAr = axi::AxiAr; type CsrAxiR = axi::AxiR; @@ -916,8 +1017,44 @@ pub proc ZstdDecoder< type RamWrReq = ram::WriteReq; type RamWrResp = ram::WriteResp; + type CompressBlockDecoderReq = comp_block_dec::CompressBlockDecoderReq; + type CompressBlockDecoderResp = comp_block_dec::CompressBlockDecoderResp; + + type DpdRamRdReq = ram::ReadReq; + type DpdRamRdResp = ram::ReadResp; + type DpdRamWrReq = ram::WriteReq; + type DpdRamWrResp = ram::WriteResp; + + type TmpRamRdReq = ram::ReadReq; + type TmpRamRdResp = ram::ReadResp; + type TmpRamWrReq = ram::WriteReq; + type TmpRamWrResp = ram::WriteResp; + + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; + type FseRamWrReq = ram::WriteReq; + type FseRamWrResp = ram::WriteResp; + + type CommandConstructorData = common::CommandConstructorData; + + type HuffmanWeightsReadReq = ram::ReadReq; + type HuffmanWeightsReadResp = ram::ReadResp; + type HuffmanWeightsWriteReq = ram::WriteReq; + type HuffmanWeightsWriteResp = ram::WriteResp; + type HuffmanPrescanReadReq = ram::ReadReq; + type HuffmanPrescanReadResp = ram::ReadResp; + type HuffmanPrescanWriteReq = ram::WriteReq; + type HuffmanPrescanWriteResp = ram::WriteResp; + + type LitBufRamRdReq = ram::ReadReq; + type LitBufRamRdResp = ram::ReadResp; + type LitBufRamWrReq = ram::WriteReq; + type LitBufRamWrResp = ram::WriteResp; + // Complex Block Decoder cmp_output_s: chan out; + comp_block_req_s: chan out; + comp_block_resp_r: chan in; init {} @@ -933,14 +1070,50 @@ pub proc ZstdDecoder< fh_axi_ar_s: chan out, fh_axi_r_r: chan in, - //// AXI Block Header Decoder (manager) + // AXI Block Header Decoder (manager) bh_axi_ar_s: chan out, bh_axi_r_r: chan in, - //// AXI RAW Block Decoder (manager) + // AXI RAW Block Decoder (manager) raw_axi_ar_s: chan out, raw_axi_r_r: chan in, + // AXI Compressed Block Decoder + comp_axi_ram_ar_s: chan[AXI_CHAN_N] out, + comp_axi_ram_r_r: chan[AXI_CHAN_N] in, + + dpd_rd_req_s: chan out, + dpd_rd_resp_r: chan in, + dpd_wr_req_s: chan out, + dpd_wr_resp_r: chan in, + + tmp_rd_req_s: chan out, + tmp_rd_resp_r: chan in, + tmp_wr_req_s: chan out, + tmp_wr_resp_r: chan in, + + fse_rd_req_s: chan[u32:6] out, + fse_rd_resp_r: chan[u32:6] in, + fse_wr_req_s: chan[u32:6] out, + fse_wr_resp_r: chan[u32:6] in, + + litbuf_rd_req_s: chan[u32:8] out, + litbuf_rd_resp_r: chan[u32:8] in, + litbuf_wr_req_s: chan[u32:8] out, + litbuf_wr_resp_r: chan[u32:8] in, + + // Huffman weights memory + huffman_lit_weights_mem_rd_req_s: chan out, + huffman_lit_weights_mem_rd_resp_r: chan in, + huffman_lit_weights_mem_wr_req_s: chan out, + huffman_lit_weights_mem_wr_resp_r: chan in, + + // Huffman prescan memory + huffman_lit_prescan_mem_rd_req_s: chan out, + huffman_lit_prescan_mem_rd_resp_r: chan in, + huffman_lit_prescan_mem_wr_req_s: chan out, + huffman_lit_prescan_mem_wr_resp_r: chan in, + //// AXI Output Writer (manager) output_axi_aw_s: chan out, output_axi_w_s: chan out, @@ -1079,13 +1252,70 @@ pub proc ZstdDecoder< rle_req_r, rle_resp_s, rle_output_s ); + // Compressed block decoder + + let (comp_block_req_s, comp_block_req_r) = chan("comp_block_req"); + let (comp_block_resp_s, comp_block_resp_r) = chan("comp_block_resp"); + + let (cmd_output_s, cmd_output_r) = chan("cmd_output"); + + spawn comp_block_dec::CompressBlockDecoder< + AXI_DATA_W, AXI_ADDR_W, AXI_ID_W, AXI_DEST_W, + // FSE lookup table RAMs + DPD_RAM_ADDR_W, DPD_RAM_DATA_W, DPD_RAM_NUM_PARTITIONS, + TMP_RAM_ADDR_W, TMP_RAM_DATA_W, TMP_RAM_NUM_PARTITIONS, + FSE_RAM_ADDR_W, FSE_RAM_DATA_W, FSE_RAM_NUM_PARTITIONS, + >( + // MAIN IOs + + comp_block_req_r, comp_block_resp_s, + cmd_output_s, + + // SEQUENCE DECODING + + // axi channels for sequence decoding + comp_axi_ram_ar_s[0], comp_axi_ram_r_r[0], + comp_axi_ram_ar_s[1], comp_axi_ram_r_r[1], + comp_axi_ram_ar_s[2], comp_axi_ram_r_r[2], + dpd_rd_req_s, dpd_rd_resp_r, dpd_wr_req_s, dpd_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + fse_rd_req_s[0], fse_rd_resp_r[0], fse_wr_req_s[0], fse_wr_resp_r[0], + fse_rd_req_s[1], fse_rd_resp_r[1], fse_wr_req_s[1], fse_wr_resp_r[1], + fse_rd_req_s[2], fse_rd_resp_r[2], fse_wr_req_s[2], fse_wr_resp_r[2], + fse_rd_req_s[3], fse_rd_resp_r[3], fse_wr_req_s[3], fse_wr_resp_r[3], + fse_rd_req_s[4], fse_rd_resp_r[4], fse_wr_req_s[4], fse_wr_resp_r[4], + fse_rd_req_s[5], fse_rd_resp_r[5], fse_wr_req_s[5], fse_wr_resp_r[5], + + // LITERALS DECODING + + // axi channels for literals decoding + comp_axi_ram_ar_s[3], comp_axi_ram_r_r[3], + comp_axi_ram_ar_s[4], comp_axi_ram_r_r[4], + comp_axi_ram_ar_s[5], comp_axi_ram_r_r[5], + comp_axi_ram_ar_s[6], comp_axi_ram_r_r[6], + comp_axi_ram_ar_s[7], comp_axi_ram_r_r[7], + comp_axi_ram_ar_s[8], comp_axi_ram_r_r[8], + comp_axi_ram_ar_s[9], comp_axi_ram_r_r[9], + litbuf_rd_req_s[0], litbuf_rd_req_s[1], litbuf_rd_req_s[2], litbuf_rd_req_s[3], + litbuf_rd_req_s[4], litbuf_rd_req_s[5], litbuf_rd_req_s[6], litbuf_rd_req_s[7], + litbuf_rd_resp_r[0], litbuf_rd_resp_r[1], litbuf_rd_resp_r[2], litbuf_rd_resp_r[3], + litbuf_rd_resp_r[4], litbuf_rd_resp_r[5], litbuf_rd_resp_r[6], litbuf_rd_resp_r[7], + litbuf_wr_req_s[0], litbuf_wr_req_s[1], litbuf_wr_req_s[2], litbuf_wr_req_s[3], + litbuf_wr_req_s[4], litbuf_wr_req_s[5], litbuf_wr_req_s[6], litbuf_wr_req_s[7], + litbuf_wr_resp_r[0], litbuf_wr_resp_r[1], litbuf_wr_resp_r[2], litbuf_wr_resp_r[3], + litbuf_wr_resp_r[4], litbuf_wr_resp_r[5], litbuf_wr_resp_r[6], litbuf_wr_resp_r[7], + huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_resp_r, + huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_resp_r, + huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_resp_r, + huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_resp_r, + ); + // Collecting Packets - let (cmp_output_s, cmp_output_r) = chan("cmp_output"); let (seq_exec_input_s, seq_exec_input_r) = chan("demux_output"); spawn dec_mux::DecoderMux( - raw_output_r, rle_output_r, cmp_output_r, + raw_output_r, rle_output_r, cmd_output_r, seq_exec_input_s, ); @@ -1121,11 +1351,40 @@ pub proc ZstdDecoder< bh_req_s, bh_resp_r, raw_req_s, raw_resp_r, rle_req_s, rle_resp_r, + comp_block_req_s, comp_block_resp_r, output_mem_wr_req_s, output_mem_wr_resp_r, notify_s, reset_s, ); - (cmp_output_s,) + + // // Sequence config (section header) decoder's memory reader + // let (scd_mem_rd_req_s, scd_mem_rd_req_r) = chan("scd_mem_rd_req"); + // let (scd_mem_rd_resp_s, scd_mem_rd_resp_r) = chan("scd_mem_rd_resp"); + + // spawn mem_reader::MemReader( + // scd_mem_rd_req_r, scd_mem_rd_resp_s, + // scd_axi_ar_s, scd_axi_r_r, + // ); + + // // FSE lookup decoder's memory reader for RefillingShiftBuffer + // let (fse_lookup_mem_rd_req_s, fse_lookup_mem_rd_req_r) = chan("fse_lookup_mem_rd_req"); + // let (fse_lookup_mem_rd_resp_s, fse_lookup_mem_rd_resp_r) = chan("fse_lookup_mem_rd_resp"); + + // spawn mem_reader::MemReader( + // fse_lookup_mem_rd_req_r, fse_lookup_mem_rd_resp_s, + // fse_lookup_axi_ar_s, fse_lookup_axi_r_r, + // ); + + // // FSE decoder's memory reader for RefillingShiftBuffer + // let (fse_dec_mem_rd_req_s, fse_dec_mem_rd_req_r) = chan("scd_mem_rd_req"); + // let (fse_dec_mem_rd_resp_s, fse_dec_mem_rd_resp_r) = chan("scd_mem_rd_resp"); + + // spawn mem_reader::MemReader( + // fse_dec_mem_rd_req_r, fse_dec_mem_rd_resp_s, + // fse_dec_axi_ar_s, fse_dec_axi_r_r, + // ); + + (cmd_output_s, comp_block_req_s, comp_block_resp_r) } next (state: ()) { @@ -1148,6 +1407,46 @@ const INST_LOG2_REGS_N = std::clog2(INST_REGS_N); const INST_AXI_DATA_W_DIV8 = INST_AXI_DATA_W / u32:8; const INST_HB_RAM_N = u32:8; +const INST_DPD_RAM_DATA_W = u32:16; +const INST_DPD_RAM_SIZE = u32:256; +const INST_DPD_RAM_ADDR_W = std::clog2(INST_DPD_RAM_SIZE); +const INST_DPD_RAM_WORD_PARTITION_SIZE = INST_DPD_RAM_DATA_W; +const INST_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( + INST_DPD_RAM_WORD_PARTITION_SIZE, INST_DPD_RAM_DATA_W); + +const INST_FSE_RAM_DATA_W = u32:32; +const INST_FSE_RAM_SIZE = u32:256; +const INST_FSE_RAM_ADDR_W = std::clog2(INST_FSE_RAM_SIZE); +const INST_FSE_RAM_WORD_PARTITION_SIZE = INST_FSE_RAM_DATA_W / u32:3; +const INST_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( + INST_FSE_RAM_WORD_PARTITION_SIZE, INST_FSE_RAM_DATA_W); + +const INST_TMP_RAM_DATA_W = u32:16; +const INST_TMP_RAM_SIZE = u32:256; +const INST_TMP_RAM_ADDR_W = std::clog2(INST_TMP_RAM_SIZE); +const INST_TMP_RAM_WORD_PARTITION_SIZE = INST_TMP_RAM_DATA_W; +const INST_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( + INST_TMP_RAM_WORD_PARTITION_SIZE, INST_TMP_RAM_DATA_W); + +const HUFFMAN_WEIGHTS_RAM_ADDR_W: u32 = huffman_literals_dec::WEIGHTS_ADDR_WIDTH; +const HUFFMAN_WEIGHTS_RAM_DATA_W: u32 = huffman_literals_dec::WEIGHTS_DATA_WIDTH; +const HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::WEIGHTS_NUM_PARTITIONS; +// Huffman prescan memory parameters +const HUFFMAN_PRESCAN_RAM_ADDR_W: u32 = huffman_literals_dec::PRESCAN_ADDR_WIDTH; +const HUFFMAN_PRESCAN_RAM_DATA_W: u32 = huffman_literals_dec::PRESCAN_DATA_WIDTH; +const HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::PRESCAN_NUM_PARTITIONS; + +const INST_HISTORY_BUFFER_SIZE_KB = u32:64; +const INST_AXI_CHAN_N = u32:10; + +// Literals buffer memory parameters +const LITERALS_BUFFER_RAM_ADDR_W: u32 = parallel_rams::ram_addr_width(INST_HISTORY_BUFFER_SIZE_KB); +const LITERALS_BUFFER_RAM_SIZE: u32 = parallel_rams::ram_size(INST_HISTORY_BUFFER_SIZE_KB); +const LITERALS_BUFFER_RAM_DATA_W: u32 = literals_buffer::RAM_DATA_WIDTH; +const LITERALS_BUFFER_RAM_NUM_PARTITIONS: u32 = literals_buffer::RAM_NUM_PARTITIONS; +const LITERALS_BUFFER_RAM_WORD_PARTITION_SIZE: u32 = LITERALS_BUFFER_RAM_DATA_W; + + proc ZstdDecoderInternalInst { type State = ZstdDecoderInternalState; type Fsm = ZstdDecoderInternalFsm; @@ -1170,6 +1469,9 @@ proc ZstdDecoderInternalInst { type RleBlockDecoderReq = rle_block_dec::RleBlockDecoderReq; type RleBlockDecoderResp = rle_block_dec::RleBlockDecoderResp; + type CompressBlockDecoderReq = comp_block_dec::CompressBlockDecoderReq; + type CompressBlockDecoderResp = comp_block_dec::CompressBlockDecoderResp; + type MemWriterReq = mem_writer::MemWriterReq; type MemWriterResp = mem_writer::MemWriterResp; @@ -1198,6 +1500,9 @@ proc ZstdDecoderInternalInst { rle_req_s: chan out, rle_resp_r: chan in, + comp_req_s: chan out, + comp_resp_r: chan in, + // Output MemWriter output_mem_wr_req_s: chan out, output_mem_wr_resp_r: chan in, @@ -1214,6 +1519,7 @@ proc ZstdDecoderInternalInst { bh_req_s, bh_resp_r, raw_req_s, raw_resp_r, rle_req_s, rle_resp_r, + comp_req_s, comp_resp_r, output_mem_wr_req_s, output_mem_wr_resp_r, notify_s, reset_s, ); @@ -1243,6 +1549,35 @@ proc ZstdDecoderInst { type ZstdDecodedPacket = common::ZstdDecodedPacket; + type DpdRamRdReq = ram::ReadReq; + type DpdRamRdResp = ram::ReadResp; + type DpdRamWrReq = ram::WriteReq; + type DpdRamWrResp = ram::WriteResp; + + type TmpRamRdReq = ram::ReadReq; + type TmpRamRdResp = ram::ReadResp; + type TmpRamWrReq = ram::WriteReq; + type TmpRamWrResp = ram::WriteResp; + + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; + type FseRamWrReq = ram::WriteReq; + type FseRamWrResp = ram::WriteResp; + + type LitBufRamRdReq = ram::ReadReq; + type LitBufRamRdResp = ram::ReadResp; + type LitBufRamWrReq = ram::WriteReq; + type LitBufRamWrResp = ram::WriteResp; + + type HuffmanWeightsReadReq = ram::ReadReq; + type HuffmanWeightsReadResp = ram::ReadResp; + type HuffmanWeightsWriteReq = ram::WriteReq; + type HuffmanWeightsWriteResp = ram::WriteResp; + type HuffmanPrescanReadReq = ram::ReadReq; + type HuffmanPrescanReadResp = ram::ReadResp; + type HuffmanPrescanWriteReq = ram::WriteReq; + type HuffmanPrescanWriteResp = ram::WriteResp; + init { } config( @@ -1257,14 +1592,49 @@ proc ZstdDecoderInst { fh_axi_ar_s: chan out, fh_axi_r_r: chan in, - // AXI Block Header Decoder (manager) + //// AXI Block Header Decoder (manager) bh_axi_ar_s: chan out, bh_axi_r_r: chan in, - // AXI RAW Block Decoder (manager) + //// AXI RAW Block Decoder (manager) raw_axi_ar_s: chan out, raw_axi_r_r: chan in, + axi_ram_ar_s: chan[INST_AXI_CHAN_N] out, + axi_ram_r_r: chan[INST_AXI_CHAN_N] in, + + dpd_rd_req_s: chan out, + dpd_rd_resp_r: chan in, + dpd_wr_req_s: chan out, + dpd_wr_resp_r: chan in, + + tmp_rd_req_s: chan out, + tmp_rd_resp_r: chan in, + tmp_wr_req_s: chan out, + tmp_wr_resp_r: chan in, + + fse_rd_req_s: chan[u32:6] out, + fse_rd_resp_r: chan[u32:6] in, + fse_wr_req_s: chan[u32:6] out, + fse_wr_resp_r: chan[u32:6] in, + + litbuf_rd_req_s: chan[u32:8] out, + litbuf_rd_resp_r: chan[u32:8] in, + litbuf_wr_req_s: chan[u32:8] out, + litbuf_wr_resp_r: chan[u32:8] in, + + // Huffman weights memory + huffman_lit_weights_mem_rd_req_s: chan out, + huffman_lit_weights_mem_rd_resp_r: chan in, + huffman_lit_weights_mem_wr_req_s: chan out, + huffman_lit_weights_mem_wr_resp_r: chan in, + + // Huffman prescan memory + huffman_lit_prescan_mem_rd_req_s: chan out, + huffman_lit_prescan_mem_rd_resp_r: chan in, + huffman_lit_prescan_mem_wr_req_s: chan out, + huffman_lit_prescan_mem_wr_resp_r: chan in, + //// AXI Output Writer (manager) output_axi_aw_s: chan out, output_axi_w_s: chan out, @@ -1311,11 +1681,29 @@ proc ZstdDecoderInst { INST_AXI_DATA_W, INST_AXI_ADDR_W, INST_AXI_ID_W, INST_AXI_DEST_W, INST_REGS_N, INST_WINDOW_LOG_MAX, INST_HB_ADDR_W, INST_HB_DATA_W, INST_HB_NUM_PARTITIONS, INST_HB_SIZE_KB, + INST_DPD_RAM_ADDR_W, INST_DPD_RAM_DATA_W, INST_DPD_RAM_NUM_PARTITIONS, + INST_TMP_RAM_ADDR_W, INST_TMP_RAM_DATA_W, INST_TMP_RAM_NUM_PARTITIONS, + INST_FSE_RAM_ADDR_W, INST_FSE_RAM_DATA_W, INST_FSE_RAM_NUM_PARTITIONS, + INST_HISTORY_BUFFER_SIZE_KB, + INST_AXI_CHAN_N, >( csr_axi_aw_r, csr_axi_w_r, csr_axi_b_s, csr_axi_ar_r, csr_axi_r_s, fh_axi_ar_s, fh_axi_r_r, bh_axi_ar_s, bh_axi_r_r, raw_axi_ar_s, raw_axi_r_r, + axi_ram_ar_s, axi_ram_r_r, + dpd_rd_req_s, dpd_rd_resp_r, + dpd_wr_req_s, dpd_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, + tmp_wr_req_s, tmp_wr_resp_r, + fse_rd_req_s, fse_rd_resp_r, + fse_wr_req_s, fse_wr_resp_r, + litbuf_rd_req_s, litbuf_rd_resp_r, + litbuf_wr_req_s, litbuf_wr_resp_r, + huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_resp_r, + huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_resp_r, + huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_resp_r, + huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_resp_r, output_axi_aw_s, output_axi_w_s, output_axi_b_r, 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, diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x index bd90210ef1..a115e1f83c 100644 --- a/xls/modules/zstd/zstd_dec_test.x +++ b/xls/modules/zstd/zstd_dec_test.x @@ -18,9 +18,18 @@ import xls.modules.zstd.common; import xls.modules.zstd.memory.axi; import xls.modules.zstd.csr_config; import xls.modules.zstd.sequence_executor; -import xls.modules.zstd.zstd_frame_testcases; +// import xls.modules.zstd.zstd_frame_testcases; import xls.modules.zstd.memory.axi_ram; import xls.modules.zstd.zstd_dec; +import xls.modules.zstd.comp_block_dec; +import xls.modules.zstd.sequence_dec; +import xls.modules.zstd.memory.mem_reader; +import xls.modules.zstd.huffman_literals_dec; +import xls.modules.zstd.parallel_rams; +import xls.modules.zstd.literals_buffer; +import xls.modules.zstd.fse_table_creator; +import xls.modules.zstd.ram_mux; +import xls.modules.zstd.comp_frame; const TEST_WINDOW_LOG_MAX = u32:30; @@ -45,7 +54,7 @@ const TEST_HB_RAM_INITIALIZED = sequence_executor::TEST_RAM_INITIALIZED; const TEST_HB_RAM_ASSERT_VALID_READ:bool = false; const TEST_RAM_DATA_W:u32 = TEST_AXI_DATA_W; -const TEST_RAM_SIZE:u32 = u32:16384; +const TEST_RAM_SIZE:u32 = u32:512; const TEST_RAM_ADDR_W:u32 = std::clog2(TEST_RAM_SIZE); const TEST_RAM_WORD_PARTITION_SIZE:u32 = u32:8; const TEST_RAM_NUM_PARTITIONS:u32 = ram::num_partitions(TEST_RAM_WORD_PARTITION_SIZE, TEST_RAM_DATA_W); @@ -53,7 +62,47 @@ const TEST_RAM_BASE_ADDR:u32 = u32:0; const TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; const TEST_RAM_INITIALIZED = true; -const TEST_MOCK_OUTPUT_RAM_SIZE:u32 = TEST_RAM_SIZE / TEST_AXI_DATA_W_DIV8; +const TEST_DPD_RAM_DATA_W = u32:16; +const TEST_DPD_RAM_SIZE = u32:256; +const TEST_DPD_RAM_ADDR_W = std::clog2(TEST_DPD_RAM_SIZE); +const TEST_DPD_RAM_WORD_PARTITION_SIZE = TEST_DPD_RAM_DATA_W; +const TEST_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_DPD_RAM_WORD_PARTITION_SIZE, TEST_DPD_RAM_DATA_W); + +const TEST_FSE_RAM_DATA_W = u32:32; +const TEST_FSE_RAM_SIZE = u32:256; +const TEST_FSE_RAM_ADDR_W = std::clog2(TEST_FSE_RAM_SIZE); +const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_W / u32:3; +const TEST_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_FSE_RAM_WORD_PARTITION_SIZE, TEST_FSE_RAM_DATA_W); + +const TEST_TMP_RAM_DATA_W = u32:16; +const TEST_TMP_RAM_SIZE = u32:256; +const TEST_TMP_RAM_ADDR_W = std::clog2(TEST_TMP_RAM_SIZE); +const TEST_TMP_RAM_WORD_PARTITION_SIZE = TEST_TMP_RAM_DATA_W; +const TEST_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_TMP_RAM_WORD_PARTITION_SIZE, TEST_TMP_RAM_DATA_W); + +const HUFFMAN_WEIGHTS_RAM_ADDR_W: u32 = huffman_literals_dec::WEIGHTS_ADDR_WIDTH; +const HUFFMAN_WEIGHTS_RAM_DATA_W: u32 = huffman_literals_dec::WEIGHTS_DATA_WIDTH; +const HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::WEIGHTS_NUM_PARTITIONS; +// Huffman prescan memory parameters +const HUFFMAN_PRESCAN_RAM_ADDR_W: u32 = huffman_literals_dec::PRESCAN_ADDR_WIDTH; +const HUFFMAN_PRESCAN_RAM_DATA_W: u32 = huffman_literals_dec::PRESCAN_DATA_WIDTH; +const HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::PRESCAN_NUM_PARTITIONS; + +const HISTORY_BUFFER_SIZE_KB = common::HISTORY_BUFFER_SIZE_KB; + +// Literals buffer memory parameters +const LITERALS_BUFFER_RAM_ADDR_W: u32 = parallel_rams::ram_addr_width(HISTORY_BUFFER_SIZE_KB); +const LITERALS_BUFFER_RAM_SIZE: u32 = parallel_rams::ram_size(HISTORY_BUFFER_SIZE_KB); +const LITERALS_BUFFER_RAM_DATA_W: u32 = literals_buffer::RAM_DATA_WIDTH; +const LITERALS_BUFFER_RAM_NUM_PARTITIONS: u32 = literals_buffer::RAM_NUM_PARTITIONS; +const LITERALS_BUFFER_RAM_WORD_PARTITION_SIZE: u32 = LITERALS_BUFFER_RAM_DATA_W; + +const AXI_CHAN_N = u32:10; + +const TEST_MOCK_OUTPUT_RAM_SIZE:u32 = TEST_RAM_SIZE; fn csr_addr(c: zstd_dec::Csr) -> uN[TEST_AXI_ADDR_W] { (c as uN[TEST_AXI_ADDR_W]) << 3 @@ -90,33 +139,106 @@ proc ZstdDecoderTest { type RamWrResp = ram::WriteResp; type ZstdDecodedPacket = common::ZstdDecodedPacket; + + type Req = comp_block_dec::CompressBlockDecoderReq; + type Resp = comp_block_dec::CompressBlockDecoderResp; + + type SequenceDecReq = sequence_dec::SequenceDecoderReq; + type SequenceDecResp = sequence_dec::SequenceDecoderResp; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + + type MemAxiAr = axi::AxiAr; + type MemAxiR = axi::AxiR; + type MemAxiAw = axi::AxiAw; + type MemAxiW = axi::AxiW; + type MemAxiB = axi::AxiB; + + type DpdRamRdReq = ram::ReadReq; + type DpdRamRdResp = ram::ReadResp; + type DpdRamWrReq = ram::WriteReq; + type DpdRamWrResp = ram::WriteResp; + + type TmpRamRdReq = ram::ReadReq; + type TmpRamRdResp = ram::ReadResp; + type TmpRamWrReq = ram::WriteReq; + type TmpRamWrResp = ram::WriteResp; + + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; + type FseRamWrReq = ram::WriteReq; + type FseRamWrResp = ram::WriteResp; + + type SequenceExecutorPacket = common::SequenceExecutorPacket; + type CommandConstructorData = common::CommandConstructorData; + + type HuffmanWeightsReadReq = ram::ReadReq; + type HuffmanWeightsReadResp = ram::ReadResp; + type HuffmanWeightsWriteReq = ram::WriteReq; + type HuffmanWeightsWriteResp = ram::WriteResp; + type HuffmanPrescanReadReq = ram::ReadReq; + type HuffmanPrescanReadResp = ram::ReadResp; + type HuffmanPrescanWriteReq = ram::WriteReq; + type HuffmanPrescanWriteResp = ram::WriteResp; + + type LitBufRamRdReq = ram::ReadReq; + type LitBufRamRdResp = ram::ReadResp; + type LitBufRamWrReq = ram::WriteReq; + type LitBufRamWrResp = ram::WriteResp; + terminator: chan out; + csr_axi_aw_s: chan out; csr_axi_w_s: chan out; csr_axi_b_r: chan in; csr_axi_ar_s: chan out; csr_axi_r_r: chan in; + fh_axi_ar_r: chan in; fh_axi_r_s: chan out; + fh_ram_wr_req_s: chan out; + fh_raw_wr_resp_r: chan in; + bh_axi_ar_r: chan in; bh_axi_r_s: chan out; + bh_ram_wr_req_s: chan out; + bh_raw_wr_resp_r: chan in; + raw_axi_ar_r: chan in; raw_axi_r_s: chan out; + raw_ram_wr_req_s: chan out; + raw_raw_wr_resp_r: chan in; + + comp_ram_wr_req_s: chan[AXI_CHAN_N] out; + comp_ram_wr_resp_r: chan[AXI_CHAN_N] in; + output_axi_aw_r: chan in; output_axi_w_r: chan in; output_axi_b_s: chan out; - ram_rd_req_r: chan[8] in; - ram_rd_resp_s: chan[8] out; - ram_wr_req_r: chan[8] in; - ram_wr_resp_s: chan[8] out; - - ram_wr_req_fh_s: chan out; - ram_wr_req_bh_s: chan out; - ram_wr_req_raw_s: chan out; - raw_wr_resp_fh_r: chan in; - raw_wr_resp_bh_r: chan in; - raw_wr_resp_raw_r: chan in; + hb_ram_rd_req_r: chan[8] in; + hb_ram_rd_resp_s: chan[8] out; + hb_ram_wr_req_r: chan[8] in; + hb_ram_wr_resp_s: chan[8] out; + + ll_sel_test_s: chan out; + ll_def_test_rd_req_s: chan out; + ll_def_test_rd_resp_r: chan in; + ll_def_test_wr_req_s: chan out; + ll_def_test_wr_resp_r: chan in; + + ml_sel_test_s: chan out; + ml_def_test_rd_req_s: chan out; + ml_def_test_rd_resp_r: chan in; + ml_def_test_wr_req_s: chan out; + ml_def_test_wr_resp_r: chan in; + + of_sel_test_s: chan out; + of_def_test_rd_req_s: chan out; + of_def_test_rd_resp_r: chan in; + of_def_test_wr_req_s: chan out; + of_def_test_wr_resp_r: chan in; notify_r: chan<()> in; reset_r: chan<()> in; @@ -133,170 +255,365 @@ proc ZstdDecoderTest { let (fh_axi_ar_s, fh_axi_ar_r) = chan("fh_axi_ar"); let (fh_axi_r_s, fh_axi_r_r) = chan("fh_axi_r"); + let (fh_ram_wr_req_s, fh_ram_wr_req_r) = chan("fh_ram_wr_req"); + let (fh_ram_wr_resp_s, fh_ram_wr_resp_r) = chan("fh_ram_wr_resp"); + let (fh_ram_rd_req_s, fh_ram_rd_req_r) = chan("fh_ram_rd_req"); + let (fh_ram_rd_resp_s, fh_ram_rd_resp_r) = chan("fh_ram_rd_resp"); let (bh_axi_ar_s, bh_axi_ar_r) = chan("bh_axi_ar"); let (bh_axi_r_s, bh_axi_r_r) = chan("bh_axi_r"); + let (bh_ram_rd_req_s, bh_ram_rd_req_r) = chan("bh_ram_rd_req"); + let (bh_ram_rd_resp_s, bh_ram_rd_resp_r) = chan("bh_ram_rd_resp"); + let (bh_ram_wr_req_s, bh_ram_wr_req_r) = chan("bh_ram_wr_req"); + let (bh_ram_wr_resp_s, bh_ram_wr_resp_r) = chan("bh_ram_wr_resp"); let (raw_axi_ar_s, raw_axi_ar_r) = chan("raw_axi_ar"); let (raw_axi_r_s, raw_axi_r_r) = chan("raw_axi_r"); + let (raw_ram_rd_req_s, raw_ram_rd_req_r) = chan("raw_ram_rd_req"); + let (raw_ram_rd_resp_s, raw_ram_rd_resp_r) = chan("raw_ram_rd_resp"); + let (raw_ram_wr_req_s, raw_ram_wr_req_r) = chan("raw_ram_wr_req"); + let (raw_ram_wr_resp_s, raw_ram_wr_resp_r) = chan("raw_ram_wr_resp"); let (output_axi_aw_s, output_axi_aw_r) = chan("output_axi_aw"); let (output_axi_w_s, output_axi_w_r) = chan("output_axi_w"); let (output_axi_b_s, output_axi_b_r) = chan("output_axi_b"); - let (ram_rd_req_s, ram_rd_req_r) = chan[8]("ram_rd_req"); - let (ram_rd_resp_s, ram_rd_resp_r) = chan[8]("ram_rd_resp"); - let (ram_wr_req_s, ram_wr_req_r) = chan[8]("ram_wr_req"); - let (ram_wr_resp_s, ram_wr_resp_r) = chan[8]("ram_wr_resp"); - - let (ram_rd_req_fh_s, ram_rd_req_fh_r) = chan("ram_rd_req_fh"); - let (ram_rd_req_bh_s, ram_rd_req_bh_r) = chan("ram_rd_req_bh"); - let (ram_rd_req_raw_s, ram_rd_req_raw_r) = chan("ram_rd_req_raw"); - let (ram_rd_resp_fh_s, ram_rd_resp_fh_r) = chan("ram_rd_resp_fh"); - let (ram_rd_resp_bh_s, ram_rd_resp_bh_r) = chan("ram_rd_resp_bh"); - let (ram_rd_resp_raw_s, ram_rd_resp_raw_r) = chan("ram_rd_resp_raw"); - - let (ram_wr_req_fh_s, ram_wr_req_fh_r) = chan("ram_wr_req_fh"); - let (ram_wr_req_bh_s, ram_wr_req_bh_r) = chan("ram_wr_req_bh"); - let (ram_wr_req_raw_s, ram_wr_req_raw_r) = chan("ram_wr_req_raw"); - let (ram_wr_resp_fh_s, ram_wr_resp_fh_r) = chan("ram_wr_resp_fh"); - let (ram_wr_resp_bh_s, ram_wr_resp_bh_r) = chan("ram_wr_resp_bh"); - let (ram_wr_resp_raw_s, ram_wr_resp_raw_r) = chan("ram_wr_resp_raw"); + let (hb_ram_rd_req_s, hb_ram_rd_req_r) = chan[8]("hb_ram_rd_req"); + let (hb_ram_rd_resp_s, hb_ram_rd_resp_r) = chan[8]("hb_ram_rd_resp"); + let (hb_ram_wr_req_s, hb_ram_wr_req_r) = chan[8]("hb_ram_wr_req"); + let (hb_ram_wr_resp_s, hb_ram_wr_resp_r) = chan[8]("hb_ram_wr_resp"); let (notify_s, notify_r) = chan<()>("notify"); let (reset_s, reset_r) = chan<()>("reset"); + // Huffman weights memory + let (huffman_lit_weights_mem_rd_req_s, _huffman_lit_weights_mem_rd_req_r) = chan("huffman_lit_weights_mem_rd_req"); + let (_huffman_lit_weights_mem_rd_resp_s, huffman_lit_weights_mem_rd_resp_r) = chan("huffman_lit_weights_mem_rd_resp"); + let (huffman_lit_weights_mem_wr_req_s, _huffman_lit_weights_mem_wr_req_r) = chan("huffman_lit_weights_mem_wr_req"); + let (_huffman_lit_weights_mem_wr_resp_s, huffman_lit_weights_mem_wr_resp_r) = chan("huffman_lit_weights_mem_wr_resp"); + + // Huffman prescan memory + let (huffman_lit_prescan_mem_rd_req_s, _huffman_lit_prescan_mem_rd_req_r) = chan("huffman_lit_prescan_mem_rd_req"); + let (_huffman_lit_prescan_mem_rd_resp_s, huffman_lit_prescan_mem_rd_resp_r) = chan("huffman_lit_prescan_mem_rd_resp"); + let (huffman_lit_prescan_mem_wr_req_s, _huffman_lit_prescan_mem_wr_req_r) = chan("huffman_lit_prescan_mem_wr_req"); + let (_huffman_lit_prescan_mem_wr_resp_s, huffman_lit_prescan_mem_wr_resp_r) = chan("huffman_lit_prescan_mem_wr_resp"); + + // AXI channels for various blocks + let (comp_axi_ar_s, comp_axi_ar_r) = chan[AXI_CHAN_N]("comp_axi_ar"); + let (comp_axi_r_s, comp_axi_r_r) = chan[AXI_CHAN_N]("comp_axi_r"); + + let (comp_ram_rd_req_s, comp_ram_rd_req_r) = chan[AXI_CHAN_N]("comp_ram_rd_req"); + let (comp_ram_rd_resp_s, comp_ram_rd_resp_r) = chan[AXI_CHAN_N]("comp_ram_rd_resp"); + let (comp_ram_wr_req_s, comp_ram_wr_req_r) = chan[AXI_CHAN_N]("comp_ram_wr_req"); + let (comp_ram_wr_resp_s, comp_ram_wr_resp_r) = chan[AXI_CHAN_N]("comp_ram_wr_resp"); + + unroll_for! (i, ()): (u32, ()) in range(u32:0, AXI_CHAN_N) { + spawn ram::RamModel< + TEST_RAM_DATA_W, TEST_RAM_SIZE, TEST_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED + >(comp_ram_rd_req_r[i], comp_ram_rd_resp_s[i], comp_ram_wr_req_r[i], comp_ram_wr_resp_s[i]); + + spawn axi_ram::AxiRamReader< + TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, TEST_RAM_SIZE, + TEST_RAM_BASE_ADDR, TEST_RAM_DATA_W, TEST_RAM_ADDR_W + >(comp_axi_ar_r[i], comp_axi_r_s[i], comp_ram_rd_req_s[i], comp_ram_rd_resp_r[i]); + }(()); + + // Literals buffer RAMs + let (litbuf_rd_req_s, litbuf_rd_req_r) = chan[u32:8]("litbuf_rd_req"); + let (litbuf_rd_resp_s, litbuf_rd_resp_r) = chan[u32:8]("litbuf_rd_resp"); + let (litbuf_wr_req_s, litbuf_wr_req_r) = chan[u32:8]("litbuf_wr_req"); + let (litbuf_wr_resp_s, litbuf_wr_resp_r) = chan[u32:8]("litbuf_wr_resp"); + unroll_for! (i, ()): (u32, ()) in range(u32:0, u32:8) { + spawn ram::RamModel< + LITERALS_BUFFER_RAM_DATA_W, LITERALS_BUFFER_RAM_SIZE, LITERALS_BUFFER_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED + >( + litbuf_rd_req_r[i], litbuf_rd_resp_s[i], litbuf_wr_req_r[i], litbuf_wr_resp_s[i] + ); + }(()); + + // RAMs for FSE decoder + // DPD RAM + let (dpd_rd_req_s, dpd_rd_req_r) = chan("dpd_rd_req"); + let (dpd_rd_resp_s, dpd_rd_resp_r) = chan("dpd_rd_resp"); + let (dpd_wr_req_s, dpd_wr_req_r) = chan("dpd_wr_req"); + let (dpd_wr_resp_s, dpd_wr_resp_r) = chan("dpd_wr_resp"); + spawn ram::RamModel< + TEST_DPD_RAM_DATA_W, TEST_DPD_RAM_SIZE, TEST_DPD_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED + >( + dpd_rd_req_r, dpd_rd_resp_s, dpd_wr_req_r, dpd_wr_resp_s, + ); + + // TMP RAM + let (tmp_rd_req_s, tmp_rd_req_r) = chan("tmp_rd_req"); + let (tmp_rd_resp_s, tmp_rd_resp_r) = chan("tmp_rd_resp"); + let (tmp_wr_req_s, tmp_wr_req_r) = chan("tmp_wr_req"); + let (tmp_wr_resp_s, tmp_wr_resp_r) = chan("tmp_wr_resp"); + spawn ram::RamModel< + TEST_TMP_RAM_DATA_W, TEST_TMP_RAM_SIZE, TEST_TMP_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED + >( + tmp_rd_req_r, tmp_rd_resp_s, tmp_wr_req_r, tmp_wr_resp_s, + ); + + // FSE RAMs + let (fse_rd_req_s, fse_rd_req_r) = chan[u32:6]("fse_rd_req"); + let (fse_rd_resp_s, fse_rd_resp_r) = chan[u32:6]("fse_rd_resp"); + let (fse_wr_req_s, fse_wr_req_r) = chan[u32:6]("fse_wr_req"); + let (fse_wr_resp_s, fse_wr_resp_r) = chan[u32:6]("fse_wr_resp"); + unroll_for! (i, ()): (u32, ()) in range(u32:0, u32:6) { + spawn ram::RamModel< + TEST_FSE_RAM_DATA_W, TEST_FSE_RAM_SIZE, TEST_FSE_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED + >( + fse_rd_req_r[i], fse_rd_resp_s[i], fse_wr_req_r[i], fse_wr_resp_s[i] + ); + }(()); + + // Default LL + + let (ll_sel_test_s, ll_sel_test_r) = chan("ll_sel_test"); + + let (ll_def_test_rd_req_s, ll_def_test_rd_req_r) = chan("ll_def_test_rd_req"); + let (ll_def_test_rd_resp_s, ll_def_test_rd_resp_r) = chan("ll_def_test_rd_resp"); + let (ll_def_test_wr_req_s, ll_def_test_wr_req_r) = chan("ll_def_test_wr_req"); + let (ll_def_test_wr_resp_s, ll_def_test_wr_resp_r) = chan("ll_def_test_wr_resp"); + + let (ll_def_fse_rd_req_s, ll_def_fse_rd_req_r) = chan("ll_def_fse_rd_req"); + let (ll_def_fse_rd_resp_s, ll_def_fse_rd_resp_r) = chan("ll_def_fse_rd_resp"); + let (ll_def_fse_wr_req_s, ll_def_fse_wr_req_r) = chan("ll_def_fse_wr_req"); + let (ll_def_fse_wr_resp_s, ll_def_fse_wr_resp_r) = chan("ll_def_fse_wr_resp"); + + spawn ram_mux::RamMux< + TEST_FSE_RAM_ADDR_W, + TEST_FSE_RAM_DATA_W, + TEST_FSE_RAM_NUM_PARTITIONS, + >( + ll_sel_test_r, + ll_def_test_rd_req_r, ll_def_test_rd_resp_s, ll_def_test_wr_req_r, ll_def_test_wr_resp_s, + ll_def_fse_rd_req_r, ll_def_fse_rd_resp_s, ll_def_fse_wr_req_r, ll_def_fse_wr_resp_s, + fse_rd_req_s[0], fse_rd_resp_r[0], fse_wr_req_s[0], fse_wr_resp_r[0], + ); + + // Default ML + + let (ml_sel_test_s, ml_sel_test_r) = chan("ml_sel_test"); + + let (ml_def_test_rd_req_s, ml_def_test_rd_req_r) = chan("ml_def_test_rd_req"); + let (ml_def_test_rd_resp_s, ml_def_test_rd_resp_r) = chan("ml_def_test_rd_resp"); + let (ml_def_test_wr_req_s, ml_def_test_wr_req_r) = chan("ml_def_test_wr_req"); + let (ml_def_test_wr_resp_s, ml_def_test_wr_resp_r) = chan("ml_def_test_wr_resp"); + + let (ml_def_fse_rd_req_s, ml_def_fse_rd_req_r) = chan("ml_def_fse_rd_req"); + let (ml_def_fse_rd_resp_s, ml_def_fse_rd_resp_r) = chan("ml_def_fse_rd_resp"); + let (ml_def_fse_wr_req_s, ml_def_fse_wr_req_r) = chan("ml_def_fse_wr_req"); + let (ml_def_fse_wr_resp_s, ml_def_fse_wr_resp_r) = chan("ml_def_fse_wr_resp"); + + spawn ram_mux::RamMux< + TEST_FSE_RAM_ADDR_W, + TEST_FSE_RAM_DATA_W, + TEST_FSE_RAM_NUM_PARTITIONS, + >( + ml_sel_test_r, + ml_def_test_rd_req_r, ml_def_test_rd_resp_s, ml_def_test_wr_req_r, ml_def_test_wr_resp_s, + ml_def_fse_rd_req_r, ml_def_fse_rd_resp_s, ml_def_fse_wr_req_r, ml_def_fse_wr_resp_s, + fse_rd_req_s[2], fse_rd_resp_r[2], fse_wr_req_s[2], fse_wr_resp_r[2], + ); + + // Default OF + + let (of_sel_test_s, of_sel_test_r) = chan("of_sel_test"); + + let (of_def_test_rd_req_s, of_def_test_rd_req_r) = chan("of_def_test_rd_req"); + let (of_def_test_rd_resp_s, of_def_test_rd_resp_r) = chan("of_def_test_rd_resp"); + let (of_def_test_wr_req_s, of_def_test_wr_req_r) = chan("of_def_test_wr_req"); + let (of_def_test_wr_resp_s, of_def_test_wr_resp_r) = chan("of_def_test_wr_resp"); + + let (of_def_fse_rd_req_s, of_def_fse_rd_req_r) = chan("of_def_fse_rd_req"); + let (of_def_fse_rd_resp_s, of_def_fse_rd_resp_r) = chan("of_def_fse_rd_resp"); + let (of_def_fse_wr_req_s, of_def_fse_wr_req_r) = chan("of_def_fse_wr_req"); + let (of_def_fse_wr_resp_s, of_def_fse_wr_resp_r) = chan("of_def_fse_wr_resp"); + + spawn ram_mux::RamMux< + TEST_FSE_RAM_ADDR_W, + TEST_FSE_RAM_DATA_W, + TEST_FSE_RAM_NUM_PARTITIONS, + >( + of_sel_test_r, + of_def_test_rd_req_r, of_def_test_rd_resp_s, of_def_test_wr_req_r, of_def_test_wr_resp_s, + of_def_fse_rd_req_r, of_def_fse_rd_resp_s, of_def_fse_wr_req_r, of_def_fse_wr_resp_s, + fse_rd_req_s[4], fse_rd_resp_r[4], fse_wr_req_s[4], fse_wr_resp_r[4], + ); + spawn zstd_dec::ZstdDecoder< TEST_AXI_DATA_W, TEST_AXI_ADDR_W, TEST_AXI_ID_W, TEST_AXI_DEST_W, TEST_REGS_N, TEST_WINDOW_LOG_MAX, TEST_HB_ADDR_W, TEST_HB_DATA_W, TEST_HB_NUM_PARTITIONS, TEST_HB_SIZE_KB, + TEST_DPD_RAM_ADDR_W, TEST_DPD_RAM_DATA_W, TEST_DPD_RAM_NUM_PARTITIONS, + TEST_TMP_RAM_ADDR_W, TEST_TMP_RAM_DATA_W, TEST_TMP_RAM_NUM_PARTITIONS, + TEST_FSE_RAM_ADDR_W, TEST_FSE_RAM_DATA_W, TEST_FSE_RAM_NUM_PARTITIONS, + HISTORY_BUFFER_SIZE_KB, AXI_CHAN_N, >( csr_axi_aw_r, csr_axi_w_r, csr_axi_b_s, csr_axi_ar_r, csr_axi_r_s, fh_axi_ar_s, fh_axi_r_r, bh_axi_ar_s, bh_axi_r_r, raw_axi_ar_s, raw_axi_r_r, + comp_axi_ar_s, comp_axi_r_r, + dpd_rd_req_s, dpd_rd_resp_r, + dpd_wr_req_s, dpd_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, + tmp_wr_req_s, tmp_wr_resp_r, + + // Channels for accessing FSE tables with muxed default FSE tables + [ll_def_fse_rd_req_s, fse_rd_req_s[1], ml_def_fse_rd_req_s, fse_rd_req_s[3], of_def_fse_rd_req_s, fse_rd_req_s[5]], + [ll_def_fse_rd_resp_r, fse_rd_resp_r[1], ml_def_fse_rd_resp_r, fse_rd_resp_r[3], of_def_fse_rd_resp_r, fse_rd_resp_r[5]], + [ll_def_fse_wr_req_s, fse_wr_req_s[1], ml_def_fse_wr_req_s, fse_wr_req_s[3], of_def_fse_wr_req_s, fse_wr_req_s[5]], + [ll_def_fse_wr_resp_r, fse_wr_resp_r[1], ml_def_fse_wr_resp_r, fse_wr_resp_r[3], of_def_fse_wr_resp_r, fse_wr_resp_r[5]], + + litbuf_rd_req_s, litbuf_rd_resp_r, + litbuf_wr_req_s, litbuf_wr_resp_r, + huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_resp_r, + huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_resp_r, + huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_resp_r, + huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_resp_r, output_axi_aw_s, output_axi_w_s, output_axi_b_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], - 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], - notify_s, reset_s, - ); - spawn ram::RamModel< - TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, - TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, - TEST_HB_RAM_ASSERT_VALID_READ - > (ram_rd_req_r[0], ram_rd_resp_s[0], ram_wr_req_r[0], ram_wr_resp_s[0]); + // RAMs for SequenceExecutor + hb_ram_rd_req_s[0], hb_ram_rd_req_s[1], hb_ram_rd_req_s[2], hb_ram_rd_req_s[3], + hb_ram_rd_req_s[4], hb_ram_rd_req_s[5], hb_ram_rd_req_s[6], hb_ram_rd_req_s[7], + hb_ram_rd_resp_r[0], hb_ram_rd_resp_r[1], hb_ram_rd_resp_r[2], hb_ram_rd_resp_r[3], + hb_ram_rd_resp_r[4], hb_ram_rd_resp_r[5], hb_ram_rd_resp_r[6], hb_ram_rd_resp_r[7], + hb_ram_wr_req_s[0], hb_ram_wr_req_s[1], hb_ram_wr_req_s[2], hb_ram_wr_req_s[3], + hb_ram_wr_req_s[4], hb_ram_wr_req_s[5], hb_ram_wr_req_s[6], hb_ram_wr_req_s[7], + hb_ram_wr_resp_r[0], hb_ram_wr_resp_r[1], hb_ram_wr_resp_r[2], hb_ram_wr_resp_r[3], + hb_ram_wr_resp_r[4], hb_ram_wr_resp_r[5], hb_ram_wr_resp_r[6], hb_ram_wr_resp_r[7], - spawn ram::RamModel< - TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, - TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, - TEST_HB_RAM_ASSERT_VALID_READ - > (ram_rd_req_r[1], ram_rd_resp_s[1], ram_wr_req_r[1], ram_wr_resp_s[1]); - - spawn ram::RamModel< - TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, - TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, - TEST_HB_RAM_ASSERT_VALID_READ - > (ram_rd_req_r[2], ram_rd_resp_s[2], ram_wr_req_r[2], ram_wr_resp_s[2]); - - spawn ram::RamModel< - TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, - TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, - TEST_HB_RAM_ASSERT_VALID_READ - > (ram_rd_req_r[3], ram_rd_resp_s[3], ram_wr_req_r[3], ram_wr_resp_s[3]); - - spawn ram::RamModel< - TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, - TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, - TEST_HB_RAM_ASSERT_VALID_READ - > (ram_rd_req_r[4], ram_rd_resp_s[4], ram_wr_req_r[4], ram_wr_resp_s[4]); + notify_s, reset_s, + ); - spawn ram::RamModel< - TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, - TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, - TEST_HB_RAM_ASSERT_VALID_READ - > (ram_rd_req_r[5], ram_rd_resp_s[5], ram_wr_req_r[5], ram_wr_resp_s[5]); + unroll_for! (i, ()): (u32, ()) in range(u32:0, u32:8) { + spawn ram::RamModel< + TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, + TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, + TEST_HB_RAM_ASSERT_VALID_READ + >(hb_ram_rd_req_r[i], hb_ram_rd_resp_s[i], hb_ram_wr_req_r[i], hb_ram_wr_resp_s[i]); + }(()); - spawn ram::RamModel< - TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, - TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, - TEST_HB_RAM_ASSERT_VALID_READ - > (ram_rd_req_r[6], ram_rd_resp_s[6], ram_wr_req_r[6], ram_wr_resp_s[6]); - - spawn ram::RamModel< - TEST_HB_DATA_W, TEST_HB_RAM_SIZE, TEST_HB_RAM_WORD_PARTITION_SIZE, - TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_HB_RAM_INITIALIZED, - TEST_HB_RAM_ASSERT_VALID_READ - > (ram_rd_req_r[7], ram_rd_resp_s[7], ram_wr_req_r[7], ram_wr_resp_s[7]); spawn ram::RamModel< TEST_RAM_DATA_W, TEST_RAM_SIZE, TEST_RAM_WORD_PARTITION_SIZE, TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, - > (ram_rd_req_fh_r, ram_rd_resp_fh_s, ram_wr_req_fh_r, ram_wr_resp_fh_s); + > (fh_ram_rd_req_r, fh_ram_rd_resp_s, fh_ram_wr_req_r, fh_ram_wr_resp_s); spawn ram::RamModel< TEST_RAM_DATA_W, TEST_RAM_SIZE, TEST_RAM_WORD_PARTITION_SIZE, TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, - > (ram_rd_req_bh_r, ram_rd_resp_bh_s, ram_wr_req_bh_r, ram_wr_resp_bh_s); + > (bh_ram_rd_req_r, bh_ram_rd_resp_s, bh_ram_wr_req_r, bh_ram_wr_resp_s); spawn ram::RamModel< TEST_RAM_DATA_W, TEST_RAM_SIZE, TEST_RAM_WORD_PARTITION_SIZE, TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, - > (ram_rd_req_raw_r, ram_rd_resp_raw_s, ram_wr_req_raw_r, ram_wr_resp_raw_s); + > (raw_ram_rd_req_r, raw_ram_rd_resp_s, raw_ram_wr_req_r, raw_ram_wr_resp_s); spawn axi_ram::AxiRamReader< TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, TEST_RAM_SIZE, TEST_RAM_BASE_ADDR, TEST_RAM_DATA_W, TEST_RAM_ADDR_W, - >(fh_axi_ar_r, fh_axi_r_s, ram_rd_req_fh_s, ram_rd_resp_fh_r); + >(fh_axi_ar_r, fh_axi_r_s, fh_ram_rd_req_s, fh_ram_rd_resp_r); spawn axi_ram::AxiRamReader< TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, TEST_RAM_SIZE, TEST_RAM_BASE_ADDR, TEST_RAM_DATA_W, TEST_RAM_ADDR_W, - >(bh_axi_ar_r, bh_axi_r_s, ram_rd_req_bh_s, ram_rd_resp_bh_r); + >(bh_axi_ar_r, bh_axi_r_s, bh_ram_rd_req_s, bh_ram_rd_resp_r); spawn axi_ram::AxiRamReader< TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, TEST_RAM_SIZE, TEST_RAM_BASE_ADDR, TEST_RAM_DATA_W, TEST_RAM_ADDR_W, - >(raw_axi_ar_r, raw_axi_r_s, ram_rd_req_raw_s, ram_rd_resp_raw_r); + >(raw_axi_ar_r, raw_axi_r_s, raw_ram_rd_req_s, raw_ram_rd_resp_r); ( terminator, csr_axi_aw_s, csr_axi_w_s, csr_axi_b_r, csr_axi_ar_s, csr_axi_r_r, - fh_axi_ar_r, fh_axi_r_s, - bh_axi_ar_r, bh_axi_r_s, - raw_axi_ar_r, raw_axi_r_s, + fh_axi_ar_r, fh_axi_r_s, fh_ram_wr_req_s, fh_ram_wr_resp_r, + bh_axi_ar_r, bh_axi_r_s, bh_ram_wr_req_s, bh_ram_wr_resp_r, + raw_axi_ar_r, raw_axi_r_s, raw_ram_wr_req_s, raw_ram_wr_resp_r, + comp_ram_wr_req_s, comp_ram_wr_resp_r, output_axi_aw_r, output_axi_w_r, output_axi_b_s, - ram_rd_req_r, ram_rd_resp_s, ram_wr_req_r, ram_wr_resp_s, - ram_wr_req_fh_s, ram_wr_req_bh_s, ram_wr_req_raw_s, - ram_wr_resp_fh_r, ram_wr_resp_bh_r, ram_wr_resp_raw_r, + hb_ram_rd_req_r, hb_ram_rd_resp_s, hb_ram_wr_req_r, hb_ram_wr_resp_s, + ll_sel_test_s, ll_def_test_rd_req_s, ll_def_test_rd_resp_r, ll_def_test_wr_req_s, ll_def_test_wr_resp_r, + ml_sel_test_s, ml_def_test_rd_req_s, ml_def_test_rd_resp_r, ml_def_test_wr_req_s, ml_def_test_wr_resp_r, + of_sel_test_s, of_def_test_rd_req_s, of_def_test_rd_resp_r, of_def_test_wr_req_s, of_def_test_wr_resp_r, notify_r, reset_r, ) } next (state: ()) { trace_fmt!("Test start"); - let frames_count = array_size(zstd_frame_testcases::FRAMES); + let frames_count = array_size(comp_frame::FRAMES); let tok = join(); + + // FILL THE LL DEFAULT RAM + trace_fmt!("Filling LL default FSE table"); + let tok = send(tok, ll_sel_test_s, u1:0); + let tok = unroll_for! (i, tok): (u32, token) in range(u32:0, array_size(sequence_dec::DEFAULT_LL_TABLE)) { + let req = FseRamWrReq { + addr: i as uN[TEST_FSE_RAM_ADDR_W], + data: fse_table_creator::fse_record_to_bits(sequence_dec::DEFAULT_LL_TABLE[i]), + mask: !uN[TEST_FSE_RAM_NUM_PARTITIONS]:0, + }; + let tok = send(tok, ll_def_test_wr_req_s, req); + let (tok, _) = recv(tok, ll_def_test_wr_resp_r); + tok + }(tok); + let tok = send(tok, ll_sel_test_s, u1:1); + + // FILL THE OF DEFAULT RAM + trace_fmt!("Filling OF default FSE table"); + let tok = send(tok, of_sel_test_s, u1:0); + let tok = unroll_for! (i, tok): (u32, token) in range(u32:0, array_size(sequence_dec::DEFAULT_OF_TABLE)) { + let req = FseRamWrReq { + addr: i as uN[TEST_FSE_RAM_ADDR_W], + data: fse_table_creator::fse_record_to_bits(sequence_dec::DEFAULT_OF_TABLE[i]), + mask: !uN[TEST_FSE_RAM_NUM_PARTITIONS]:0, + }; + let tok = send(tok, of_def_test_wr_req_s, req); + let (tok, _) = recv(tok, of_def_test_wr_resp_r); + tok + }(tok); + let tok = send(tok, of_sel_test_s, u1:1); + + // FILL THE ML DEFAULT RAM + trace_fmt!("Filling ML default FSE table"); + let tok = send(tok, ml_sel_test_s, u1:0); + let tok = unroll_for! (i, tok): (u32, token) in range(u32:0, array_size(sequence_dec::DEFAULT_ML_TABLE)) { + let req = FseRamWrReq { + addr: i as uN[TEST_FSE_RAM_ADDR_W], + data: fse_table_creator::fse_record_to_bits(sequence_dec::DEFAULT_ML_TABLE[i]), + mask: !uN[TEST_FSE_RAM_NUM_PARTITIONS]:0, + }; + let tok = send(tok, ml_def_test_wr_req_s, req); + let (tok, _) = recv(tok, ml_def_test_wr_resp_r); + tok + }(tok); + let tok = send(tok, ml_sel_test_s, u1:1); + let tok = unroll_for! (test_i, tok): (u32, token) in range(u32:0, frames_count) { trace_fmt!("Loading testcase {:x}", test_i + u32:1); - let frame = zstd_frame_testcases::FRAMES[test_i]; + let frame = comp_frame::FRAMES[test_i]; let tok = for (i, tok): (u32, token) in range(u32:0, frame.array_length) { let req = RamWrReq { addr: i as uN[TEST_RAM_ADDR_W], data: frame.data[i] as uN[TEST_RAM_DATA_W], mask: uN[TEST_RAM_NUM_PARTITIONS]:0xFF }; - let tok = send(tok, ram_wr_req_fh_s, req); - let tok = send(tok, ram_wr_req_bh_s, req); - let tok = send(tok, ram_wr_req_raw_s, req); - tok + let tok = send(tok, fh_ram_wr_req_s, req); + let tok = send(tok, bh_ram_wr_req_s, req); + let tok = send(tok, raw_ram_wr_req_s, req); + for (i, tok): (u32, token) in range(u32:0, AXI_CHAN_N) { + send(tok, comp_ram_wr_req_s[i], req) + }(tok) }(tok); trace_fmt!("Running decoder on testcase {:x}", test_i + u32:1); @@ -358,7 +675,7 @@ proc ZstdDecoderTest { }); let (tok, _) = recv(tok, csr_axi_b_r); - let decomp_frame = zstd_frame_testcases::DECOMPRESSED_FRAMES[test_i]; + let decomp_frame = comp_frame::DECOMPRESSED_FRAMES[test_i]; // Test ZstdDecoder memory output interface // Mock the output memory buffer as a DSLX array // It is required to handle AXI write transactions and to write the incoming data to @@ -377,7 +694,7 @@ proc ZstdDecoderTest { // The maximal number if beats in AXI burst transaction let MAX_AXI_TRANSFERS = u32:256; // Actual size of decompressed payload for current test - let DECOMPRESSED_BYTES = zstd_frame_testcases::DECOMPRESSED_FRAMES[test_i].length; + let DECOMPRESSED_BYTES = comp_frame::DECOMPRESSED_FRAMES[test_i].length; trace_fmt!("ZstdDecTest: Start receiving output"); let (tok, final_output_memory, final_output_memory_id, final_transfered_bytes) = for (axi_transaction, (tok, output_memory, output_memory_id, transfered_bytes)): From a75f9e6c11d95b63cb0ec0917fc030222d6bae64 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 14 Jan 2025 15:33:07 +0100 Subject: [PATCH 48/85] modules/zstd: Add example frame for DSLX tests Signed-off-by: Robert Winkler --- xls/modules/zstd/comp_frame.x | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 xls/modules/zstd/comp_frame.x diff --git a/xls/modules/zstd/comp_frame.x b/xls/modules/zstd/comp_frame.x new file mode 100644 index 0000000000..5e1a97dbd4 --- /dev/null +++ b/xls/modules/zstd/comp_frame.x @@ -0,0 +1,21 @@ +pub struct DataArray{ + data: uN[BITS_PER_WORD][LENGTH], + length: u32, + array_length: u32 +} +pub const FRAMES:DataArray< + u32:64, + u32:5 +>[1] = [DataArray<64, 5>{ + length: u32:33, + array_length: u32:5, + data: uN[64][5]:[uN[64]:0x001a3384fd2fb528, uN[64]:0xc1d3500000850000, uN[64]:0xdcf0529b98db8a06, uN[64]:0x308fa3120a430001, uN[64]:0x50] +}]; +pub const DECOMPRESSED_FRAMES:DataArray< + u32:64, + u32:4 +>[1] = [DataArray<64, 4>{ + length: u32:26, + array_length: u32:4, + data: uN[64][4]:[uN[64]:0x529b98db8a06c1d3, uN[64]:0x529b98db8a06dcf0, uN[64]:0x529b98db8a06dcf0, uN[64]:0xdcf0] +}]; From 362a00e590a8dbc655753992ff98a0b1e75ded7c Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 14 Jan 2025 15:34:16 +0100 Subject: [PATCH 49/85] modules/zstd: Adjust SequenceExecutor Signed-off-by: Robert Winkler --- xls/modules/zstd/sequence_executor.x | 371 ++++++++++++++------------- 1 file changed, 188 insertions(+), 183 deletions(-) diff --git a/xls/modules/zstd/sequence_executor.x b/xls/modules/zstd/sequence_executor.x index 80a7a6e70a..cab106b244 100644 --- a/xls/modules/zstd/sequence_executor.x +++ b/xls/modules/zstd/sequence_executor.x @@ -196,7 +196,7 @@ pub proc SequenceExecutor> out; ram_comp_output_r: chan> in; ram_resp_input_s: chan out; - ram_resp_output_r: chan in; + looped_channel_r: chan in; rd_req_m0_s: chan> out; rd_req_m1_s: chan> out; rd_req_m2_s: chan> out; @@ -217,8 +217,8 @@ pub proc SequenceExecutor in, output_mem_wr_data_in_s: chan out, - ram_resp_output_r: chan in, - ram_resp_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, @@ -262,14 +262,14 @@ pub proc SequenceExecutor()); // ... 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!()); + 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, looped_channel_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. @@ -678,136 +680,136 @@ const LITERAL_TEST_MEMORY_CONTENT:(TestRamAddr, RamData)[3][RAM_NUM] = [ ], ]; -#[test_proc] -proc SequenceExecutorLiteralsTest { - type MemWriterDataPacket = mem_writer::MemWriterDataPacket; - terminator: chan out; - - input_s: chan out; - output_mem_wr_data_in_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_mem_wr_data_in_s, output_mem_wr_data_in_r) = chan("output_mem_wr_data_in"); - - 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_DATA_W, TEST_ADDR_W, - TEST_RAM_SIZE, - TEST_RAM_ADDR_WIDTH, - INIT_HB_PTR_ADDR, - > ( - input_r, output_mem_wr_data_in_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_mem_wr_data_in_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 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 expected_mem_writer_data = decode_literal_packet(LITERAL_TEST_INPUT_DATA[i]); - let (tok, recv_mem_writer_data) = recv(tok, output_mem_wr_data_in_r); - assert_eq(expected_mem_writer_data, recv_mem_writer_data); - } else {} - }(()); - - for (i, ()): (u32, ()) in u32:0..RAM_NUM { - for (j, ()): (u32, ()) in 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); - } -} +// #[test_proc] +// proc SequenceExecutorLiteralsTest { +// type MemWriterDataPacket = mem_writer::MemWriterDataPacket; +// terminator: chan out; + +// input_s: chan out; +// output_mem_wr_data_in_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_mem_wr_data_in_s, output_mem_wr_data_in_r) = chan("output_mem_wr_data_in"); + +// 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_DATA_W, TEST_ADDR_W, +// TEST_RAM_SIZE, +// TEST_RAM_ADDR_WIDTH, +// INIT_HB_PTR_ADDR, +// > ( +// input_r, output_mem_wr_data_in_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_mem_wr_data_in_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 expected_mem_writer_data = decode_literal_packet(LITERAL_TEST_INPUT_DATA[i]); +// let (tok, recv_mem_writer_data) = recv(tok, output_mem_wr_data_in_r); +// assert_eq(expected_mem_writer_data, recv_mem_writer_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 { @@ -1037,67 +1039,70 @@ proc SequenceExecutorSequenceTest { next(state: ()) { let tok = join(); - for (i, ()): (u32, ()) in 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 expected_mem_writer_data = decode_literal_packet(LITERAL_TEST_INPUT_DATA[i]); - let (tok, recv_mem_writer_data) = recv(tok, output_mem_wr_data_in_r); - assert_eq(expected_mem_writer_data, recv_mem_writer_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_mem_wr_data_in_r); - assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[0], recv_data); - let (tok, recv_data) = recv(tok, output_mem_wr_data_in_r); - assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[1], recv_data); + // let tok = send(tok, print_start_s, ()); + // let (tok, _) = recv(tok, print_finish_r); - let tok = send(tok, input_s, SEQUENCE_TEST_INPUT_SEQUENCES[1]); + let tok = send(tok, input_s, SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:1, + content: CopyOrMatchContent:0x31, + last: false + }); let (tok, recv_data) = recv(tok, output_mem_wr_data_in_r); - assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[2], recv_data); + assert_eq(recv_data, TestMemWriterDataPacket { + data: uN[TEST_DATA_W]:0x31, + length: uN[TEST_ADDR_W]:1, + last: false + }); - let tok = send(tok, input_s, SEQUENCE_TEST_INPUT_SEQUENCES[2]); - let (tok, recv_data) = recv(tok, output_mem_wr_data_in_r); - assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[3], recv_data); + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); - let tok = send(tok, input_s, SEQUENCE_TEST_INPUT_SEQUENCES[3]); + let tok = send(tok, input_s, SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: CopyOrMatchLength:3, + content: CopyOrMatchContent:4, + last: false + }); let (tok, recv_data) = recv(tok, output_mem_wr_data_in_r); - assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[4], recv_data); + assert_eq(recv_data, TestMemWriterDataPacket { + data: uN[TEST_DATA_W]:0x31, + length: uN[TEST_ADDR_W]:1, + last: false + }); - let tok = send(tok, input_s, SEQUENCE_TEST_INPUT_SEQUENCES[4]); - let (tok, recv_data) = recv(tok, output_mem_wr_data_in_r); - assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[5], recv_data); + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); - let tok = send(tok, input_s, SEQUENCE_TEST_INPUT_SEQUENCES[5]); - let (tok, recv_data) = recv(tok, output_mem_wr_data_in_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_mem_wr_data_in_r); - assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[7], recv_data); + assert_eq(recv_data, TestMemWriterDataPacket { + data: uN[TEST_DATA_W]:0x3131, + length: uN[TEST_ADDR_W]:2, + last: false + }); - 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_mem_wr_data_in_r); - assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[8], recv_data); - let (tok, recv_data) = recv(tok, output_mem_wr_data_in_r); - assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[9], recv_data); + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); - let tok = send(tok, input_s, SEQUENCE_TEST_INPUT_SEQUENCES[10]); + let tok = send(tok, input_s, SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: CopyOrMatchLength:3, + content: CopyOrMatchContent:7, + last: false + }); let (tok, recv_data) = recv(tok, output_mem_wr_data_in_r); - assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[10], recv_data); + assert_eq(recv_data, TestMemWriterDataPacket { + data: uN[TEST_DATA_W]:0x313131, + length: uN[TEST_ADDR_W]:3, + last: false + }); - // Print RAM content let tok = send(tok, print_start_s, ()); let (tok, _) = recv(tok, print_finish_r); + send(tok, terminator, true); } } From 90f40de164db47e2733d4660341f7d1125eae1d2 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 14 Jan 2025 15:35:27 +0100 Subject: [PATCH 50/85] modules/zstd: Adjust AxiRam Signed-off-by: Robert Winkler --- xls/modules/zstd/memory/axi_ram.x | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/xls/modules/zstd/memory/axi_ram.x b/xls/modules/zstd/memory/axi_ram.x index 688fe2c171..4886028a9c 100644 --- a/xls/modules/zstd/memory/axi_ram.x +++ b/xls/modules/zstd/memory/axi_ram.x @@ -105,9 +105,9 @@ proc AxiRamReaderRequester< // validate bundle let ar_bundle_ok = ar_bundle_valid && ((ar_bundle.size as u32 + u32:3) <= AXI_DATA_W_LOG2); - if ar_bundle_valid { - trace_fmt!("{:#x}", ar_bundle); - } else {}; + //if ar_bundle_valid { + // trace_fmt!("{:#x}", ar_bundle); + //} else {}; let tok = send_if(tok, sync_s, ar_bundle_valid && !ar_bundle_ok, Sync { id: ar_bundle.id, resp: AxiReadResp::SLVERR, @@ -680,9 +680,9 @@ proc AxiRamReaderTest { tok }(tok); - let tok = for ((i, axi_ar_bundle), tok): ((u32, TestAxiAr), token) in enumerate(TEST_AXI_AR_BUNDLES) { + let tok = for ((_i, axi_ar_bundle), tok): ((u32, TestAxiAr), token) in enumerate(TEST_AXI_AR_BUNDLES) { let tok = send(tok, axi_ar_s, axi_ar_bundle); - trace_fmt!("Sent bundle #{} {:#x}", i + u32:1, axi_ar_bundle); + // trace_fmt!("Sent bundle #{} {:#x}", i + u32:1, axi_ar_bundle); let size_valid = (u32:1 << (axi_ar_bundle.size as u32 + u32:3)) <= TEST_AXI_DATA_W; From a9916cee9f05c99a2ebf570eaa8329d2273acd0e Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 14 Jan 2025 15:16:31 +0100 Subject: [PATCH 51/85] modules/zstd: Add RamMux Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 16 +++ xls/modules/zstd/ram_mux.x | 238 +++++++++++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 xls/modules/zstd/ram_mux.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 3cc6c22d56..9ee783025c 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1959,6 +1959,22 @@ place_and_route( target_die_utilization_percentage = "10", ) +xls_dslx_library( + name = "ram_mux_dslx", + srcs = [ + "ram_mux.x" + ], + deps = [ + "//xls/examples:ram_dslx", + ], +) + +xls_dslx_test( + name = "ram_mux_dslx_test", + library = ":ram_mux_dslx", + tags = ["manual"], +) + xls_dslx_library( name = "sequence_dec_dslx", srcs = [ diff --git a/xls/modules/zstd/ram_mux.x b/xls/modules/zstd/ram_mux.x new file mode 100644 index 0000000000..25d785a69f --- /dev/null +++ b/xls/modules/zstd/ram_mux.x @@ -0,0 +1,238 @@ +// 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 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.examples.ram; + +struct RamMuxState { sel: u1, cnt0: u32, cnt1: u32 } + +pub proc RamMux< + ADDR_WIDTH: u32, DATA_WIDTH: u32, NUM_PARTITIONS: u32, + INIT_SEL: u1 = {u1:0} +> { + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp; + + sel_r: chan in; + + rd_req0_r: chan in; + rd_resp0_s: chan out; + wr_req0_r: chan in; + wr_resp0_s: chan out; + rd_req1_r: chan in; + rd_resp1_s: chan out; + wr_req1_r: chan in; + wr_resp1_s: chan out; + rd_req_s: chan out; + rd_resp_r: chan in; + wr_req_s: chan out; + wr_resp_r: chan in; + + config( + sel_r: chan in, + rd_req0_r: chan in, + rd_resp0_s: chan out, + wr_req0_r: chan in, + wr_resp0_s: chan out, + rd_req1_r: chan in, + rd_resp1_s: chan out, + wr_req1_r: chan in, + wr_resp1_s: chan out, + rd_req_s: chan out, + rd_resp_r: chan in, + wr_req_s: chan out, + wr_resp_r: chan in + ) { + ( + sel_r, + rd_req0_r, rd_resp0_s, wr_req0_r, wr_resp0_s, + rd_req1_r, rd_resp1_s, wr_req1_r, wr_resp1_s, + rd_req_s, rd_resp_r, wr_req_s, wr_resp_r, + ) + } + + init { + RamMuxState { + sel: INIT_SEL, ..zero!() + } + } + + next(state: RamMuxState) { + let tok0 = join(); + + let sel = state.sel; + let (cnt0, cnt1) = (state.cnt0, state.cnt1); + + // receive requests from channel 0 + let (tok1_0, rd_req0, rd_req0_valid) = + recv_if_non_blocking(tok0, rd_req0_r, sel == u1:0, zero!()); + let cnt0 = if rd_req0_valid { cnt0 + u32:1 } else { cnt0 }; + + let (tok1_1, wr_req0, wr_req0_valid) = + recv_if_non_blocking(tok0, wr_req0_r, sel == u1:0, zero!()); + let cnt0 = if wr_req0_valid { cnt0 + u32:1 } else { cnt0 }; + + // receive requests from channel 1 + let (tok1_2, rd_req1, rd_req1_valid) = + recv_if_non_blocking(tok0, rd_req1_r, sel == u1:1, zero!()); + let cnt1 = if rd_req1_valid { cnt1 + u32:1 } else { cnt1 }; + + let (tok1_3, wr_req1, wr_req1_valid) = + recv_if_non_blocking(tok0, wr_req1_r, sel == u1:1, zero!()); + let cnt1 = if wr_req1_valid { cnt1 + u32:1 } else { cnt1 }; + + // receive responses from output channel + let (tok1_4, rd_resp, rd_resp_valid) = + recv_non_blocking(tok0, rd_resp_r, zero!()); + let (tok1_5, wr_resp, wr_resp_valid) = + recv_non_blocking(tok0, wr_resp_r, zero!()); + + let tok1 = join(tok1_0, tok1_1, tok1_2, tok1_3, tok1_4, tok1_5); + + // prepare output values + let (rd_req, rd_req_valid, wr_req, wr_req_valid) = if sel == u1:0 { + (rd_req0, rd_req0_valid, wr_req0, wr_req0_valid) + } else { + (rd_req1, rd_req1_valid, wr_req1, wr_req1_valid) + }; + + // send requests to output channel + let tok2_0 = send_if(tok1, rd_req_s, rd_req_valid, rd_req); + let tok2_1 = send_if(tok1, wr_req_s, wr_req_valid, wr_req); + + // send responses to channel 0 + let rd_resp0_cond = (sel == u1:0 && rd_resp_valid); + let tok2_2 = send_if(tok1, rd_resp0_s, rd_resp0_cond, rd_resp); + let cnt0 = if rd_resp0_cond { cnt0 - u32:1 } else { cnt0 }; + + let wr_resp0_cond = (sel == u1:0 && wr_resp_valid); + let tok2_3 = send_if(tok1, wr_resp0_s, wr_resp0_cond, wr_resp); + let cnt0 = if wr_resp0_cond { cnt0 - u32:1 } else { cnt0 }; + + // send responses to channel 1 + let rd_resp1_cond = (sel == u1:1 && rd_resp_valid); + let tok2_4 = send_if(tok1, rd_resp1_s, rd_resp1_cond, rd_resp); + let cnt1 = if rd_resp1_cond { cnt1 - u32:1 } else { cnt1 }; + + let wr_resp1_cond = (sel == u1:1 && wr_resp_valid); + let tok2_5 = send_if(tok1, wr_resp1_s, wr_resp1_cond, wr_resp); + let cnt1 = if wr_resp1_cond { cnt1 - u32:1 } else { cnt1 }; + + // handle select + let (tok2_6, sel, sel_valid) = + recv_if_non_blocking(tok1, sel_r, cnt0 == u32:0 && cnt1 == u32:0, state.sel); + + RamMuxState { sel, cnt0, cnt1 } + } +} + +const MUX_TEST_SIZE = u32:32; +const MUX_TEST_DATA_WIDTH = u32:8; +const MUX_TEST_ADDR_WIDTH = std::clog2(MUX_TEST_SIZE); +const MUX_TEST_WORD_PARTITION_SIZE = u32:1; +const MUX_TEST_NUM_PARTITIONS = ram::num_partitions(MUX_TEST_WORD_PARTITION_SIZE, MUX_TEST_DATA_WIDTH); + +type MuxTestAddr = uN[MUX_TEST_ADDR_WIDTH]; +type MuxTestData = uN[MUX_TEST_DATA_WIDTH]; + +fn MuxTestWriteWordReq (addr: MuxTestAddr, data: MuxTestData) -> + ram::WriteReq { + ram::WriteWordReq(addr, data) +} + +fn MuxTestReadWordReq(addr: MuxTestAddr) -> + ram::ReadReq { + ram::ReadWordReq(addr) +} + +#[test_proc] +proc RamMuxTest { + terminator: chan out; + sel_s: chan out; + + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp; + + + rd_req0_s: chan out; + rd_resp0_r: chan in; + wr_req0_s: chan out; + wr_resp0_r: chan in; + rd_req1_s: chan out; + rd_resp1_r: chan in; + wr_req1_s: chan out; + wr_resp1_r: chan in; + + config(terminator: chan out) { + let (sel_s, sel_r) = chan("sel"); + + let (rd_req0_s, rd_req0_r) = chan("rd_req0"); + let (rd_resp0_s, rd_resp0_r) = chan("rd_resp0"); + let (wr_req0_s, wr_req0_r) = chan("wr_req0"); + let (wr_resp0_s, wr_resp0_r) = chan("wr_resp0"); + + let (rd_req1_s, rd_req1_r) = chan("rd_req1"); + let (rd_resp1_s, rd_resp1_r) = chan("rd_resp1"); + let (wr_req1_s, wr_req1_r) = chan("rd_req1"); + let (wr_resp1_s, wr_resp1_r) = chan("wr_resp1"); + + let (rd_req_s, rd_req_r) = chan("rd_req"); + let (rd_resp_s, rd_resp_r) = chan("rd_resp"); + let (wr_req_s, wr_req_r) = chan("wr_req"); + let (wr_resp_s, wr_resp_r) = chan("wr_resp"); + + spawn RamMux( + sel_r, rd_req0_r, rd_resp0_s, wr_req0_r, wr_resp0_s, rd_req1_r, rd_resp1_s, wr_req1_r, + wr_resp1_s, rd_req_s, rd_resp_r, wr_req_s, wr_resp_r); + + spawn ram::RamModel( + rd_req_r, rd_resp_s, wr_req_r, wr_resp_s); + ( + terminator, sel_s, rd_req0_s, rd_resp0_r, wr_req0_s, wr_resp0_r, rd_req1_s, rd_resp1_r, + wr_req1_s, wr_resp1_r, + ) + } + + init { } + + next(state: ()) { + let tok = join(); + let req = MuxTestWriteWordReq(MuxTestAddr:0, MuxTestData:0xAB); + let tok = send(tok, wr_req0_s, req); + let (tok, _) = recv(tok, wr_resp0_r); + let tok = send(tok, rd_req0_s, MuxTestReadWordReq(req.addr)); + let (tok, resp) = recv(tok, rd_resp0_r); + assert_eq(resp.data, req.data); + + let req = MuxTestWriteWordReq(MuxTestAddr:1, MuxTestData:0xCD); + let tok = send(tok, wr_req1_s, req); + let tok = send(tok, sel_s, u1:1); + let (tok, _) = recv(tok, wr_resp1_r); + let tok = send(tok, rd_req1_s, MuxTestReadWordReq(req.addr)); + let (tok, resp) = recv(tok, rd_resp1_r); + assert_eq(resp.data, req.data); + + let tok = send(tok, terminator, true); + } +} + From c15d82ecad7e67671df7e0c7435ae4b3f0c06e7e Mon Sep 17 00:00:00 2001 From: Maciej Torhan Date: Tue, 7 Jan 2025 15:00:08 +0100 Subject: [PATCH 52/85] modules/zstd: Fix HuffmanRawWeightsDecoder Signed-off-by: Maciej Torhan --- xls/modules/zstd/BUILD | 3 +- xls/modules/zstd/comp_frame_huffman.x | 21 ++++ xls/modules/zstd/huffman_ctrl.x | 73 ++++++++---- xls/modules/zstd/huffman_data_preprocessor.x | 113 ++++++++++++------- xls/modules/zstd/huffman_decoder.x | 17 ++- xls/modules/zstd/huffman_literals_dec.x | 11 +- xls/modules/zstd/huffman_prescan.x | 28 ++++- xls/modules/zstd/huffman_weights_dec.x | 37 +++--- xls/modules/zstd/literals_decoder.x | 4 +- xls/modules/zstd/zstd_dec_test.x | 43 +++++-- 10 files changed, 241 insertions(+), 109 deletions(-) create mode 100755 xls/modules/zstd/comp_frame_huffman.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 9ee783025c..3616a980ad 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1276,7 +1276,8 @@ xls_dslx_test( "zstd_dec.x", "zstd_dec_test.x", # "zstd_frame_testcases.x", - "comp_frame.x", + # "comp_frame.x", + "comp_frame_huffman.x", ], tags = ["manual"], deps = zstd_dec_deps, diff --git a/xls/modules/zstd/comp_frame_huffman.x b/xls/modules/zstd/comp_frame_huffman.x new file mode 100755 index 0000000000..a83c42577d --- /dev/null +++ b/xls/modules/zstd/comp_frame_huffman.x @@ -0,0 +1,21 @@ +pub struct DataArray{ + data: uN[BITS_PER_WORD][LENGTH], + length: u32, + array_length: u32 +} +pub const FRAMES:DataArray< + u32:64, + u32:12 +>[1] = [DataArray<64, 12>{ + length: u32:93, + array_length: u32:12, + data: uN[64][12]:[uN[64]:0x00704484fd2fb528, uN[64]:0xac033a0002650000, uN[64]:0x1111111111118e00, uN[64]:0x0007000700071011, uN[64]:0x131a053a5606874c, uN[64]:0x93b7146cb45c3584, uN[64]:0x06499215949aa275, uN[64]:0x0132000c0126fd3b, uN[64]:0x15a7b54443de03b8, uN[64]:0x5da6a9b37c005000, uN[64]:0x4e0200656960219d, uN[64]:0x912a65cf0b] +}]; +pub const DECOMPRESSED_FRAMES:DataArray< + u32:64, + u32:14 +>[1] = [DataArray<64, 14>{ + length: u32:112, + array_length: u32:14, + data: uN[64][14]:[uN[64]:0x0a03050a0305000a, uN[64]:0x0605050a0305000a, uN[64]:0x0708050a03050600, uN[64]:0x05040b0c06040c04, uN[64]:0x050a030c0b05040b, uN[64]:0x06040c0408050308, uN[64]:0x0b05040b05040b0c, uN[64]:0x0a030301050a030c, uN[64]:0x090409050a030505, uN[64]:0x040c040507020a0a, uN[64]:0x0602070b03090c06, uN[64]:0x030d0f060b030d0f, uN[64]:0x0f06040c0408050b, uN[64]:0x020909040600030d] +}]; diff --git a/xls/modules/zstd/huffman_ctrl.x b/xls/modules/zstd/huffman_ctrl.x index 036a765525..c9841be1fc 100644 --- a/xls/modules/zstd/huffman_ctrl.x +++ b/xls/modules/zstd/huffman_ctrl.x @@ -59,6 +59,7 @@ struct HuffmanControlAndSequenceState { tree_description_size: uN[AXI_ADDR_W], ctrl: HuffmanControlAndSequenceCtrl, stream_sizes: uN[AXI_ADDR_W][4], + prescan_start_sent: bool, } const JUMP_TABLE_SIZE = u32:6; @@ -172,6 +173,7 @@ pub proc HuffmanControlAndSequence { let (tok, weights_dec_resp, weights_dec_resp_valid) = recv_if_non_blocking(tok, weights_dec_resp_r, state.weights_dec_pending, zero!()); if (weights_dec_resp_valid) { trace_fmt!("Received Weights Decoding response: {:#x}", weights_dec_resp); } else {}; let state = if weights_dec_resp_valid { + trace_fmt!("Tree description size: {:#x}", weights_dec_resp.tree_description_size); State { weights_dec_pending: false, tree_description_size: weights_dec_resp.tree_description_size, @@ -194,12 +196,16 @@ pub proc HuffmanControlAndSequence { let (tok, jump_table_raw, jump_table_valid) = recv_if_non_blocking(tok, mem_rd_resp_r, state.jump_table_dec_pending, zero!()); let stream_sizes = jump_table_raw.data[0:48] as u16[3]; let total_streams_size = state.ctrl.len - state.tree_description_size; - let stream_sizes = uN[AXI_ADDR_W][4]:[stream_sizes[0] as uN[AXI_ADDR_W], stream_sizes[1] as uN[AXI_ADDR_W], stream_sizes[2] as uN[AXI_ADDR_W], total_streams_size - JUMP_TABLE_SIZE as uN[AXI_ADDR_W] - (stream_sizes[0] + stream_sizes[1] + stream_sizes[2]) as uN[AXI_ADDR_W]]; + let stream_sizes = uN[AXI_ADDR_W][4]:[ + stream_sizes[0] as uN[AXI_ADDR_W], + stream_sizes[1] as uN[AXI_ADDR_W], + stream_sizes[2] as uN[AXI_ADDR_W], + total_streams_size - JUMP_TABLE_SIZE as uN[AXI_ADDR_W] - (stream_sizes[0] + stream_sizes[1] + stream_sizes[2]) as uN[AXI_ADDR_W] + ]; if jump_table_valid { trace_fmt!("Received Jump Table: {:#x}", jump_table_raw); - trace_fmt!("total_streams_size: {:#x}", total_streams_size); - trace_fmt!("state.tree_description_size: {:#x}", state.tree_description_size); - trace_fmt!("stream sizes: {:#x}", stream_sizes); + trace_fmt!("Total streams size: {:#x}", total_streams_size); + trace_fmt!("Stream sizes: {:#x}", stream_sizes); } else {}; let state = if do_send_jump_table_req { State { @@ -217,10 +223,29 @@ pub proc HuffmanControlAndSequence { state }; - let start_decoding = ((state.fsm == FSM::DECODING) & (!state.weights_dec_pending) & (!state.stream_dec_pending) & (!state.jump_table_dec_pending) & ((!state.multi_stream_dec_pending) || (state.multi_stream_dec_pending && state.multi_stream_decodings_finished != u3:4))); - send_if(tok, prescan_start_s, start_decoding, true); - send_if(tok, code_builder_start_s, start_decoding, true); - if (start_decoding) { trace_fmt!("Sent START to prescan and code builder"); } else {}; + let start_decoding = ( + (state.fsm == FSM::DECODING) & + (!state.weights_dec_pending) & + (!state.stream_dec_pending) & + (!state.jump_table_dec_pending) & + ( + (!state.multi_stream_dec_pending) || + (state.multi_stream_dec_pending && state.multi_stream_decodings_finished != u3:4) + ) + ); + let send_prescan_start = start_decoding & (!state.prescan_start_sent); + send_if(tok, prescan_start_s, send_prescan_start, true); + send_if(tok, code_builder_start_s, send_prescan_start, true); + if (send_prescan_start) { trace_fmt!("Sent START to prescan and code builder"); } else {}; + + let state = if send_prescan_start { + State { + prescan_start_sent: send_prescan_start, + ..state + } + } else { + state + }; let stream_sizes = state.stream_sizes; let (huffman_stream_addr, huffman_stream_len) = match(state.ctrl.new_config, state.ctrl.multi_stream, state.multi_stream_decodings_finished) { @@ -566,16 +591,16 @@ proc HuffmanControlAndSequence_test { uN[TEST_AXI_ADDR_W]:0x3, ]; - for (i, tok) in u32:0..u32:4 { - trace_fmt!("[TEST] Stream #{}", i); + let (tok, prescan_start) = recv(tok, prescan_start_r); + trace_fmt!("[TEST] Received prescan START"); + assert_eq(true, prescan_start); - let (tok, prescan_start) = recv(tok, prescan_start_r); - trace_fmt!("[TEST] Received prescan START"); - assert_eq(true, prescan_start); + let (tok, code_builder_start) = recv(tok, code_builder_start_r); + trace_fmt!("[TEST] Received code builder START"); + assert_eq(true, code_builder_start); - let (tok, code_builder_start) = recv(tok, code_builder_start_r); - trace_fmt!("[TEST] Received code builder START"); - assert_eq(true, code_builder_start); + for (i, tok) in u32:0..u32:4 { + trace_fmt!("[TEST] Stream #{}", i); let (tok, axi_reader_ctrl) = recv(tok, axi_reader_ctrl_r); trace_fmt!("[TEST] Received AXI reader CTRL"); @@ -641,16 +666,16 @@ proc HuffmanControlAndSequence_test { uN[TEST_AXI_ADDR_W]:0x1F, ]; - for (i, tok) in u32:0..u32:4 { - trace_fmt!("[TEST] Stream #{}", i); + let (tok, prescan_start) = recv(tok, prescan_start_r); + trace_fmt!("[TEST] Received prescan START"); + assert_eq(true, prescan_start); - let (tok, prescan_start) = recv(tok, prescan_start_r); - trace_fmt!("[TEST] Received prescan START"); - assert_eq(true, prescan_start); + let (tok, code_builder_start) = recv(tok, code_builder_start_r); + trace_fmt!("[TEST] Received code builder START"); + assert_eq(true, code_builder_start); - let (tok, code_builder_start) = recv(tok, code_builder_start_r); - trace_fmt!("[TEST] Received code builder START"); - assert_eq(true, code_builder_start); + for (i, tok) in u32:0..u32:4 { + trace_fmt!("[TEST] Stream #{}", i); let (tok, axi_reader_ctrl) = recv(tok, axi_reader_ctrl_r); trace_fmt!("[TEST] Received AXI reader CTRL"); diff --git a/xls/modules/zstd/huffman_data_preprocessor.x b/xls/modules/zstd/huffman_data_preprocessor.x index 445f6fed8d..3b27e39dd0 100644 --- a/xls/modules/zstd/huffman_data_preprocessor.x +++ b/xls/modules/zstd/huffman_data_preprocessor.x @@ -34,7 +34,6 @@ enum HuffmanDataPreprocessorFSM: u2 { IDLE = 0, AWAITING_CONFIG = 1, READ_DATA = 2, - PROCESSING = 3, } pub struct HuffmanDataPreprocessorStart { @@ -45,13 +44,20 @@ pub struct HuffmanDataPreprocessorData { data: Data, data_len: CodeLen, code_length: CodeLen[H_DATA_W], + last: bool, } struct HuffmanDataPreprocessorState { fsm: HuffmanDataPreprocessorFSM, lookahead_config: Config, - data: Data, - data_len: CodeLen, + data_in: Data, + data_in_len: CodeLen, + data_in_last: bool, + data_in_ready: bool, + data_out: Data, + data_out_len: CodeLen, + data_out_last: bool, + remove_prefix: bool, } pub proc HuffmanDataPreprocessor { @@ -115,50 +121,70 @@ pub proc HuffmanDataPreprocessor { State { fsm: FSM::READ_DATA, lookahead_config: config, + remove_prefix: true, ..state } } else { state }; // receive data - let do_read_data = state.fsm == FSM::READ_DATA; + let do_read_data = state.fsm == FSM::READ_DATA && (state.data_in_len < H_DATA_W as CodeLen); let (tok, data, data_valid) = recv_if_non_blocking(tok, data_r, do_read_data, zero!()); // process data let state = if data_valid { - trace_fmt!("Received data {:#x}", data); - let fsm = if data.last { - FSM::PROCESSING - } else { - state.fsm - }; + trace_fmt!("Received data {:#b}", data); + trace_fmt!("Data in state {:#b} (length {})", state.data_in, state.data_in_len); State { - fsm: fsm, - data: state.data | ((rev(data.data) as Data) << state.data_len), - data_len: state.data_len + CodeLen:8, + data_in: state.data_in | ((rev(data.data) as Data) << state.data_in_len), + data_in_len: state.data_in_len + CodeLen:8, + data_in_last: data.last, + data_in_ready: data.last || ((state.data_in_len + CodeLen:8) == (H_DATA_W as CodeLen)), ..state } } else { state }; - let processed_data = if state.fsm == FSM::PROCESSING { - let data_bits = state.data; - let data_bits_len = state.data_len; + let state = if state.data_in_ready && state.data_out_len == CodeLen:0 { + State { + data_out: state.data_in, + data_out_len: state.data_in_len, + data_out_last: state.data_in_last, + data_in: uN[H_DATA_W]:0, + data_in_len: CodeLen:0, + data_in_last: false, + data_in_ready: false, + ..state + } + } else { + state + }; - // remove prefix - let (prefix_len, _) = for (i, (prefix_len, stop)): (u32, (u4, bool)) in range(u32:0, MAX_PREFIX_LEN as u32) { - if stop || (data_bits >> i) as u1 { - ( - prefix_len, - true, - ) - } else { - ( - prefix_len + u4:1, - stop, - ) - } - }((u4:1, false)); + let do_process_data = state.data_out_len > CodeLen:0; - trace_fmt!("Prefix len: {}", prefix_len); + let processed_data = if do_process_data { + let data_bits = state.data_out; + let data_bits_len = state.data_out_len; + trace_fmt!("Processing data {:#b} (length {})", state.data_out, state.data_out_len); + + // remove prefix + let prefix_len = if state.remove_prefix { + let (prefix_len, _) = for (i, (prefix_len, stop)): (u32, (u4, bool)) in range(u32:0, MAX_PREFIX_LEN as u32) { + if stop || (data_bits >> i) as u1 { + ( + prefix_len, + true, + ) + } else { + ( + prefix_len + u4:1, + stop, + ) + } + }((u4:1, false)); + trace_fmt!("Prefix len: {}", prefix_len); + prefix_len + } else { + u4:0 + }; let data_bits = data_bits >> prefix_len; let data_bits_len = data_bits_len - prefix_len as CodeLen; @@ -215,18 +241,23 @@ pub proc HuffmanDataPreprocessor { PreprocessedData { data: data_bits, data_len: data_bits_len, + last: state.data_out_last, code_length: code_lengths, } } else { zero!() }; - let tok = send_if(tok, preprocessed_data_s, state.fsm == FSM::PROCESSING, processed_data); + let tok = send_if(tok, preprocessed_data_s, do_process_data, processed_data); + if do_process_data { + trace_fmt!("Sent preprocessed data {:#x} (length {})", processed_data.data, processed_data.data_len); + } else {}; - let state = if state.fsm == FSM::PROCESSING { + let state = if do_process_data { State { - fsm: FSM::IDLE, - lookahead_config: state.lookahead_config, - ..zero!() + data_out: uN[H_DATA_W]:0, + data_out_len: CodeLen:0, + remove_prefix: processed_data.last, + ..state } } else { state }; @@ -311,6 +342,7 @@ const TEST_PREPROCESSED_DATA = HuffmanDataPreprocessorData[2]:[ HuffmanDataPreprocessorData { data: Data:0b110_010_1_010000_010_110_1_1_010000_010, data_len: CodeLen:30, + last: true, code_length: [ CodeLen:3, CodeLen:1, CodeLen:6, CodeLen:6, CodeLen:4, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:3, CodeLen:1, @@ -329,13 +361,14 @@ const TEST_PREPROCESSED_DATA = HuffmanDataPreprocessorData[2]:[ HuffmanDataPreprocessorData { data: Data:0b1_010_100_110_1_010_100_100_001000_1_010_110_1_010_100000_010_101000_1_1_110_010_1, data_len: CodeLen:61, + last: true, code_length: [ CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:1, - CodeLen:1, CodeLen:6, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, - CodeLen:1, CodeLen:9, CodeLen:6, CodeLen:6, CodeLen:6, CodeLen:3, CodeLen:3, CodeLen:1, + CodeLen:1, CodeLen:4, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, + CodeLen:1, CodeLen:6, CodeLen:6, CodeLen:6, CodeLen:4, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:3, - CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:6, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:6, - CodeLen:6, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, + CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:4, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:6, + CodeLen:4, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, CodeLen:0, diff --git a/xls/modules/zstd/huffman_decoder.x b/xls/modules/zstd/huffman_decoder.x index 83bc91959b..154827637a 100644 --- a/xls/modules/zstd/huffman_decoder.x +++ b/xls/modules/zstd/huffman_decoder.x @@ -54,6 +54,7 @@ struct HuffmanDecoderState { symbol_code_len: uN[hcommon::WEIGHT_LOG][SYMBOLS_N], data_len: uN[BUFF_W_LOG2], data: uN[BUFF_W], + data_last: bool, code_length: CodeLen[BUFF_W], decoded_literals: uN[common::SYMBOL_WIDTH][u32:8], decoded_literals_len: u4, @@ -248,6 +249,7 @@ pub proc HuffmanDecoder { fsm: FSM::DECODE, data_len: state.data_len + data.data_len as uN[BUFF_W_LOG2], data: state.data | (data.data as uN[BUFF_W] << state.data_len), + data_last: data.last, code_length: extend_buff_array(state.code_length, state.data_len as u32, data.code_length), ..state } @@ -269,7 +271,8 @@ pub proc HuffmanDecoder { let literals = for (i, literals):(u32, uN[common::SYMBOL_WIDTH][SYMBOLS_N]) in range(u32:0, SYMBOLS_N){ if ( state.symbol_valid[i] && - (data_masked == state.symbol_code[i]) + (data_masked == state.symbol_code[i]) && + (state.code_length[0] == state.symbol_code_len[i] as CodeLen) ) { update(literals, i, i as uN[common::SYMBOL_WIDTH]) } else { @@ -324,8 +327,13 @@ pub proc HuffmanDecoder { let state = if do_send_literals { let fsm = if state.data_len == uN[BUFF_W_LOG2]:0 { - trace_fmt!("{} -> IDLE", state.fsm); - FSM::IDLE + if state.data_last { + trace_fmt!("{} -> IDLE", state.fsm); + FSM::IDLE + } else { + trace_fmt!("{} -> READ_DATA", state.fsm); + FSM::READ_DATA + } } else { trace_fmt!("{} -> DECODE", state.fsm); FSM::DECODE @@ -574,6 +582,7 @@ const TEST_DATA = huffman_data_preprocessor::HuffmanDataPreprocessorData[3]:[ huffman_data_preprocessor::HuffmanDataPreprocessorData { data: huffman_data_preprocessor::Data:0x32a0b682, data_len: CodeLen:30, + last: true, code_length: [ CodeLen:3, CodeLen:1, CodeLen:6, CodeLen:6, CodeLen:4, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:3, CodeLen:1, @@ -592,6 +601,7 @@ const TEST_DATA = huffman_data_preprocessor::HuffmanDataPreprocessorData[3]:[ huffman_data_preprocessor::HuffmanDataPreprocessorData { data: huffman_data_preprocessor::Data:0x32a0b682, data_len: CodeLen:30, + last: true, code_length: [ CodeLen:3, CodeLen:1, CodeLen:6, CodeLen:6, CodeLen:4, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:3, CodeLen:1, @@ -610,6 +620,7 @@ const TEST_DATA = huffman_data_preprocessor::HuffmanDataPreprocessorData[3]:[ huffman_data_preprocessor::HuffmanDataPreprocessorData { data: huffman_data_preprocessor::Data:0b1_010_100_110_1_010_100_100_001000_1_010_110_1_010_100000_010_101000_1_1_110_010_1, data_len: CodeLen:61, + last: true, code_length: [ CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:1, CodeLen:1, CodeLen:1, CodeLen:6, CodeLen:3, CodeLen:3, CodeLen:1, CodeLen:3, CodeLen:1, CodeLen:3, diff --git a/xls/modules/zstd/huffman_literals_dec.x b/xls/modules/zstd/huffman_literals_dec.x index ad396a78c6..55589b9992 100644 --- a/xls/modules/zstd/huffman_literals_dec.x +++ b/xls/modules/zstd/huffman_literals_dec.x @@ -37,11 +37,14 @@ pub type HuffmanLiteralsDecoderReq = ctrl::HuffmanControlAndSequenceCtrl; pub type HuffmanLiteralsDecoderResp = ctrl::HuffmanControlAndSequenceResp; pub type HuffmanLiteralsDecoderStatus = ctrl::HuffmanControlAndSequenceStatus; +pub const RAM_SIZE: u32 = prescan::RAM_SIZE; pub const WEIGHTS_ADDR_WIDTH: u32 = prescan::RAM_ADDR_WIDTH; pub const WEIGHTS_DATA_WIDTH: u32 = prescan::RAM_ACCESS_WIDTH; +pub const WEIGHTS_PARTITION_WORD_SIZE: u32 = WEIGHTS_DATA_WIDTH; pub const WEIGHTS_NUM_PARTITIONS: u32 = u32:1; pub const PRESCAN_ADDR_WIDTH: u32 = prescan::RAM_ADDR_WIDTH; pub const PRESCAN_DATA_WIDTH: u32 = prescan::WeightPreScanMetaDataSize(); +pub const PRESCAN_PARTITION_WORD_SIZE: u32 = PRESCAN_DATA_WIDTH; pub const PRESCAN_NUM_PARTITIONS: u32 = u32:1; pub proc HuffmanLiteralsDecoder< @@ -371,7 +374,7 @@ const TEST_MEMORY: TestAxiRamWrReq[7] = [ // Literals #0 // Length: 6 bytes // New config, 1 Stream - // HTD Header: 0x85 (Direct representation, HTD length: 3 + HTD_header (1 byte)) + // HTD Header: 0x84 (Direct representation, HTD length: 3) // Huffman Tree Description // code symbol length weight // N/A 0x03 0 0 @@ -382,7 +385,7 @@ const TEST_MEMORY: TestAxiRamWrReq[7] = [ // 0b1 0x00 1 4 // 0b00001 padding - TestAxiRamWrReq { addr: TestAxiRamAddr:0x0, data: (u16:0b00001_1_01_0000_0001 ++ u24:0x010234 ++ u8:0x85) as TestAxiRamData, mask: TestAxiRamMask:0xFF }, + TestAxiRamWrReq { addr: TestAxiRamAddr:0x0, data: (u16:0b00001_1_01_0000_0001 ++ u24:0x100234 ++ u8:0x84) as TestAxiRamData, mask: TestAxiRamMask:0xFF }, // AXI addr: 0x0 ^ ^ ^ // Huffman-coded stream HTD HTD Header @@ -397,7 +400,7 @@ const TEST_MEMORY: TestAxiRamWrReq[7] = [ // Literals #2 // Length: 18 bytes // New config, 4 Streams - // HTD Header: 0x85 (Direct representation, HTD length: 3 + HTD_header (1 byte)) + // HTD Header: 0x84 (Direct representation, HTD length: 3 + HTD_header (1 byte)) // Jump Table: 0x0002_0002_0002 (Stream1: 2 bytes; Stream2: 2 bytes; Stream3: 2 bytes) // Huffman Tree Description // code symbol length weight @@ -408,7 +411,7 @@ const TEST_MEMORY: TestAxiRamWrReq[7] = [ // 0b01 0x01 2 3 // 0b1 0x00 1 4 // 0b00001 padding - TestAxiRamWrReq { addr: TestAxiRamAddr:0x40, data: (u32:0x0002_0002 ++ u24:0x010234 ++ u8:0x85) as TestAxiRamData, mask: TestAxiRamMask:0xFF }, + TestAxiRamWrReq { addr: TestAxiRamAddr:0x40, data: (u32:0x0002_0002 ++ u24:0x100234 ++ u8:0x84) as TestAxiRamData, mask: TestAxiRamMask:0xFF }, // AXI addr: 0x200 ^ ^ ^ // Jump table HTD HTD Header TestAxiRamWrReq { addr: TestAxiRamAddr:0x41, data: (u16:0b00001_1_01_0000_0001 ++ u16:0b00001_1_01_0000_0001 ++ u16:0b00001_1_01_0000_0001 ++ u16:0x0002) as TestAxiRamData, mask: TestAxiRamMask:0xFF }, diff --git a/xls/modules/zstd/huffman_prescan.x b/xls/modules/zstd/huffman_prescan.x index 4cc49c9cd5..6acf0284aa 100644 --- a/xls/modules/zstd/huffman_prescan.x +++ b/xls/modules/zstd/huffman_prescan.x @@ -171,6 +171,7 @@ pub proc WeightPreScan next(state: State) { let tok = join(); + trace_fmt!("State {}", state.fsm); let (recv_start, send_addr, write_internal, read_internal, addr) = match state.fsm { FSM::IDLE => (true, false, false, false, u32:0 as ExternalRamAddr), FSM::FIRST_RUN => (false, true, true, false, state.addr as ExternalRamAddr), @@ -184,8 +185,14 @@ pub proc WeightPreScan } }; let (tok1, start) = recv_if(tok, start_r, recv_start, false); - - let (tok2, internal_addr, _) = recv_non_blocking(tok, internal_memory_written_r, state.internal_addr as InternalRamAddr); + if recv_start { + trace_fmt!("Start received"); + } else {}; + + let (tok2, internal_addr, internal_addr_valid) = recv_non_blocking(tok, internal_memory_written_r, state.internal_addr as InternalRamAddr); + if internal_addr_valid { + trace_fmt!("Received internal addr {:#x}", internal_addr); + } else {}; let next_state = match (state.fsm, start, send_addr, state.addr as u32 == MAX_RAM_ADDR - u32:1) { (FSM::IDLE, true, _, _) => State { fsm: FSM::FIRST_RUN, @@ -237,8 +244,14 @@ pub proc WeightPreScan mask: u1:1, }; let tok3 = send_if(tok, read_req_s, send_addr, external_ram_req); + if send_addr { + trace_fmt!("Sent read request {:#x}", external_ram_req); + } else {}; let (tok3, ram_data) = recv_if(tok3, read_rsp_r, send_addr, zero!()); let ram_data = ram_data.data as uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH]; + if send_addr { + trace_fmt!("Received read response {:#x}", ram_data); + } else {}; let internal_ram_r_req = InternalReadReq { addr: addr, @@ -249,11 +262,18 @@ pub proc WeightPreScan let meta_data = BitsToInternalStruct(meta_data_flat.data); let tok34 = join(tok3, tok4); + if read_internal { + trace_fmt!("Reading internal memory data: {:#x}", meta_data); + } else {}; + let prescan_output = OutData { weights: ram_data, meta_data: meta_data }; let tok34 = send_if(tok34, weight_s, send_addr, prescan_output); + if send_addr { + trace_fmt!("Sent output {:#x}", prescan_output); + } else {}; let occurance_matrix = for (i, occurance_matrix) in range(u32:0, PARALLEL_ACCESS_WIDTH) { let row = for (j, row) in range(u32:0, MAX_WEIGHT + u32:1) { @@ -288,6 +308,10 @@ pub proc WeightPreScan let tok5 = send_if(tok, internal_write_req_s, write_internal, internal_ram_w_req); let (tok5, _) = recv_if(tok5, internal_write_rsp_r, write_internal, zero!()); send_if(tok5, internal_memory_written_s, state.fsm == FSM::FIRST_RUN, addr as InternalRamAddr); + if write_internal { + trace_fmt!("Internal write {:#x}", _meta_data); + } else {}; + next_state } } diff --git a/xls/modules/zstd/huffman_weights_dec.x b/xls/modules/zstd/huffman_weights_dec.x index 4dd04173b8..e44b0d11d0 100644 --- a/xls/modules/zstd/huffman_weights_dec.x +++ b/xls/modules/zstd/huffman_weights_dec.x @@ -138,20 +138,9 @@ proc HuffmanRawWeightsDecoder< let (tok, mem_rd_resp, mem_rd_resp_valid) = recv_if_non_blocking(tok, mem_rd_resp_r, do_recv_data, zero!()); if do_recv_data && mem_rd_resp_valid { trace_fmt!("[RAW] Received MemReader response {:#x}", mem_rd_resp); + trace_fmt!("[RAW] Data {:#x}", mem_rd_resp.data); } else {}; - // FIXME: support injecting the last implicit weight for Huffman Tree Descriptions larger - // than a single bus width - // - // ^ This is partially done with saving the sum calculated below in the state. - // When the last mem_reader packet is received (received the last chunk of HTD) - // We should calculate the final sum and use it to calculate the `last_weight` of the HTD - // and put it in a correct spot in the `weights` array - - // Calculate the last weight by summing 2^(weight - 1) - // for each weight read from the HTD (excluding 0's) - // The resulting sum must be subtracted from the next power of 2 after the resulting sum. - // The result is the weight of the last literal. const MAX_WEIGHTS_IN_PACKET = AXI_DATA_W >> u32:2; let weights = mem_rd_resp.data as u4[MAX_WEIGHTS_IN_PACKET]; let sum = for (i, sum): (u32, u32) in u32:0..MAX_WEIGHTS_IN_PACKET { @@ -202,18 +191,16 @@ proc HuffmanRawWeightsDecoder< ) as uN[AXI_DATA_W], _ => fail!("unsupported_axi_data_width", uN[AXI_DATA_W]:0), }; - //trace_fmt!("[RAW] Reversed weights: {:#x}", reversed_weights); - //trace_fmt!("[RAW] BUFF_LEN: {:#x}", BUFF_LEN); - //trace_fmt!("[RAW] WEIGHTS_RAM_DATA_W: {:#x}", WEIGHTS_RAM_DATA_W); - //trace_fmt!("[RAW] AXI_DATA_W: {:#x}", AXI_DATA_W); - //trace_fmt!("[RAW] buffer_len: {:#x}", buffer_len); + + if do_recv_data && mem_rd_resp_valid { + trace_fmt!("[RAW] Weights: {:#x}", weights); + } else {}; if do_recv_data && mem_rd_resp_valid { trace_fmt!("[RAW] Weights: {:#x}", weights); } else {}; let (buffer, buffer_len) = if do_recv_data && mem_rd_resp_valid { - //trace_fmt!("[RAW] shift: {:#x}", BUFF_LEN - AXI_DATA_W - buffer_len as u32); ( buffer | ((reversed_weights as uN[BUFF_LEN] << (BUFF_LEN - AXI_DATA_W - buffer_len as u32))), buffer_len + (AXI_DATA_W as uN[BUFF_LEN_LOG2]), @@ -621,7 +608,9 @@ pub proc HuffmanWeightsDecoder< let tok = send(tok, decoded_weights_sel_s, weights_type == WeightsType::FSE); // FSE - trace_fmt!("Decoding FSE Huffman weights"); + if weights_type == WeightsType::FSE { + trace_fmt!("Decoding FSE Huffman weights"); + } else {}; let fse_weights_req = zero!(); let tok = send_if(tok, fse_weights_req_s, weights_type == WeightsType::FSE, fse_weights_req); let (tok, fse_weights_resp) = recv_if(tok, fse_weights_resp_r, weights_type == WeightsType::FSE, zero!()); @@ -633,7 +622,9 @@ pub proc HuffmanWeightsDecoder< }; // RAW - trace_fmt!("Decoding RAW Huffman weights"); + if weights_type == WeightsType::RAW { + trace_fmt!("Decoding RAW Huffman weights"); + } else {}; let raw_weights_req = RawWeightsReq { addr: req.addr, n_symbols: header_byte - u8:127, @@ -651,13 +642,13 @@ pub proc HuffmanWeightsDecoder< WeightsType::RAW => { Resp { status: raw_status, - tree_description_size: (((header_byte - u8:127) >> u8:1) + u8:1) as uN[AXI_ADDR_W], + tree_description_size: (((header_byte - u8:127) >> u8:1) + u8:1) as uN[AXI_ADDR_W] + uN[AXI_ADDR_W]:1, // include header size } }, WeightsType::FSE => { Resp { status: fse_status, - tree_description_size: header_byte as uN[AXI_ADDR_W], + tree_description_size: header_byte as uN[AXI_ADDR_W] + uN[AXI_ADDR_W]:1, // include header size } }, _ => fail!("impossible_weights_type", zero!()), @@ -975,7 +966,7 @@ proc HuffmanWeightsDecoder_test { let (tok, resp) = recv(tok, resp_r); trace_fmt!("[TEST] Received respose {:#x}", resp); assert_eq(HuffmanWeightsDecoderStatus::OKAY, resp.status); - assert_eq(((TEST_DATA[0] - u8:127 + u8:1) >> u32:1) as uN[TEST_AXI_ADDR_W], resp.tree_description_size); + assert_eq((((TEST_DATA[0] - u8:127 + u8:1) >> u32:1) + u8:1) as uN[TEST_AXI_ADDR_W], resp.tree_description_size); // Insert last weight in test data let last_weight_idx = ((TEST_DATA[0] as u32 - u32:127) / u32:2) + u32:1; diff --git a/xls/modules/zstd/literals_decoder.x b/xls/modules/zstd/literals_decoder.x index 1cf493af93..4aab622bfd 100644 --- a/xls/modules/zstd/literals_decoder.x +++ b/xls/modules/zstd/literals_decoder.x @@ -1531,7 +1531,7 @@ proc LiteralsDecoder_test { // Literals #4 (Huffman; 6 bytes) // Header: 0x01_80_42 (0b0000000110_0000000100_00_10) - AxiRamWrReq { addr: AxiRamAddr:0x10, data: (u8:0b0000_0001 ++ u24:0x010234 ++ u8:0x85 ++ u24:0x01_80_42) as AxiRamData, mask: AxiRamMask:0xFF }, // AXI addr: 0x80 + AxiRamWrReq { addr: AxiRamAddr:0x10, data: (u8:0b0000_0001 ++ u24:0x100234 ++ u8:0x84 ++ u24:0x01_80_42) as AxiRamData, mask: AxiRamMask:0xFF }, // AXI addr: 0x80 AxiRamWrReq { addr: AxiRamAddr:0x11, data: u8:0b00001_1_01 as AxiRamData, mask: AxiRamMask:0xFF }, // AXI addr: 0x88 // Literals #5 (RLE; 12 bytes) @@ -1548,7 +1548,7 @@ proc LiteralsDecoder_test { // Literals #8 (Huffman; 18 bytes) // Header: 0x04_81_06 (0b0000010010_0000010010_01_10) - AxiRamWrReq { addr: AxiRamAddr:0x50, data: (u8:0x02 ++ u24:0x010234 ++ u8:0x85 ++ u24:0x04_81_06) as AxiRamData, mask: AxiRamMask:0xFF }, // AXI addr: 0x280 + AxiRamWrReq { addr: AxiRamAddr:0x50, data: (u8:0x02 ++ u24:0x100234 ++ u8:0x84 ++ u24:0x04_81_06) as AxiRamData, mask: AxiRamMask:0xFF }, // AXI addr: 0x280 AxiRamWrReq { addr: AxiRamAddr:0x51, data: (u8:0b0000_0001 ++ u16:0b00001_1_01_0000_0001 ++ u40:0x0002_0002_00) as AxiRamData, mask: AxiRamMask:0xFF }, // AXI addr: 0x288 AxiRamWrReq { addr: AxiRamAddr:0x52, data: (u16:0b00001_1_01_0000_0001 ++ u16:0b00001_1_01_0000_0001 ++ u8:0b00001_1_01) as AxiRamData, mask: AxiRamMask:0xFF }, // AXI addr: 290 diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x index a115e1f83c..e73e2edffa 100644 --- a/xls/modules/zstd/zstd_dec_test.x +++ b/xls/modules/zstd/zstd_dec_test.x @@ -18,7 +18,6 @@ import xls.modules.zstd.common; import xls.modules.zstd.memory.axi; import xls.modules.zstd.csr_config; import xls.modules.zstd.sequence_executor; -// import xls.modules.zstd.zstd_frame_testcases; import xls.modules.zstd.memory.axi_ram; import xls.modules.zstd.zstd_dec; import xls.modules.zstd.comp_block_dec; @@ -29,7 +28,9 @@ import xls.modules.zstd.parallel_rams; import xls.modules.zstd.literals_buffer; import xls.modules.zstd.fse_table_creator; import xls.modules.zstd.ram_mux; -import xls.modules.zstd.comp_frame; +// import xls.modules.zstd.zstd_frame_testcases as comp_frame; +// import xls.modules.zstd.comp_frame; +import xls.modules.zstd.comp_frame_huffman as comp_frame; const TEST_WINDOW_LOG_MAX = u32:30; @@ -83,12 +84,18 @@ const TEST_TMP_RAM_WORD_PARTITION_SIZE = TEST_TMP_RAM_DATA_W; const TEST_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( TEST_TMP_RAM_WORD_PARTITION_SIZE, TEST_TMP_RAM_DATA_W); +// Huffman weights memory parameters +const HUFFMAN_WEIGHTS_RAM_SIZE: u32 = huffman_literals_dec::RAM_SIZE; const HUFFMAN_WEIGHTS_RAM_ADDR_W: u32 = huffman_literals_dec::WEIGHTS_ADDR_WIDTH; const HUFFMAN_WEIGHTS_RAM_DATA_W: u32 = huffman_literals_dec::WEIGHTS_DATA_WIDTH; +const HUFFMAN_WEIGHTS_RAM_PARTITION_WORD_SIZE: u32 = huffman_literals_dec::WEIGHTS_PARTITION_WORD_SIZE; const HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::WEIGHTS_NUM_PARTITIONS; + // Huffman prescan memory parameters +const HUFFMAN_PRESCAN_RAM_SIZE: u32 = huffman_literals_dec::RAM_SIZE; const HUFFMAN_PRESCAN_RAM_ADDR_W: u32 = huffman_literals_dec::PRESCAN_ADDR_WIDTH; const HUFFMAN_PRESCAN_RAM_DATA_W: u32 = huffman_literals_dec::PRESCAN_DATA_WIDTH; +const HUFFMAN_PRESCAN_RAM_PARTITION_WORD_SIZE: u32 = huffman_literals_dec::PRESCAN_PARTITION_WORD_SIZE; const HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::PRESCAN_NUM_PARTITIONS; const HISTORY_BUFFER_SIZE_KB = common::HISTORY_BUFFER_SIZE_KB; @@ -287,16 +294,32 @@ proc ZstdDecoderTest { let (reset_s, reset_r) = chan<()>("reset"); // Huffman weights memory - let (huffman_lit_weights_mem_rd_req_s, _huffman_lit_weights_mem_rd_req_r) = chan("huffman_lit_weights_mem_rd_req"); - let (_huffman_lit_weights_mem_rd_resp_s, huffman_lit_weights_mem_rd_resp_r) = chan("huffman_lit_weights_mem_rd_resp"); - let (huffman_lit_weights_mem_wr_req_s, _huffman_lit_weights_mem_wr_req_r) = chan("huffman_lit_weights_mem_wr_req"); - let (_huffman_lit_weights_mem_wr_resp_s, huffman_lit_weights_mem_wr_resp_r) = chan("huffman_lit_weights_mem_wr_resp"); + let (huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_req_r) = chan("huffman_lit_weights_mem_rd_req"); + let (huffman_lit_weights_mem_rd_resp_s, huffman_lit_weights_mem_rd_resp_r) = chan("huffman_lit_weights_mem_rd_resp"); + let (huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_req_r) = chan("huffman_lit_weights_mem_wr_req"); + let (huffman_lit_weights_mem_wr_resp_s, huffman_lit_weights_mem_wr_resp_r) = chan("huffman_lit_weights_mem_wr_resp"); + + spawn ram::RamModel< + HUFFMAN_WEIGHTS_RAM_DATA_W, HUFFMAN_WEIGHTS_RAM_SIZE, HUFFMAN_WEIGHTS_RAM_PARTITION_WORD_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED + >( + huffman_lit_weights_mem_rd_req_r, huffman_lit_weights_mem_rd_resp_s, + huffman_lit_weights_mem_wr_req_r, huffman_lit_weights_mem_wr_resp_s, + ); // Huffman prescan memory - let (huffman_lit_prescan_mem_rd_req_s, _huffman_lit_prescan_mem_rd_req_r) = chan("huffman_lit_prescan_mem_rd_req"); - let (_huffman_lit_prescan_mem_rd_resp_s, huffman_lit_prescan_mem_rd_resp_r) = chan("huffman_lit_prescan_mem_rd_resp"); - let (huffman_lit_prescan_mem_wr_req_s, _huffman_lit_prescan_mem_wr_req_r) = chan("huffman_lit_prescan_mem_wr_req"); - let (_huffman_lit_prescan_mem_wr_resp_s, huffman_lit_prescan_mem_wr_resp_r) = chan("huffman_lit_prescan_mem_wr_resp"); + let (huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_req_r) = chan("huffman_lit_prescan_mem_rd_req"); + let (huffman_lit_prescan_mem_rd_resp_s, huffman_lit_prescan_mem_rd_resp_r) = chan("huffman_lit_prescan_mem_rd_resp"); + let (huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_req_r) = chan("huffman_lit_prescan_mem_wr_req"); + let (huffman_lit_prescan_mem_wr_resp_s, huffman_lit_prescan_mem_wr_resp_r) = chan("huffman_lit_prescan_mem_wr_resp"); + + spawn ram::RamModel< + HUFFMAN_PRESCAN_RAM_DATA_W, HUFFMAN_PRESCAN_RAM_SIZE, HUFFMAN_PRESCAN_RAM_PARTITION_WORD_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED + >( + huffman_lit_prescan_mem_rd_req_r, huffman_lit_prescan_mem_rd_resp_s, + huffman_lit_prescan_mem_wr_req_r, huffman_lit_prescan_mem_wr_resp_s, + ); // AXI channels for various blocks let (comp_axi_ar_s, comp_axi_ar_r) = chan[AXI_CHAN_N]("comp_axi_ar"); From 72d517d4c9edcc9ab5872305b368d24e783fc4f8 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Thu, 16 Jan 2025 15:11:27 +0100 Subject: [PATCH 53/85] Fix decoding multiple frames Signed-off-by: Robert Winkler --- xls/modules/zstd/command_constructor.x | 34 +-- xls/modules/zstd/comp_block_dec.x | 170 +++++++++------ xls/modules/zstd/comp_frame.x | 20 +- xls/modules/zstd/dec_mux.x | 22 +- xls/modules/zstd/fse_dec.x | 2 +- xls/modules/zstd/literals_buffer.x | 2 +- xls/modules/zstd/parallel_rams.x | 15 +- xls/modules/zstd/sequence_dec.x | 34 ++- xls/modules/zstd/sequence_executor.x | 284 ++++++++++++++++++------- xls/modules/zstd/zstd_dec.x | 19 +- xls/modules/zstd/zstd_dec_test.x | 1 + 11 files changed, 403 insertions(+), 200 deletions(-) diff --git a/xls/modules/zstd/command_constructor.x b/xls/modules/zstd/command_constructor.x index 9fd3f9eb9d..35d58f31b3 100644 --- a/xls/modules/zstd/command_constructor.x +++ b/xls/modules/zstd/command_constructor.x @@ -27,7 +27,7 @@ type CopyOrMatchContent = common::CopyOrMatchContent; type CopyOrMatchLength = common::CopyOrMatchLength; type SequenceExecutorPacket = common::SequenceExecutorPacket; -type LiteralsBufferCtrl = common::LiteralsBufferCtrl; +type LiteralsBufferCtrl = common::LiteralsBufferCtrl; type CommandConstructorData = common::CommandConstructorData; type ExtendedBlockDataPacket = common::ExtendedBlockDataPacket; type BlockSyncData = common::BlockSyncData; @@ -42,7 +42,7 @@ struct State { status: Status, received_literals: CopyOrMatchLength, literals_to_receive: CopyOrMatchLength, - sync: BlockSyncData, + command: CommandConstructorData, } pub proc CommandConstructor { @@ -64,24 +64,29 @@ pub proc CommandConstructor { let tok0 = join(); let recv_command = state.status == Status::RECV_COMMAND; - let (tok1_0, command) = - recv_if(tok0, sequence_decoder_r, recv_command, zero!()); + let (tok1_0, command) = recv_if(tok0, sequence_decoder_r, recv_command, state.command); + if recv_command { + trace_fmt!("[CommandConstructor] Received command: {:#x}", command); + } else {}; let recv_literals = state.status == Status::RECV_LITERALS; - let (tok1_1, literals) = - recv_if(tok0, literals_buffer_resp_r, recv_literals, zero!()); + let (tok1_1, literals) = recv_if(tok0, literals_buffer_resp_r, recv_literals, zero!()); + if recv_literals { + trace_fmt!("[CommandConstructor] Received literals: {:#x}", literals); + } else {}; let tok1 = join(tok1_0, tok1_1); let (new_state, do_send_command, do_send_literals_req) = match (state.status) { Status::RECV_COMMAND => { - if command.data.msg_type == SequenceExecutorMessageType::LITERAL { + if (command.data.msg_type == SequenceExecutorMessageType::LITERAL) && + (command.data.length != CopyOrMatchLength:0) { ( State { status: Status::RECV_LITERALS, received_literals: CopyOrMatchLength:0, literals_to_receive: command.data.length, - sync: command.sync, + command: command, }, false, true, ) } else { @@ -108,7 +113,7 @@ pub proc CommandConstructor { let resp = match(state.status) { // sent only if the original message was of type SEQUENCE Status::RECV_COMMAND => ExtendedBlockDataPacket { - msg_type: SequenceExecutorMessageType::SEQUENCE, + msg_type: command.data.msg_type, packet: BlockDataPacket { last: command.data.last, last_block: command.sync.last_block, @@ -120,9 +125,9 @@ pub proc CommandConstructor { Status::RECV_LITERALS => ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { - last: literals.last, - last_block: state.sync.last_block, - id: state.sync.id, + last: literals.last & command.data.last, + last_block: command.sync.last_block, + id: command.sync.id, data: literals.content, length: literals.length as u32, // FIXME: remove cast after unifying types of 'length' fields } @@ -130,6 +135,9 @@ pub proc CommandConstructor { _ => fail!("resp_match_unreachable", zero!()) }; send_if(tok1, command_aggregator_s, do_send_command, resp); + if do_send_command { + trace_fmt!("[CommandConstructor] Sending command: {:#x}", resp); + } else {}; new_state } @@ -179,7 +187,7 @@ proc FakeLiteralsBuffer { ) }, FakeLiteralsBufferStatus::SEND => { - let length = std::umin(state.literals_left_to_send, CopyOrMatchLength:64); + let length = std::min(state.literals_left_to_send, CopyOrMatchLength:64); let next_left_to_send = state.literals_left_to_send - length; let last = next_left_to_send == CopyOrMatchLength:0; let resp = SequenceExecutorPacket { diff --git a/xls/modules/zstd/comp_block_dec.x b/xls/modules/zstd/comp_block_dec.x index 9a47caffd1..94a3912ed6 100644 --- a/xls/modules/zstd/comp_block_dec.x +++ b/xls/modules/zstd/comp_block_dec.x @@ -512,27 +512,27 @@ const COMP_BLOCK_DEC_TESTCASES: (u32, u64[64], u32, ExtendedPacket[128])[7] = [ ExtendedPacket[128]:[ ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x431fba7f9f155239, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:0, data: u64:0x431fba7f9f155239, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xe75b86b1dfe3, length: u32:6 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:0, data: u64:0xe75b86b1dfe3, length: u32:6 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x192, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:0, data: u64:0x192, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xd6c643, length: u32:3 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:0, data: u64:0xd6c643, length: u32:3 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x223, length: u32:3 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:0, data: u64:0x223, length: u32:3 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: true, last_block: false, id: u32:1234, data: u64:0x00, length: u32:1 } + packet: BlockDataPacket { last: true, last_block: false, id: u32:0, data: u64:0x00, length: u32:1 } }, zero!(), ... ] @@ -576,43 +576,43 @@ const COMP_BLOCK_DEC_TESTCASES: (u32, u64[64], u32, ExtendedPacket[128])[7] = [ ExtendedPacket[128]:[ ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xa2bbc79280150052, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:1, data: u64:0xa2bbc79280150052, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x2e159be2210a8b13, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:1, data: u64:0x2e159be2210a8b13, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x2bcd291994532c42, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:1, data: u64:0x2bcd291994532c42, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x64de1c37a8940c11, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:1, data: u64:0x64de1c37a8940c11, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x9fa347, length: u32:3 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:1, data: u64:0x9fa347, length: u32:3 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x116, length: u32:4 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:1, data: u64:0x116, length: u32:4 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x9b679780bbc95f95, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:1, data: u64:0x9b679780bbc95f95, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xd90c2f2b9757c107, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:1, data: u64:0xd90c2f2b9757c107, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x58ba369e427a819d, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:1, data: u64:0x58ba369e427a819d, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: true, last_block: false, id: u32:1234, data: u64:0xd27d5a829f, length: u32:5 } + packet: BlockDataPacket { last: true, last_block: false, id: u32:1, data: u64:0xd27d5a829f, length: u32:5 } }, zero!(), ... ] @@ -651,91 +651,127 @@ const COMP_BLOCK_DEC_TESTCASES: (u32, u64[64], u32, ExtendedPacket[128])[7] = [ u64:0x40381ea080, u64:0, ... ], - u32:21, + u32:30, ExtendedPacket[128]:[ + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x0, length: u32:0 } + }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x1f50, length: u32:3 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x1f50, length: u32:3 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x76767676, length: u32:4 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x76767676, length: u32:4 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x21a, length: u32:3 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x21a, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x0, length: u32:0 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x2, length: u32:3 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x2, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x0, length: u32:0 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x1bee, length: u32:3 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x1bee, length: u32:3 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x76, length: u32:1 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x76, length: u32:1 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x2026, length: u32:3 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x2026, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x0, length: u32:0 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x1d93, length: u32:3 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x1d93, length: u32:3 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x76, length: u32:1 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x76, length: u32:1 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x2a39, length: u32:3 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x2a39, length: u32:3 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x76, length: u32:1 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x76, length: u32:1 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x3111, length: u32:3 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x3111, length: u32:3 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x76, length: u32:1 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x76, length: u32:1 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xe76, length: u32:4 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0xe76, length: u32:4 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x0, length: u32:0 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x303d, length: u32:3 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x303d, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x0, length: u32:0 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x3, length: u32:3 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x3, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x0, length: u32:0 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x36ea, length: u32:3 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x36ea, length: u32:3 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: true, last_block: false, id: u32:1234, data: u64:0x7676767676, length: u32:5 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x7676767676, length: u32:5 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x53be, length: u32:3 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x53be, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x0, length: u32:0 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x14ef, length: u32:3 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x14ef, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:2, data: u64:0x0, length: u32:0 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: true, last_block: false, id: u32:1234, data: u64:0x2ce2, length: u32:4 } + packet: BlockDataPacket { last: true, last_block: false, id: u32:2, data: u64:0x2ce2, length: u32:4 } }, zero!(), ... ], @@ -772,67 +808,67 @@ const COMP_BLOCK_DEC_TESTCASES: (u32, u64[64], u32, ExtendedPacket[128])[7] = [ ExtendedPacket[128]:[ ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:3, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5, length: u32:6 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:3, data: u64:0xf5f5f5f5f5f5, length: u32:6 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x2, length: u32:4 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:3, data: u64:0x2, length: u32:4 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5, length: u32:4 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:3, data: u64:0xf5f5f5f5, length: u32:4 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0x4c, length: u32:6 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:3, data: u64:0x4c, length: u32:6 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:3, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:3, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:3, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:3, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:3, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:3, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:3, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:3, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:3, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:3, data: u64:0xf5f5f5f5f5f5f5f5, length: u32:8 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: true, last_block: false, id: u32:1234, data: u64:0xf5f5f5f5, length: u32:4 } + packet: BlockDataPacket { last: true, last_block: false, id: u32:3, data: u64:0xf5f5f5f5, length: u32:4 } }, zero!(), ... ] @@ -865,8 +901,12 @@ const COMP_BLOCK_DEC_TESTCASES: (u32, u64[64], u32, ExtendedPacket[128])[7] = [ u64:0x1501, u64:0, ... ], - u32:0, + u32:1, ExtendedPacket[128]:[ + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: true, last_block: false, id: u32:4, data: u64:0x0, length: u32:0 } + }, zero!(), ... ] ), @@ -897,15 +937,23 @@ const COMP_BLOCK_DEC_TESTCASES: (u32, u64[64], u32, ExtendedPacket[128])[7] = [ u64:0x1020070, u64:0, ... ], - u32:2, + u32:4, ExtendedPacket[128]:[ + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:5, data: u64:0x0, length: u32:0 } + }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: false, last_block: false, id: u32:1234, data: u64:0xf06, length: u32:3 } + packet: BlockDataPacket { last: false, last_block: false, id: u32:5, data: u64:0xf06, length: u32:3 } + }, + ExtendedPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { last: false, last_block: false, id: u32:5, data: u64:0, length: u32:0 } }, ExtendedPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, - packet: BlockDataPacket { last: true, last_block: false, id: u32:1234, data: u64:0x2b77, length: u32:4 } + packet: BlockDataPacket { last: true, last_block: false, id: u32:5, data: u64:0x2b77, length: u32:4 } }, zero!(), ... ], @@ -940,7 +988,7 @@ const COMP_BLOCK_DEC_TESTCASES: (u32, u64[64], u32, ExtendedPacket[128])[7] = [ ExtendedPacket[128]:[ ExtendedPacket { msg_type: SequenceExecutorMessageType::LITERAL, - packet: BlockDataPacket { last: true, last_block: false, id: u32:1234, data: u64:0x215a, length: u32:2 } + packet: BlockDataPacket { last: true, last_block: false, id: u32:6, data: u64:0x215a, length: u32:2 } }, zero!(), ... ], @@ -1306,7 +1354,7 @@ proc CompressBlockDecoderTest { }(tok); let tok = send(tok, ml_sel_test_s, u1:1); - let tok = unroll_for!(test_i, tok): (u32, token) in range(u32:6, array_size(COMP_BLOCK_DEC_TESTCASES)) { + let tok = unroll_for!(test_i, tok): (u32, token) in range(u32:0, array_size(COMP_BLOCK_DEC_TESTCASES)) { let (input_length, input, output_length, output) = COMP_BLOCK_DEC_TESTCASES[test_i]; trace_fmt!("Loading testcase {}", test_i); @@ -1330,7 +1378,7 @@ proc CompressBlockDecoderTest { let req = Req { addr: uN[TEST_AXI_ADDR_W]:0x100, length: input_length as BlockSize, - id: u32:1234, + id: test_i, last_block: false, }; diff --git a/xls/modules/zstd/comp_frame.x b/xls/modules/zstd/comp_frame.x index 5e1a97dbd4..8376d6bf1d 100644 --- a/xls/modules/zstd/comp_frame.x +++ b/xls/modules/zstd/comp_frame.x @@ -5,17 +5,17 @@ pub struct DataArray{ } pub const FRAMES:DataArray< u32:64, - u32:5 ->[1] = [DataArray<64, 5>{ - length: u32:33, - array_length: u32:5, - data: uN[64][5]:[uN[64]:0x001a3384fd2fb528, uN[64]:0xc1d3500000850000, uN[64]:0xdcf0529b98db8a06, uN[64]:0x308fa3120a430001, uN[64]:0x50] + u32:7 +>[1] = [DataArray<64, 7>{ + length: u32:51, + array_length: u32:7, + data: uN[64][7]:[uN[64]:0x00504784fd2fb528, uN[64]:0xcf95700001150000, uN[64]:0xe17d50b989ac93c4, uN[64]:0x0daf000895a6e608, uN[64]:0xb96010b86f7602a4, uN[64]:0x05b0e051238666e8, uN[64]:0x8470e3] }]; pub const DECOMPRESSED_FRAMES:DataArray< u32:64, - u32:4 ->[1] = [DataArray<64, 4>{ - length: u32:26, - array_length: u32:4, - data: uN[64][4]:[uN[64]:0x529b98db8a06c1d3, uN[64]:0x529b98db8a06dcf0, uN[64]:0x529b98db8a06dcf0, uN[64]:0xdcf0] + u32:10 +>[1] = [DataArray<64, 10>{ + length: u32:80, + array_length: u32:10, + data: uN[64][10]:[uN[64]:0xc4c4cf95cf95cf95, uN[64]:0x93c4c4c4c4c4c4c4, uN[64]:0xacc493c493c493c4, uN[64]:0xc493c493c493c489, uN[64]:0x93c493c489acc493, uN[64]:0x08e17d50b9c493c4, uN[64]:0xc4c4c4cf9595a6e6, uN[64]:0x93c493c4c4c4c4c4, uN[64]:0xc489acc493c493c4, uN[64]:0xc493c493c493c493] }]; diff --git a/xls/modules/zstd/dec_mux.x b/xls/modules/zstd/dec_mux.x index 6f0d1d7f8b..681b1b402d 100644 --- a/xls/modules/zstd/dec_mux.x +++ b/xls/modules/zstd/dec_mux.x @@ -130,7 +130,7 @@ pub proc DecoderMux { (((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, + ((state.raw_data.packet.length as CopyOrMatchLength) != CopyOrMatchLength:0, SequenceExecutorPacket { msg_type: state.raw_data.msg_type, length: state.raw_data.packet.length as CopyOrMatchLength, @@ -153,7 +153,7 @@ pub proc DecoderMux { (((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, + ((state.rle_data.packet.length as CopyOrMatchLength) != CopyOrMatchLength:0, SequenceExecutorPacket { msg_type: state.rle_data.msg_type, length: state.rle_data.packet.length as CopyOrMatchLength, @@ -282,9 +282,7 @@ proc DecoderMuxEmptyRawBlocksTest { let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x11111111, length: CopyOrMatchLength:4 }); let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x22222222, length: CopyOrMatchLength:4 }); - 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:4 }); - 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); } @@ -320,9 +318,7 @@ proc DecoderMuxEmptyRleBlocksTest { let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x11111111, length: CopyOrMatchLength:4 }); let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x22222222, length: CopyOrMatchLength:4 }); - 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:4 }); - 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); } @@ -361,7 +357,6 @@ proc DecoderMuxEmptyBlockBetweenRegularBlocksOnTheSameInputChannelTest { let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x11111111, length: CopyOrMatchLength:4 }); let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x22222222, length: CopyOrMatchLength:4 }); let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x33333333, length: CopyOrMatchLength:4 }); - 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:4 }); let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xBBBBBBBB, length: CopyOrMatchLength:4 }); let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: true, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x00000000, length: CopyOrMatchLength:4 }); @@ -403,7 +398,6 @@ proc DecoderMuxEmptyBlockBetweenRegularBlocksOnDifferentInputChannelsTest { let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x11111111, length: CopyOrMatchLength:4 }); let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x22222222, length: CopyOrMatchLength:4 }); let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x33333333, length: CopyOrMatchLength:4 }); - 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:4 }); let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xBBBBBBBB, length: CopyOrMatchLength:4 }); let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: true, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x00000000, length: CopyOrMatchLength:4 }); @@ -474,19 +468,9 @@ proc DecoderMuxMultipleFramesTest { // 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:4 }); 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 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, 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:4 }); - 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); } diff --git a/xls/modules/zstd/fse_dec.x b/xls/modules/zstd/fse_dec.x index 143cd45b7f..e19943a433 100644 --- a/xls/modules/zstd/fse_dec.x +++ b/xls/modules/zstd/fse_dec.x @@ -343,13 +343,13 @@ pub proc FseDecoder< } }; let do_send_command = ( - (command_data.length != CopyOrMatchLength:0) && ( state.fsm == FseDecoderFSM::SEND_COMMAND_LITERAL || state.fsm == FseDecoderFSM::SEND_COMMAND_SEQUENCE || state.fsm == FseDecoderFSM::SEND_LEFTOVER_LITERALS_REQ ) ); + let command = CommandConstructorData { sync: state.ctrl.sync, data: command_data, diff --git a/xls/modules/zstd/literals_buffer.x b/xls/modules/zstd/literals_buffer.x index b1e02c21c8..868953a593 100644 --- a/xls/modules/zstd/literals_buffer.x +++ b/xls/modules/zstd/literals_buffer.x @@ -709,7 +709,7 @@ proc LiteralsBufferReader< last: ctrl_last, }; - let (read_reqs, read_start, read_len, packet, _) = parallel_rams::sequence_packet_to_read_reqs< + let (read_reqs, read_start, read_len, _, _) = parallel_rams::sequence_packet_to_read_reqs< HISTORY_BUFFER_SIZE_KB, RAM_ADDR_WIDTH, RAM_DATA_WIDTH >( state.hyp_ptr, packet, state.hb_len diff --git a/xls/modules/zstd/parallel_rams.x b/xls/modules/zstd/parallel_rams.x index 24f9718326..7e10796cfb 100644 --- a/xls/modules/zstd/parallel_rams.x +++ b/xls/modules/zstd/parallel_rams.x @@ -443,9 +443,20 @@ pub fn sequence_packet_to_read_reqs< 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 max_len = std::min(seq.length as u32, std::min(RAM_NUM, std::min(hb_len as u32, seq.content as u32))); let (curr_seq, next_seq, next_seq_valid) = if seq.length > max_len as CopyOrMatchLength { + ( + seq, + Packet { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: seq.length - max_len as CopyOrMatchLength, + content: seq.content, + last: false, + }, + true, + ) + } else if seq.length > seq.content as CopyOrMatchLength { ( Packet { msg_type: SequenceExecutorMessageType::SEQUENCE, @@ -545,7 +556,7 @@ fn test_sequence_packet_to_read_reqs() { RamReadLen:8, Packet { msg_type: SequenceExecutorMessageType::SEQUENCE, - content: CopyOrMatchContent:18, + content: CopyOrMatchContent:10, length: CopyOrMatchLength:1, last: false }, true, diff --git a/xls/modules/zstd/sequence_dec.x b/xls/modules/zstd/sequence_dec.x index 6f1823df49..e6e1a2eea0 100644 --- a/xls/modules/zstd/sequence_dec.x +++ b/xls/modules/zstd/sequence_dec.x @@ -495,6 +495,13 @@ pub proc SequenceDecoderCtrl< let (tok_recv_scd, conf_resp) = recv(tok_send_scd, scd_resp_r); trace_fmt!("[SequenceDecoderCtrl]: Received decoded Sequence header: {:#x}", conf_resp); + let zero_sequences = (conf_resp.header.sequence_count == u17:0); + if !zero_sequences { + assert!(conf_resp.header.literals_mode != CompressionMode::RLE, "unsupported_fse_table_mode"); + assert!(conf_resp.header.match_mode != CompressionMode::RLE, "unsupported_fse_table_mode"); + assert!(conf_resp.header.offset_mode != CompressionMode::RLE, "unsupported_fse_table_mode"); + } else {}; + // Request decoding lookups let flc_req = FseLookupCtrlReq { addr: req.start_addr + conf_resp.length as Addr, @@ -503,7 +510,6 @@ pub proc SequenceDecoderCtrl< of: (conf_resp.header.offset_mode == CompressionMode::COMPRESSED), }; - let zero_sequences = (conf_resp.header.sequence_count == u17:0); let tok_send_ctrl = send_if(tok_recv_scd, flc_req_s, !zero_sequences, flc_req); if !zero_sequences { trace_fmt!("[SequenceDecoderCtrl]: Sent FseLookupCtrl request: {:#x}", flc_req); @@ -1155,7 +1161,13 @@ const TEST_RAM_DATA = u64[40]:[ u64:0x0, ... ]; -const EXPECTED_OUTPUT = SequenceExecutorPacket[16]:[ +const EXPECTED_OUTPUT = SequenceExecutorPacket[20]:[ + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0000, + content: u64:0x0, + last: false, + }, SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, length: u64:0x0003, @@ -1174,12 +1186,24 @@ const EXPECTED_OUTPUT = SequenceExecutorPacket[16]:[ content: u64:0x0389, last: false, }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0000, + content: u64:0x0, + last: false, + }, SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, length: u64:0x0003, content: u64:0x013c, last: false, }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0000, + content: u64:0x0, + last: false, + }, SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, length: u64:0x0003, @@ -1246,6 +1270,12 @@ const EXPECTED_OUTPUT = SequenceExecutorPacket[16]:[ content: u64:0x03a9, last: false, }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0, + content: u64:0x0, + last: false, + }, SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::SEQUENCE, length: u64:0x0004, diff --git a/xls/modules/zstd/sequence_executor.x b/xls/modules/zstd/sequence_executor.x index cab106b244..861783ce8d 100644 --- a/xls/modules/zstd/sequence_executor.x +++ b/xls/modules/zstd/sequence_executor.x @@ -135,50 +135,40 @@ pub fn handle_repeated_offset_for_sequences -> (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 == Content:0 { - fail!( - "match_offset_zero_not_allowed", - (zero!(), Offset[3]:[Offset:0, ...])) - } else if seq.content == Content:1 { - let offset = modified_repeat_offsets[0]; - ( - Packet { content: offset as Content, ..seq }, - Offset[3]:[ - offset, repeat_offsets[1], repeat_offsets[2], - ], - ) - } else if seq.content == CopyOrMatchContent:2 { - let offset = modified_repeat_offsets[1]; - ( - Packet { content: offset as Content, ..seq }, - Offset[3]:[ - offset, repeat_offsets[0], repeat_offsets[2], - ], - ) - } else if seq.content == CopyOrMatchContent:3 { - let offset = modified_repeat_offsets[2]; - ( - Packet { content: offset as Content, ..seq }, - Offset[3]:[ - offset, repeat_offsets[0], repeat_offsets[1], - ], - ) + let (offset, repeat_offsets) = if (seq.content <= Content:3) { + let idx = (seq.content - Content:1) as u32; + let idx = if (repeat_req) { + idx + u32:1 + } else { idx }; + + if (idx == u32:0) { + (repeat_offsets[0], repeat_offsets) + } else { + let offset = if idx < u32:3 { repeat_offsets[idx] } else { repeat_offsets[0] - Offset:1 }; + + let repeat_offsets = if idx > u32:1 { + update(repeat_offsets, u32:2, repeat_offsets[1]) + } else {repeat_offsets}; + let repeat_offsets = update(repeat_offsets, u32:1, repeat_offsets[0]); + let repeat_offsets = update(repeat_offsets, u32:0, offset); + + (offset, repeat_offsets) + } } else { - let offset = seq.content as Offset - Offset:3; - ( - Packet { content: offset as Content, ..seq }, - Offset[3]:[ - offset, repeat_offsets[0], repeat_offsets[1], - ], - ) + let offset = (seq.content - Content:3) as Offset; + + let repeat_offsets = update(repeat_offsets, u32:2, repeat_offsets[1]); + let repeat_offsets = update(repeat_offsets, u32:1, repeat_offsets[0]); + let repeat_offsets = update(repeat_offsets, u32:0, offset); + + (offset, repeat_offsets) }; - (seq, final_repeat_offsets) + + ( + Packet { content: offset as Content, ..seq }, + repeat_offsets, + ) } pub proc SequenceExecutor()); + if input_packet_valid { + trace_fmt!("[SequenceExecutor]: received input: {:#x}", input_packet); + } else {}; // ... or our own sequences from the looped channel let do_recv_ram = ( @@ -351,6 +344,9 @@ pub proc SequenceExecutor(packet); - if do_write_output { trace_fmt!("Sending output MemWriter data: {:#x}", output_mem_wr_data_in); } else { }; + if do_write_output { + trace_fmt!("*** Sending output MemWriter data: {:#x}", output_mem_wr_data_in); + } else { }; let tok2_10_1 = send_if(tok1, output_mem_wr_data_in_s, do_write_output, output_mem_wr_data_in); // Ask for response @@ -1040,64 +1038,196 @@ proc SequenceExecutorSequenceTest { next(state: ()) { let tok = join(); - // Print RAM content - // let tok = send(tok, print_start_s, ()); - // let (tok, _) = recv(tok, print_finish_r); + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); + + trace_fmt!("----------------------- Packet 0 -----------------------"); let tok = send(tok, input_s, SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::LITERAL, - length: CopyOrMatchLength:1, - content: CopyOrMatchContent:0x31, - last: false + msg_type: SequenceExecutorMessageType::LITERAL, // u1:0, + length: CopyOrMatchLength:0x2, + content: CopyOrMatchContent:0xcf95, + last: false, }); - let (tok, recv_data) = recv(tok, output_mem_wr_data_in_r); - assert_eq(recv_data, TestMemWriterDataPacket { - data: uN[TEST_DATA_W]:0x31, - length: uN[TEST_ADDR_W]:1, - last: false + + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); + + trace_fmt!("----------------------- Packet 1 -----------------------"); + + let tok = send(tok, input_s, SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, // u1:1, + length: CopyOrMatchLength:0x4, + content: CopyOrMatchContent:0x5, + last: false, }); let tok = send(tok, print_start_s, ()); let (tok, _) = recv(tok, print_finish_r); + trace_fmt!("----------------------- Packet 2 -----------------------"); + let tok = send(tok, input_s, SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::SEQUENCE, - length: CopyOrMatchLength:3, - content: CopyOrMatchContent:4, - last: false + msg_type: SequenceExecutorMessageType::LITERAL, // u1:0, + length: CopyOrMatchLength:0x1, + content: CopyOrMatchContent:0xc4, + last: false, }); - let (tok, recv_data) = recv(tok, output_mem_wr_data_in_r); - assert_eq(recv_data, TestMemWriterDataPacket { - data: uN[TEST_DATA_W]:0x31, - length: uN[TEST_ADDR_W]:1, - last: false + + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); + + trace_fmt!("----------------------- Packet 3 -----------------------"); + + let tok = send(tok, input_s, SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, // u1:1, + length: CopyOrMatchLength:0x8, + content: CopyOrMatchContent:0x4, + last: false, }); let tok = send(tok, print_start_s, ()); let (tok, _) = recv(tok, print_finish_r); + trace_fmt!("----------------------- Packet 4 -----------------------"); - let (tok, recv_data) = recv(tok, output_mem_wr_data_in_r); - assert_eq(recv_data, TestMemWriterDataPacket { - data: uN[TEST_DATA_W]:0x3131, - length: uN[TEST_ADDR_W]:2, - last: false + let tok = send(tok, input_s, SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, // u1:0, + length: CopyOrMatchLength:0x1, + content: CopyOrMatchContent:0x93, + last: false, + }); + + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); + + trace_fmt!("----------------------- Packet 5 -----------------------"); + + let tok = send(tok, input_s, SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, // u1:1, + length: CopyOrMatchLength:0x7, + content: CopyOrMatchContent:0x2, + last: false, }); let tok = send(tok, print_start_s, ()); let (tok, _) = recv(tok, print_finish_r); + trace_fmt!("----------------------- Packet 6 -----------------------"); + let tok = send(tok, input_s, SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::SEQUENCE, - length: CopyOrMatchLength:3, - content: CopyOrMatchContent:7, - last: false + msg_type: SequenceExecutorMessageType::LITERAL, // u1:0, + length: CopyOrMatchLength:0x2, + content: CopyOrMatchContent:0x89ac, + last: false, }); - let (tok, recv_data) = recv(tok, output_mem_wr_data_in_r); - assert_eq(recv_data, TestMemWriterDataPacket { - data: uN[TEST_DATA_W]:0x313131, - length: uN[TEST_ADDR_W]:3, - last: false + + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); + + trace_fmt!("----------------------- Packet 7 -----------------------"); + + let tok = send(tok, input_s, SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, // u1:1, + length: CopyOrMatchLength:0x5, + content: CopyOrMatchContent:0xc, + last: false, + }); + + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); + + trace_fmt!("----------------------- Packet 8 -----------------------"); + + let tok = send(tok, input_s, SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, // u1:0, + length: CopyOrMatchLength:0x0, + content: CopyOrMatchContent:0x0, + last: false, + }); + + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); + + trace_fmt!("----------------------- Packet 9 -----------------------"); + + let tok = send(tok, input_s, SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, // u1:1, + length: CopyOrMatchLength:0x8, + content: CopyOrMatchContent:0xe, + last: false, + }); + + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); + + trace_fmt!("----------------------- Packet 10 -----------------------"); + + let tok = send(tok, input_s, SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, // u1:0, + length: CopyOrMatchLength:0x0, + content: CopyOrMatchContent:0x0, + last: false, + }); + + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); + + trace_fmt!("----------------------- Packet 11 -----------------------"); + + let tok = send(tok, input_s, SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, // u1:1, + length: CopyOrMatchLength:0x5, + content: CopyOrMatchContent:0x2, + last: false, + }); + + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); + + trace_fmt!("----------------------- Packet 12 -----------------------"); + + let tok = send(tok, input_s, SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, // u1:0, + length: CopyOrMatchLength:0x8, + content: CopyOrMatchContent:0x95a6_e608_e17d_50b9, + last: false, + }); + + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); + + trace_fmt!("----------------------- Packet 13 -----------------------"); + + let tok = send(tok, input_s, SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, // u1:1, + length: CopyOrMatchLength:0x3, + content: CopyOrMatchContent:0x32, + last: false, + }); + + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); + + trace_fmt!("----------------------- Packet 14 -----------------------"); + + let tok = send(tok, input_s, SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, // u1:0, + length: CopyOrMatchLength:0x0, + content: CopyOrMatchContent:0x0, + last: false, + }); + + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); + + trace_fmt!("----------------------- Packet 15 -----------------------"); + + let tok = send(tok, input_s, SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, // u1:1, + length: CopyOrMatchLength:0x1a, + content: CopyOrMatchContent:0x3, + last: true }); let tok = send(tok, print_start_s, ()); diff --git a/xls/modules/zstd/zstd_dec.x b/xls/modules/zstd/zstd_dec.x index f88227e88e..ba1c3f961c 100644 --- a/xls/modules/zstd/zstd_dec.x +++ b/xls/modules/zstd/zstd_dec.x @@ -489,8 +489,6 @@ proc ZstdDecoderInternal< block_addr + block_length }; - trace_fmt!("bh_addr: {:#x}", bh_addr); - (block_addr, block_length, bh_resp.header.last, block_rle_symbol, bh_addr) } else { (state.block_addr, state.block_length, state.block_last, state.block_rle_symbol, state.bh_addr) @@ -595,8 +593,8 @@ proc ZstdDecoderInternal< ( _, _, _) => Fsm::DECODE_COMPRESSED_BLOCK, }; - let req_sent = if !raw_resp_valid && !error { true } else { false }; - let block_id = if raw_resp_valid { state.block_id + u32:1} else {state.block_id }; + let req_sent = if !cmp_resp_valid && !error { true } else { false }; + let block_id = if cmp_resp_valid { state.block_id + u32:1} else {state.block_id }; let state = State {fsm, block_id, csr_wr_req, csr_wr_req_valid, req_sent, ..state}; if fsm == Fsm::DECODE_BLOCK_HEADER { @@ -630,7 +628,7 @@ proc ZstdDecoderInternal< }, Fsm::FINISH => { - // trace_fmt!("[FINISH]"); + trace_fmt!("[FINISH]"); let csr_wr_req_valid = true; let csr_wr_req = CsrWrReq { csr: csr(Csr::STATUS), @@ -1051,11 +1049,6 @@ pub proc ZstdDecoder< type LitBufRamWrReq = ram::WriteReq; type LitBufRamWrResp = ram::WriteResp; - // Complex Block Decoder - cmp_output_s: chan out; - comp_block_req_s: chan out; - comp_block_resp_r: chan in; - init {} config( @@ -1384,12 +1377,10 @@ pub proc ZstdDecoder< // fse_dec_axi_ar_s, fse_dec_axi_r_r, // ); - (cmd_output_s, comp_block_req_s, comp_block_resp_r) + () } - next (state: ()) { - send_if(join(), cmp_output_s, false, zero!()); - } + next (state: ()) { } } const INST_AXI_DATA_W = u32:64; diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x index e73e2edffa..70c1969973 100644 --- a/xls/modules/zstd/zstd_dec_test.x +++ b/xls/modules/zstd/zstd_dec_test.x @@ -763,6 +763,7 @@ proc ZstdDecoderTest { assert_eq(final_transfered_bytes, DECOMPRESSED_BYTES); assert_eq(final_output_memory_id, decomp_frame.array_length); for (memory_id, _): (u32, ()) in range(u32:0, decomp_frame.array_length) { + trace_fmt!("Comparing {} output packet: {:#x} ?= {:#x}", memory_id, final_output_memory[memory_id], decomp_frame.data[memory_id]); assert_eq(final_output_memory[memory_id], decomp_frame.data[memory_id]); }(()); From bb02038fd48c1d1c0a938b4ca548f96ef8a99dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Ob=C5=82onczek?= Date: Wed, 8 Jan 2025 16:03:30 +0100 Subject: [PATCH 54/85] Add support for Compressed FSE tables Internal-tag: [#71197] --- xls/modules/zstd/BUILD | 9 +- xls/modules/zstd/comp_block_dec.x | 43 +- xls/modules/zstd/fse_lookup_dec.x | 951 ++++++++++++++++++++-- xls/modules/zstd/fse_table_creator.x | 192 +++-- xls/modules/zstd/ram_demux.x | 48 +- xls/modules/zstd/ram_demux3.x | 4 +- xls/modules/zstd/refilling_shift_buffer.x | 4 +- xls/modules/zstd/sequence_dec.x | 918 +++++++++++++-------- xls/modules/zstd/zstd_dec.x | 35 +- xls/modules/zstd/zstd_dec_test.x | 30 +- 10 files changed, 1749 insertions(+), 485 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 3616a980ad..d8f9a033ff 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1238,6 +1238,7 @@ xls_dslx_library( xls_dslx_test( name = "comp_block_dec_dslx_test", library = ":comp_block_dec_dslx", + size = "large", tags = ["manual"], ) @@ -1471,7 +1472,7 @@ xls_dslx_verilog( library = ":fse_table_creator_dslx", opt_ir_args = { "inline_procs": "true", - "top": "__fse_table_creator__FseTableCreatorInst__FseTableCreator_0__8_16_1_8_32_4_8_16_1_next", + "top": "__fse_table_creator__FseTableCreatorInst__FseTableCreator_0__8_16_1_9_32_1_9_8_1_8_16_1_next", }, verilog_file = "fse_table_creator.v", tags = ["manual"], @@ -1651,7 +1652,7 @@ xls_dslx_verilog( library = ":ram_demux_dslx", opt_ir_args = { "inline_procs": "true", - "top": "__ram_demux__RamDemuxInst__RamDemux_0__5_8_0_8_5_next", + "top": "__ram_demux__RamDemuxInst__RamDemux_0__5_8_0_0_8_5_next", }, verilog_file = "ram_demux.v", tags = ["manual"], @@ -1661,7 +1662,7 @@ xls_benchmark_ir( name = "ram_demux_opt_ir_benchmark", src = "ram_demux_verilog.opt.ir", benchmark_ir_args = { - "top": "__ram_demux__RamDemuxInst__RamDemux_0__5_8_0_8_5_next", + "top": "__ram_demux__RamDemuxInst__RamDemux_0__5_8_0_0_8_5_next", }, codegen_args = { "pipeline_stages": "10", @@ -2014,7 +2015,7 @@ xls_dslx_verilog( "multi_proc": "true", }, opt_ir_args = { - "top": "__sequence_dec__FseLookupCtrlInst__FseLookupCtrl_0__32_next" + "top": "__sequence_dec__FseLookupCtrlInst__FseLookupCtrl_0_next" }, dslx_top = "FseLookupCtrlInst", library = ":sequence_dec_dslx", diff --git a/xls/modules/zstd/comp_block_dec.x b/xls/modules/zstd/comp_block_dec.x index 94a3912ed6..276a54cd03 100644 --- a/xls/modules/zstd/comp_block_dec.x +++ b/xls/modules/zstd/comp_block_dec.x @@ -58,6 +58,7 @@ pub proc CompressBlockDecoder< // FSE lookup table RAMs DPD_RAM_ADDR_W: u32, DPD_RAM_DATA_W: u32, DPD_RAM_NUM_PARTITIONS: u32, TMP_RAM_ADDR_W: u32, TMP_RAM_DATA_W: u32, TMP_RAM_NUM_PARTITIONS: u32, + TMP2_RAM_ADDR_W: u32, TMP2_RAM_DATA_W: u32, TMP2_RAM_NUM_PARTITIONS: u32, FSE_RAM_ADDR_W: u32, FSE_RAM_DATA_W: u32, FSE_RAM_NUM_PARTITIONS: u32, // for literals decoder @@ -108,6 +109,11 @@ pub proc CompressBlockDecoder< type TmpRamWrReq = ram::WriteReq; type TmpRamWrResp = ram::WriteResp; + type Tmp2RamRdReq = ram::ReadReq; + type Tmp2RamRdResp = ram::ReadResp; + type Tmp2RamWrReq = ram::WriteReq; + type Tmp2RamWrResp = ram::WriteResp; + type FseRamRdReq = ram::ReadReq; type FseRamRdResp = ram::ReadResp; type FseRamWrReq = ram::WriteReq; @@ -180,6 +186,11 @@ pub proc CompressBlockDecoder< tmp_wr_req_s: chan out, tmp_wr_resp_r: chan in, + tmp2_rd_req_s: chan out, + tmp2_rd_resp_r: chan in, + tmp2_wr_req_s: chan out, + tmp2_wr_resp_r: chan in, + ll_def_fse_rd_req_s: chan out, ll_def_fse_rd_resp_r: chan in, ll_def_fse_wr_req_s: chan out, @@ -330,6 +341,7 @@ pub proc CompressBlockDecoder< AXI_ADDR_W, AXI_DATA_W, AXI_DEST_W, AXI_ID_W, DPD_RAM_ADDR_W, DPD_RAM_DATA_W, DPD_RAM_NUM_PARTITIONS, TMP_RAM_ADDR_W, TMP_RAM_DATA_W, TMP_RAM_NUM_PARTITIONS, + TMP2_RAM_ADDR_W, TMP2_RAM_DATA_W, TMP2_RAM_NUM_PARTITIONS, FSE_RAM_ADDR_W, FSE_RAM_DATA_W, FSE_RAM_NUM_PARTITIONS, >( scd_axi_ar_s, scd_axi_r_r, @@ -339,6 +351,7 @@ pub proc CompressBlockDecoder< seq_dec_command_s, dpd_rd_req_s, dpd_rd_resp_r, dpd_wr_req_s, dpd_wr_resp_r, tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s, tmp2_wr_resp_r, ll_def_fse_rd_req_s, ll_def_fse_rd_resp_r, ll_def_fse_wr_req_s, ll_def_fse_wr_resp_r, ll_fse_rd_req_s, ll_fse_rd_resp_r, ll_fse_wr_req_s, ll_fse_wr_resp_r, ml_def_fse_rd_req_s, ml_def_fse_rd_resp_r, ml_def_fse_wr_req_s, ml_def_fse_wr_resp_r, @@ -436,9 +449,9 @@ const TEST_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( TEST_DPD_RAM_WORD_PARTITION_SIZE, TEST_DPD_RAM_DATA_W); const TEST_FSE_RAM_DATA_W = u32:32; -const TEST_FSE_RAM_SIZE = u32:256; +const TEST_FSE_RAM_SIZE = u32:1 << common::FSE_MAX_ACCURACY_LOG; const TEST_FSE_RAM_ADDR_W = std::clog2(TEST_FSE_RAM_SIZE); -const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_W / u32:3; +const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_W; const TEST_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( TEST_FSE_RAM_WORD_PARTITION_SIZE, TEST_FSE_RAM_DATA_W); @@ -449,6 +462,12 @@ const TEST_TMP_RAM_WORD_PARTITION_SIZE = TEST_TMP_RAM_DATA_W; const TEST_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( TEST_TMP_RAM_WORD_PARTITION_SIZE, TEST_TMP_RAM_DATA_W); +const TEST_TMP2_RAM_DATA_W = u32:8; +const TEST_TMP2_RAM_SIZE = u32:512; +const TEST_TMP2_RAM_ADDR_W = std::clog2(TEST_TMP2_RAM_SIZE); +const TEST_TMP2_RAM_WORD_PARTITION_SIZE = TEST_TMP2_RAM_DATA_W; +const TEST_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_TMP2_RAM_WORD_PARTITION_SIZE, TEST_TMP2_RAM_DATA_W); + const TEST_RAM_SIM_RW_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; const TEST_RAM_INITIALIZED = true; @@ -483,7 +502,7 @@ const COMP_BLOCK_DEC_TESTCASES: (u32, u64[64], u32, ExtendedPacket[128])[7] = [ // raw literals (18) + sequences with 3 predefined tables (2) // // last block generated with: - // ./decodecorpus -pdata2.out -odata2.in -s7110 --block-type=2 --content-size iliteral-type=0 --max-block-size-log=5 + // ./decodecorpus -pdata2.out -odata2.in -s7110 --block-type=2 --content-size --literal-type=0 --max-block-size-log=5 u32:0x1C, u64[64]:[ u64:0x0, u64:0x0, // 0x000 @@ -1022,6 +1041,11 @@ proc CompressBlockDecoderTest { type TmpRamWrReq = ram::WriteReq; type TmpRamWrResp = ram::WriteResp; + type Tmp2RamRdReq = ram::ReadReq; + type Tmp2RamRdResp = ram::ReadResp; + type Tmp2RamWrReq = ram::WriteReq; + type Tmp2RamWrResp = ram::WriteResp; + type FseRamRdReq = ram::ReadReq; type FseRamRdResp = ram::ReadResp; type FseRamWrReq = ram::WriteReq; @@ -1159,6 +1183,17 @@ proc CompressBlockDecoderTest { tmp_rd_req_r, tmp_rd_resp_s, tmp_wr_req_r, tmp_wr_resp_s, ); + let (tmp2_rd_req_s, tmp2_rd_req_r) = chan("tmp2_rd_req"); + let (tmp2_rd_resp_s, tmp2_rd_resp_r) = chan("tmp2_rd_resp"); + let (tmp2_wr_req_s, tmp2_wr_req_r) = chan("tmp2_wr_req"); + let (tmp2_wr_resp_s, tmp2_wr_resp_r) = chan("tmp2_wr_resp"); + spawn ram::RamModel< + TEST_TMP2_RAM_DATA_W, TEST_TMP2_RAM_SIZE, TEST_TMP2_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIM_RW_BEHAVIOR, TEST_RAM_INITIALIZED + >( + tmp2_rd_req_r, tmp2_rd_resp_s, tmp2_wr_req_r, tmp2_wr_resp_s, + ); + // FSE RAMs let (fse_rd_req_s, fse_rd_req_r) = chan[u32:6]("tmp_rd_req"); let (fse_rd_resp_s, fse_rd_resp_r) = chan[u32:6]("tmp_rd_resp"); @@ -1253,6 +1288,7 @@ proc CompressBlockDecoderTest { // FSE lookup table RAMs TEST_DPD_RAM_ADDR_W, TEST_DPD_RAM_DATA_W, TEST_DPD_RAM_NUM_PARTITIONS, TEST_TMP_RAM_ADDR_W, TEST_TMP_RAM_DATA_W, TEST_TMP_RAM_NUM_PARTITIONS, + TEST_TMP2_RAM_ADDR_W, TEST_TMP2_RAM_DATA_W, TEST_TMP2_RAM_NUM_PARTITIONS, TEST_FSE_RAM_ADDR_W, TEST_FSE_RAM_DATA_W, TEST_FSE_RAM_NUM_PARTITIONS, >( req_r, resp_s, @@ -1262,6 +1298,7 @@ proc CompressBlockDecoderTest { axi_ram_ar_s[2], axi_ram_r_r[2], dpd_rd_req_s, dpd_rd_resp_r, dpd_wr_req_s, dpd_wr_resp_r, tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s, tmp2_wr_resp_r, ll_def_fse_rd_req_s, ll_def_fse_rd_resp_r, ll_def_fse_wr_req_s, ll_def_fse_wr_resp_r, fse_rd_req_s[1], fse_rd_resp_r[1], fse_wr_req_s[1], fse_wr_resp_r[1], ml_def_fse_rd_req_s, ml_def_fse_rd_resp_r, ml_def_fse_wr_req_s, ml_def_fse_wr_resp_r, diff --git a/xls/modules/zstd/fse_lookup_dec.x b/xls/modules/zstd/fse_lookup_dec.x index 5c2fa5ec80..7518c0d2bc 100644 --- a/xls/modules/zstd/fse_lookup_dec.x +++ b/xls/modules/zstd/fse_lookup_dec.x @@ -31,9 +31,7 @@ pub enum FseLookupDecoderStatus: u1 { ERROR = 1, } -pub struct FseLookupDecoderReq { - addr: uN[AXI_ADDR_W] -} +pub struct FseLookupDecoderReq {} pub struct FseLookupDecoderResp { status: FseLookupDecoderStatus, @@ -41,29 +39,24 @@ pub struct FseLookupDecoderResp { } pub proc FseLookupDecoder< - AXI_DATA_W: u32, AXI_ADDR_W: u32, + AXI_DATA_W: u32, DPD_RAM_DATA_W: u32, DPD_RAM_ADDR_W: u32, DPD_RAM_NUM_PARTITIONS: u32, TMP_RAM_DATA_W: u32, TMP_RAM_ADDR_W: u32, TMP_RAM_NUM_PARTITIONS: u32, + TMP2_RAM_DATA_W: u32, TMP2_RAM_ADDR_W: u32, TMP2_RAM_NUM_PARTITIONS: u32, FSE_RAM_DATA_W: u32, FSE_RAM_ADDR_W: u32, FSE_RAM_NUM_PARTITIONS: u32, SB_LENGTH_W: u32 = {refilling_shift_buffer::length_width(AXI_DATA_W)}, > { - type Req = FseLookupDecoderReq; + type Req = FseLookupDecoderReq; type Resp = FseLookupDecoderResp; type Status = FseLookupDecoderStatus; type FseTableStart = fse_table_creator::FseStartMsg; - type MemReaderReq = mem_reader::MemReaderReq; - type MemReaderResp = mem_reader::MemReaderResp; - type MemReaderStatus = mem_reader::MemReaderStatus; - type DpdRamWrReq = ram::WriteReq; type DpdRamWrResp = ram::WriteResp; type DpdRamRdReq = ram::ReadReq; type DpdRamRdResp = ram::ReadResp; - type FseRamRdReq = ram::ReadReq; - type FseRamRdResp = ram::ReadResp; type FseRamWrReq = ram::WriteReq; type FseRamWrResp = ram::WriteResp; @@ -72,8 +65,11 @@ pub proc FseLookupDecoder< type TmpRamWrReq = ram::WriteReq; type TmpRamWrResp = ram::WriteResp; - type RefillerStartReq = refilling_shift_buffer::RefillStart; - type RefillerError = refilling_shift_buffer::RefillError; + type Tmp2RamRdReq = ram::ReadReq; + type Tmp2RamRdResp = ram::ReadResp; + type Tmp2RamWrReq = ram::WriteReq; + type Tmp2RamWrResp = ram::WriteResp; + type SBOutput = refilling_shift_buffer::RefillingShiftBufferOutput; type SBCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; @@ -84,10 +80,6 @@ pub proc FseLookupDecoder< req_r: chan in; resp_s: chan out; - start_req_s: chan out; - stop_flush_req_s: chan<()> out; - flushing_done_r: chan<()> in; - fse_pf_dec_req_s: chan out; fse_pf_dec_resp_r: chan in; fse_table_start_s: chan out; @@ -99,9 +91,6 @@ pub proc FseLookupDecoder< req_r: chan in, resp_s: chan out, - mem_rd_req_s: chan out, - mem_rd_resp_r: chan in, - dpd_rd_req_s: chan out, dpd_rd_resp_r: chan in, dpd_wr_req_s: chan out, @@ -112,10 +101,16 @@ pub proc FseLookupDecoder< tmp_wr_req_s: chan out, tmp_wr_resp_r: chan in, - fse_rd_req_s: chan out, - fse_rd_resp_r: chan in, + tmp2_rd_req_s: chan out, + tmp2_rd_resp_r: chan in, + tmp2_wr_req_s: chan out, + tmp2_wr_resp_r: chan in, + fse_wr_req_s: chan out, fse_wr_resp_r: chan in, + + buffer_ctrl_s: chan out, + buffer_data_out_r: chan in, ) { const CHANNEL_DEPTH = u32:1; @@ -126,27 +121,13 @@ pub proc FseLookupDecoder< DPD_RAM_DATA_W, DPD_RAM_ADDR_W, DPD_RAM_NUM_PARTITIONS, FSE_RAM_DATA_W, FSE_RAM_ADDR_W, FSE_RAM_NUM_PARTITIONS, TMP_RAM_DATA_W, TMP_RAM_ADDR_W, TMP_RAM_NUM_PARTITIONS, + TMP2_RAM_DATA_W, TMP2_RAM_ADDR_W, TMP2_RAM_NUM_PARTITIONS, >( fse_table_start_r, fse_table_finish_s, dpd_rd_req_s, dpd_rd_resp_r, - fse_rd_req_s, fse_rd_resp_r, fse_wr_req_s, fse_wr_resp_r, + fse_wr_req_s, fse_wr_resp_r, tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, - ); - - let (start_req_s, start_req_r) = chan("start_req"); - let (stop_flush_req_s, stop_flush_req_r) = chan<(), CHANNEL_DEPTH>("stop_flush_req"); - let (buffer_ctrl_s, buffer_ctrl_r) = chan("buffer_ctrl"); - let (buffer_data_out_s, buffer_data_out_r) = chan("buffer_data_out"); - let (flushing_done_s, flushing_done_r) = chan<(), CHANNEL_DEPTH>("flushing_done"); - - spawn refilling_shift_buffer::RefillingShiftBuffer( - mem_rd_req_s, - mem_rd_resp_r, - start_req_r, - stop_flush_req_r, - buffer_ctrl_r, - buffer_data_out_s, - flushing_done_s, + tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s, tmp2_wr_resp_r, ); let (fse_pf_dec_req_s, fse_pf_dec_req_r) = chan("fse_pf_dec_req"); @@ -162,9 +143,6 @@ pub proc FseLookupDecoder< ( req_r, resp_s, - start_req_s, - stop_flush_req_s, - flushing_done_r, fse_pf_dec_req_s, fse_pf_dec_resp_r, fse_table_start_s, fse_table_finish_r, ) @@ -174,29 +152,22 @@ pub proc FseLookupDecoder< let tok = join(); let (tok, start_req) = recv(tok, req_r); - // start refilling shift buffer - let tok_dec_pf1 = send(tok, start_req_s, RefillerStartReq { - start_addr: start_req.addr - }); // start FSE probability frequency decoder - let tok_dec_pf2 = send(tok, fse_pf_dec_req_s, FsePFDecReq {}); + let tok = send(tok, fse_pf_dec_req_s, FsePFDecReq {}); // wait for completion from FSE probability frequency decoder - let tok = join(tok_dec_pf1, tok_dec_pf2); - let (tok_dec_resp, pf_dec_res) = recv(tok, fse_pf_dec_resp_r); - - // flush refilling shift buffer (regardless of any errors) - let tok_flush = send(tok_dec_resp, stop_flush_req_s, ()); - recv(tok_flush, flushing_done_r); + let (tok, pf_dec_res) = recv(tok, fse_pf_dec_resp_r); + trace_fmt!("FSE prob decoded"); let pf_dec_ok = pf_dec_res.status == FsePFDecStatus::OK; // run FSE Table creation conditional or previous processing succeeding - let tok = send_if(tok_dec_resp, fse_table_start_s, pf_dec_ok, FseTableStart { + let tok = send_if(tok, fse_table_start_s, pf_dec_ok, FseTableStart { num_symbs: pf_dec_res.symbol_count, accuracy_log: pf_dec_res.accuracy_log, }); // wait for completion from FSE table creator let (tok, ()) = recv_if(tok, fse_table_finish_r, pf_dec_ok, ()); + trace_fmt!("FSE table created"); let resp = if pf_dec_ok { Resp { status: Status::OK, accuracy_log: pf_dec_res.accuracy_log } @@ -212,6 +183,7 @@ const TEST_AXI_DATA_WIDTH = u32:64; const TEST_AXI_ADDR_WIDTH = u32:32; const TEST_AXI_ID_WIDTH = u32:8; const TEST_AXI_DEST_WIDTH = u32:8; +const TEST_SB_LENGTH_WIDTH = refilling_shift_buffer::length_width(TEST_AXI_DATA_WIDTH); const TEST_CASE_RAM_DATA_WIDTH = u32:64; const TEST_CASE_RAM_SIZE = u32:256; @@ -229,9 +201,9 @@ const TEST_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( TEST_DPD_RAM_WORD_PARTITION_SIZE, TEST_DPD_RAM_DATA_WIDTH); const TEST_FSE_RAM_DATA_WIDTH = u32:32; -const TEST_FSE_RAM_SIZE = u32:256; +const TEST_FSE_RAM_SIZE = u32:1 << common::FSE_MAX_ACCURACY_LOG; const TEST_FSE_RAM_ADDR_WIDTH = std::clog2(TEST_FSE_RAM_SIZE); -const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_WIDTH / u32:3; +const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_WIDTH; const TEST_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( TEST_FSE_RAM_WORD_PARTITION_SIZE, TEST_FSE_RAM_DATA_WIDTH); @@ -242,12 +214,19 @@ const TEST_TMP_RAM_WORD_PARTITION_SIZE = TEST_TMP_RAM_DATA_WIDTH; const TEST_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( TEST_TMP_RAM_WORD_PARTITION_SIZE, TEST_TMP_RAM_DATA_WIDTH); +const TEST_TMP2_RAM_DATA_WIDTH = u32:8; +const TEST_TMP2_RAM_SIZE = u32:512; +const TEST_TMP2_RAM_ADDR_WIDTH = std::clog2(TEST_TMP2_RAM_SIZE); +const TEST_TMP2_RAM_WORD_PARTITION_SIZE = TEST_TMP2_RAM_DATA_WIDTH; +const TEST_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_TMP2_RAM_WORD_PARTITION_SIZE, TEST_TMP2_RAM_DATA_WIDTH); + const TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; const TEST_RAM_INITIALIZED = true; type FseTableRecord = common::FseTableRecord; -const FSE_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE], FseLookupDecoderResp)[10] = [ +const FSE_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE], FseLookupDecoderResp)[12] = [ ( u64[64]:[u64:0x72AAAAABBB1D25C0, u64:0, ...], FseTableRecord[TEST_FSE_RAM_SIZE]:[ @@ -862,11 +841,792 @@ const FSE_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE], ], FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } ), + ( + u64[64]:[u64:0x604FC0502602814, u64:0xE030505040131FF6, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x3c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x3c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x3c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x3c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x44 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x44 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x44 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x44 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x4c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x4c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x4c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x4c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x54 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x54 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x54 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x54 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x5c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x5c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x5c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x5c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x64 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x64 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x64 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x64 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x6c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x6c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x6c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x6c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x74 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x74 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x74 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x74 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x7c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x7c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x7c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x80 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x80 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x7c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x84 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x84 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x80 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x80 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x88 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x84 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x84 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x8c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x88 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x88 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x88 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x8c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x8c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x8c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x90 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x90 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x90 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x94 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x94 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x94 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x90 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x98 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x98 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x94 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x9c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x9c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x98 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x98 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xa0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x9c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x9c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xa4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xa0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xa0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xa0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xa4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xa4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xa4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xa8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xa8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xa8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xac }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xac }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xac }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xa8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xb0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xb0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xac }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xb4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xb4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xb0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xb0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xb8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xb4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xb4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xbc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xb8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xb8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xb8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xbc }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xbc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xbc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xc0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xc0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xc0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xc4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xc4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xc4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xc8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xc8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xc8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xcc }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xcc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xcc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xd0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xcc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xd0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xd4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xd0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xd0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xd4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xd4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xd4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xd8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xd8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xd8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xd8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xdc }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xdc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xdc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xdc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xe0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xe0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xe0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xe4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xe4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xe0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xe4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xe8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xe4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xe8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xec }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xe8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xe8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xec }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xec }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xec }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xf0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xf0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xf0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xf0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xf4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xf4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xf4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xf4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xf8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xf8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xf8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xfc }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xfc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xf8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xfc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x100 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x100 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x100 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x104 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x104 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xfc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x104 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x108 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x100 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x108 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x10c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x108 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x104 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x10c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x10c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x108 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x110 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x110 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x110 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x10c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x114 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x114 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x110 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x114 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x118 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x118 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x118 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x11c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x11c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x114 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x11c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x120 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x118 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x120 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x124 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x120 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x11c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x124 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x124 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x120 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x128 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x128 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x128 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x124 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x12c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x12c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x128 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x12c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x130 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x130 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x130 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x134 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x134 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x12c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x134 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x138 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x130 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x138 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x13c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x138 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x134 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x13c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x13c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x138 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x140 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x140 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x140 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x13c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x144 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x144 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x140 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x148 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x144 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x148 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x144 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x148 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x14c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x148 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x14c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x14c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x150 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x150 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x150 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x154 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x14c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x154 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x154 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x150 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x158 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x158 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x158 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x154 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x15c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x15c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x158 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x160 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x15c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x160 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x15c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x160 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x164 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x160 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x164 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x164 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x168 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x168 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x168 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x16c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x164 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x16c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x16c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x168 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x170 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x170 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x170 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x16c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x174 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x174 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x170 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x178 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x174 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x178 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x174 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x178 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x17c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x178 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x17c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x17c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x180 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x17c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x180 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x184 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x180 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x180 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x184 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x188 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x184 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x188 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x18c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x184 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x188 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x18c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x188 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x18c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x190 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x190 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x18c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x190 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x194 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x190 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x194 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x194 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x198 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x194 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x198 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x19c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x198 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x198 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x19c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1a0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x19c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1a0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1a4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x19c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1a0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1a4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1a0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1a4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1a8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1a8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1a4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1a8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1ac }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1a8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1ac }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1ac }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1b0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1ac }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1b0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1b4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1b0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1b0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1b4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1b8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1b4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1b8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1bc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1b4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1b8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1bc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1b8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1bc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1c0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1c0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1bc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1c0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1c4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1c0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1c4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1c8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1c4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1c4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1c8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1c8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1c8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1cc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1cc }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1cc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1cc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1d0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1d0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1d0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1d0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1d4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1d4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1d4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1d8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1d8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1d4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1d8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1dc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1d8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1dc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1e0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1dc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1dc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1e0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1e0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1e0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1e4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1e4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1e4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1e4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1e8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1e8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1e8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1e8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1ec }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1ec }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1ec }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1f0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1f0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1ec }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1f0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1f4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1f0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1f4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1f8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1f4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1f4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1f8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1f8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1f8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1fc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1fc }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1fc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1fc }, + ], + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:9 } + ), + ( + u64[64]:[u64:0x140FE03050504013, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x3c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x44 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x3c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x3c }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x3c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x44 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x4c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x54 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x5c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x44 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x4c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x54 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x44 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x4c }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x54 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x4c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x54 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x5c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x64 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x6c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x5c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x64 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x6c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x5c }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x64 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x6c }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x64 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x6c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x74 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x74 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x7c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x80 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x84 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x88 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x74 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x7c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x80 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x84 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x74 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x7c }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x80 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x84 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x88 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x7c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x80 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x84 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x88 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x8c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x8c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x90 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x94 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x98 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x9c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xa0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x88 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x8c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x90 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x94 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x98 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x9c }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x8c }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x90 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x94 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x98 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x9c }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xa0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x90 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x94 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x98 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x9c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xa0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xa4 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xa8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xac }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xb0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xb4 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xb8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xa0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xa4 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xa8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xac }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xb0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xb4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xa4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xa8 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xac }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xb0 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xb4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xb8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xa4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xa8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xac }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xb0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xb4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xb8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xbc }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xc0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xc4 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xc8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xcc }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xb8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xbc }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xc0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xc4 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xc8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xcc }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xbc }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xc0 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xc4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xc8 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xcc }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xd0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xbc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xcc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xd0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xd0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xd4 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xd8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xdc }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xe0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xe4 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xd0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xd4 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xd8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xdc }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xe0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xe4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xd4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xd8 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xdc }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xe0 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xe4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xd4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xd8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xdc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xe0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xe4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xe8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xe8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xec }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xf0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xf4 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xf8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xfc }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xe8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xec }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xf0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xf4 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xf8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xfc }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xe8 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xec }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xf0 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xf4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xf8 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xfc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xec }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xf0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xf4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xf8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xfc }, + zero!(), ... + ], + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:8 } + ), ]; #[test_proc] proc FseLookupDecoderTest { - type Req = FseLookupDecoderReq; + type Req = FseLookupDecoderReq; type Resp = FseLookupDecoderResp; type Status = FseLookupDecoderStatus; @@ -888,11 +1648,20 @@ proc FseLookupDecoderTest { type TmpRamWrReq = ram::WriteReq; type TmpRamWrResp = ram::WriteResp; + type Tmp2RamRdReq = ram::ReadReq; + type Tmp2RamRdResp = ram::ReadResp; + type Tmp2RamWrReq = ram::WriteReq; + type Tmp2RamWrResp = ram::WriteResp; + type TestcaseRamRdReq = ram::ReadReq; type TestcaseRamRdResp = ram::ReadResp; type TestcaseRamWrReq = ram::WriteReq; type TestcaseRamWrResp = ram::WriteResp; + type RefillStartReq = refilling_shift_buffer::RefillStart; + type SBOutput = refilling_shift_buffer::RefillingShiftBufferOutput; + type SBCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + type AxiR = axi::AxiR; type AxiAr = axi::AxiAr; @@ -905,6 +1674,9 @@ proc FseLookupDecoderTest { fse_wr_resp_r: chan in; testcase_wr_req_s: chan out; testcase_wr_resp_r: chan in; + refill_req_s: chan out; + stop_flush_req_s: chan<()> out; + flushing_done_r: chan<()> in; config(terminator: chan out) { let (req_s, req_r) = chan("req"); @@ -922,6 +1694,11 @@ proc FseLookupDecoderTest { let (tmp_wr_req_s, tmp_wr_req_r) = chan("tmp_wr_req"); let (tmp_wr_resp_s, tmp_wr_resp_r) = chan("tmp_wr_resp"); + let (tmp2_rd_req_s, tmp2_rd_req_r) = chan("tmp2_rd_req"); + let (tmp2_rd_resp_s, tmp2_rd_resp_r) = chan("tmp2_rd_resp"); + let (tmp2_wr_req_s, tmp2_wr_req_r) = chan("tmp2_wr_req"); + let (tmp2_wr_resp_s, tmp2_wr_resp_r) = chan("tmp2_wr_resp"); + let (fse_rd_req_s, fse_rd_req_r) = chan("fse_rd_req"); let (fse_rd_resp_s, fse_rd_resp_r) = chan("fse_rd_resp"); let (fse_wr_req_s, fse_wr_req_r) = chan("fse_wr_req"); @@ -932,28 +1709,22 @@ proc FseLookupDecoderTest { let (testcase_wr_req_s, testcase_wr_req_r) = chan("testcase_wr_req"); let (testcase_wr_resp_s, testcase_wr_resp_r) = chan("testcase_wr_resp"); + let (buffer_ctrl_s, buffer_ctrl_r) = chan("buffer_ctrl"); + let (buffer_data_out_s, buffer_data_out_r) = chan("buffer_data_out"); + spawn FseLookupDecoder< - TEST_AXI_DATA_WIDTH, TEST_AXI_ADDR_WIDTH, + TEST_AXI_DATA_WIDTH, TEST_DPD_RAM_DATA_WIDTH, TEST_DPD_RAM_ADDR_WIDTH, TEST_DPD_RAM_NUM_PARTITIONS, TEST_TMP_RAM_DATA_WIDTH, TEST_TMP_RAM_ADDR_WIDTH, TEST_TMP_RAM_NUM_PARTITIONS, + TEST_TMP2_RAM_DATA_WIDTH, TEST_TMP2_RAM_ADDR_WIDTH, TEST_TMP2_RAM_NUM_PARTITIONS, TEST_FSE_RAM_DATA_WIDTH, TEST_FSE_RAM_ADDR_WIDTH, TEST_FSE_RAM_NUM_PARTITIONS, >( - req_r, - resp_s, - mem_rd_req_s, - mem_rd_resp_r, - dpd_rd_req_s, - dpd_rd_resp_r, - dpd_wr_req_s, - dpd_wr_resp_r, - tmp_rd_req_s, - tmp_rd_resp_r, - tmp_wr_req_s, - tmp_wr_resp_r, - fse_rd_req_s, - fse_rd_resp_r, - fse_wr_req_s, - fse_wr_resp_r, + req_r, resp_s, + dpd_rd_req_s, dpd_rd_resp_r, dpd_wr_req_s, dpd_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s, tmp2_wr_resp_r, + fse_wr_req_s, fse_wr_resp_r, + buffer_ctrl_s, buffer_data_out_r, ); spawn ram::RamModel< @@ -971,6 +1742,11 @@ proc FseLookupDecoderTest { TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, >(tmp_rd_req_r, tmp_rd_resp_s, tmp_wr_req_r, tmp_wr_resp_s); + spawn ram::RamModel< + TEST_TMP2_RAM_DATA_WIDTH, TEST_TMP2_RAM_SIZE, TEST_TMP2_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, + >(tmp2_rd_req_r, tmp2_rd_resp_s, tmp2_wr_req_r, tmp2_wr_resp_s); + spawn ram::RamModel< TEST_CASE_RAM_DATA_WIDTH, TEST_CASE_RAM_SIZE, TEST_CASE_RAM_WORD_PARTITION_SIZE, TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, @@ -989,9 +1765,21 @@ proc FseLookupDecoderTest { TEST_AXI_DATA_WIDTH, TEST_AXI_ADDR_WIDTH, TEST_AXI_DEST_WIDTH, TEST_AXI_ID_WIDTH >(mem_rd_req_r, mem_rd_resp_s, testcase_axi_ar_s, testcase_axi_r_r); + let (refill_req_s, refill_req_r) = chan("start_req"); + let (stop_flush_req_s, stop_flush_req_r) = chan<()>("stop_flush_req"); + let (flushing_done_s, flushing_done_r) = chan<()>("flushing_done"); + + spawn refilling_shift_buffer::RefillingShiftBuffer( + mem_rd_req_s, mem_rd_resp_r, + refill_req_r, stop_flush_req_r, + buffer_ctrl_r, buffer_data_out_s, + flushing_done_s, + ); + ( terminator, req_s, resp_r, fse_rd_req_s, fse_rd_resp_r, fse_wr_req_s, fse_wr_resp_r, testcase_wr_req_s, testcase_wr_resp_r, + refill_req_s, stop_flush_req_s, flushing_done_r, ) } @@ -1000,7 +1788,7 @@ proc FseLookupDecoderTest { next(_: ()) { let tok = join(); // This has to be outside of unroll_for!, otherwise typechecker reports type mismatch on identical types - let req_start = Req { addr: uN[TEST_AXI_ADDR_WIDTH]:0x0 }; + let req_start = Req {}; let tok = unroll_for!(test_i, tok): (u32, token) in range(u32:0, array_size(FSE_LOOKUP_DECODER_TESTCASES)) { let (input, output, resp_ok) = FSE_LOOKUP_DECODER_TESTCASES[test_i]; @@ -1018,6 +1806,9 @@ proc FseLookupDecoderTest { }(tok); trace_fmt!("Running FSE lookup decoder on testcase {:x}", test_i); + let tok = send(tok, refill_req_s, RefillStartReq { + start_addr: uN[TEST_AXI_ADDR_WIDTH]:0x0 + }); let tok = send(tok, req_s, req_start); let (tok, resp) = recv(tok, resp_r); assert_eq(resp, resp_ok); @@ -1025,7 +1816,7 @@ proc FseLookupDecoderTest { let tok = for ((i, output_data), tok): ((u32, FseTableRecord), token) in enumerate(output) { let req = FseRamRdReq { addr: i as uN[TEST_FSE_RAM_ADDR_WIDTH], - mask: uN[TEST_FSE_RAM_NUM_PARTITIONS]:0x7, + mask: std::unsigned_max_value(), }; let tok = send(tok, fse_rd_req_s, req); let (tok, resp) = recv(tok, fse_rd_resp_r); @@ -1034,13 +1825,17 @@ proc FseLookupDecoderTest { // erase output for next test to start with clean memory let clear_req = FseRamWrReq { addr: i as uN[TEST_FSE_RAM_ADDR_WIDTH], - mask: uN[TEST_FSE_RAM_NUM_PARTITIONS]:0x7, + mask: std::unsigned_max_value(), data: uN[TEST_FSE_RAM_DATA_WIDTH]:0x0, }; let tok = send(tok, fse_wr_req_s, clear_req); let (tok, _) = recv(tok, fse_wr_resp_r); tok }(tok); + + let tok = send(tok, stop_flush_req_s, ()); + let (tok, ()) = recv(tok, flushing_done_r); + tok }(tok); diff --git a/xls/modules/zstd/fse_table_creator.x b/xls/modules/zstd/fse_table_creator.x index 3eca396d90..2537f13948 100644 --- a/xls/modules/zstd/fse_table_creator.x +++ b/xls/modules/zstd/fse_table_creator.x @@ -44,7 +44,7 @@ enum Status : u4 { struct FseTableCreatorState { status: Status, req: bool, - idx: u8, + idx: u10, // TODO: num_symbs is u8, possibly other fields as well num_symbs: u8, curr_symbol: u8, @@ -94,6 +94,7 @@ pub proc FseTableCreator< FSE_RAM_DATA_WIDTH: u32, FSE_RAM_ADDR_WIDTH: u32, FSE_RAM_NUM_PARTITIONS: u32, // Temp RAM parameters TMP_RAM_DATA_WIDTH: u32, TMP_RAM_ADDR_WIDTH: u32, TMP_RAM_NUM_PARTITIONS: u32, + TMP2_RAM_DATA_WIDTH: u32, TMP2_RAM_ADDR_WIDTH: u32, TMP2_RAM_NUM_PARTITIONS: u32, > { type State = FseTableCreatorState; @@ -102,14 +103,19 @@ pub proc FseTableCreator< type FseRamWriteReq = ram::WriteReq; type FseRamWriteResp = ram::WriteResp; - type FseRamReadReq = ram::ReadReq; - type FseRamReadResp = ram::ReadResp; type TmpRamWriteReq = ram::WriteReq; type TmpRamWriteResp = ram::WriteResp; type TmpRamReadReq = ram::ReadReq; type TmpRamReadResp = ram::ReadResp; + type Tmp2RamWriteReq = ram::WriteReq; + type Tmp2RamWriteResp = ram::WriteResp; + type Tmp2RamReadReq = ram::ReadReq; + type Tmp2RamReadResp = ram::ReadResp; + + type TestRamWriteResp = ram::WriteResp; + type IterCtrl = common::FseTableCreatorCtrl; type IterIndex = common::FseTableIndex; @@ -121,8 +127,6 @@ pub proc FseTableCreator< // a response with information that the table has been saved to RAM fse_table_finish_s: chan<()> out; - fse_rd_req_s: chan out; - fse_rd_resp_r: chan in; fse_wr_req_s: chan out; fse_wr_resp_r: chan in; @@ -131,6 +135,11 @@ pub proc FseTableCreator< tmp_wr_req_s: chan out; tmp_wr_resp_r: chan in; + tmp2_rd_req_s: chan out; + tmp2_rd_resp_r: chan in; + tmp2_wr_req_s: chan out; + tmp2_wr_resp_r: chan in; + it_ctrl_s: chan out; it_index_r: chan in; @@ -143,15 +152,18 @@ pub proc FseTableCreator< dpd_rd_resp_r: chan in, // Ram with FSE decoding table - fse_rd_req_s: chan out, - fse_rd_resp_r: chan in, fse_wr_req_s: chan out, fse_wr_resp_r: chan in, tmp_rd_req_s: chan out, tmp_rd_resp_r: chan in, tmp_wr_req_s: chan out, - tmp_wr_resp_r: chan in + tmp_wr_resp_r: chan in, + + tmp2_rd_req_s: chan out, + tmp2_rd_resp_r: chan in, + tmp2_wr_req_s: chan out, + tmp2_wr_resp_r: chan in, ) { let (it_ctrl_s, it_ctrl_r) = chan("it_ctrl"); let (it_index_s, it_index_r) = chan("it_index"); @@ -160,8 +172,9 @@ pub proc FseTableCreator< ( dpd_rd_req_s, dpd_rd_resp_r, fse_table_start_r, fse_table_finish_s, - fse_rd_req_s, fse_rd_resp_r, fse_wr_req_s, fse_wr_resp_r, + fse_wr_req_s, fse_wr_resp_r, tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s, tmp2_wr_resp_r, it_ctrl_s, it_index_r ) } @@ -173,15 +186,7 @@ pub proc FseTableCreator< const FSE_RAM_REQ_MASK_ALL = std::unsigned_max_value(); const FSE_RAM_REQ_MASK_SYMBOL = uN[FSE_RAM_NUM_PARTITIONS]:1; const TMP_RAM_REQ_MASK_ALL = std::unsigned_max_value(); - - // Type definitions repeated because of https://github.com/google/xls/issues/1368 - type DpdRamReadReq = ram::ReadReq; - type FseRamWriteReq = ram::WriteReq; - type FseRamWriteResp = ram::WriteResp; - type FseRamReadReq = ram::ReadReq; - type TmpRamWriteReq = ram::WriteReq; - type TestRamWriteResp = ram::WriteResp; - type TmpRamReadReq = ram::ReadReq; + const TMP2_RAM_REQ_MASK_ALL = std::unsigned_max_value(); let tok0 = join(); @@ -193,9 +198,14 @@ pub proc FseTableCreator< state.status == Status::HANDLE_POSITIVE_PROB; let send_dpd_req = get_dpd_data && state.req; + let addr = if send_dpd_req { + checked_cast(state.idx) + } else { + uN[DPD_RAM_ADDR_WIDTH]:0 + }; let tok_dpd_req = send_if(tok0, dpd_rd_req_s, send_dpd_req, DpdRamReadReq { - addr: checked_cast(state.idx), + addr: addr, mask: DPD_RAM_REQ_MASK_ALL }); let get_dpd_resp = get_dpd_data && !state.req; @@ -203,37 +213,41 @@ pub proc FseTableCreator< let handle_negative_prob_req = state.status == Status::HANDLE_NEGATIVE_PROB; let decreased_high_threshold = state.high_threshold - u16:1; - let index_as_symbol_record = FseTableRecord { - symbol: state.idx, - num_of_bits: u8:0, - base: u16:0 - }; - let fse_record_as_bits = fse_record_to_bits(index_as_symbol_record); let fse_wr_req = if handle_negative_prob_req { - FseRamWriteReq { - addr: checked_cast(decreased_high_threshold), - data: checked_cast(fse_record_as_bits), - mask: FSE_RAM_REQ_MASK_SYMBOL + Tmp2RamWriteReq { + addr: checked_cast(decreased_high_threshold), + data: checked_cast(state.idx), + mask: TMP2_RAM_REQ_MASK_ALL, } } else { - zero!() + zero!() }; - let tok3 = send_if(tok0, fse_wr_req_s, handle_negative_prob_req, fse_wr_req); + let tok3 = send_if(tok0, tmp2_wr_req_s, handle_negative_prob_req, fse_wr_req); let handle_negative_prob_resp = (state.status == Status::HANDLE_NEGATIVE_PROB); - let (tok3, _) = recv_if(tok3, fse_wr_resp_r, handle_negative_prob_resp, FseRamWriteResp {}); + let (tok3, _) = recv_if(tok3, tmp2_wr_resp_r, handle_negative_prob_resp, FseRamWriteResp {}); + let addr = if handle_negative_prob_req { + checked_cast(state.idx) + } else { + uN[TMP_RAM_ADDR_WIDTH]:0 + }; let tok5 = send_if(tok0, tmp_wr_req_s, handle_negative_prob_req, TmpRamWriteReq { - addr: checked_cast(state.idx), + addr: addr, data: checked_cast(u16:1), mask: TMP_RAM_REQ_MASK_ALL }); let (tok5, _) = recv_if(tok5, tmp_wr_resp_r, handle_negative_prob_resp, TestRamWriteResp {}); let handle_positive_prob_write_state_desc = (state.status == Status::HANDLE_POSITIVE_PROB_WRITE_STATE_DESC); + let addr = if handle_positive_prob_write_state_desc { + checked_cast(state.idx) + } else { + uN[TMP_RAM_ADDR_WIDTH]:0 + }; let tok6 = send_if(tok0, tmp_wr_req_s, handle_positive_prob_write_state_desc, TmpRamWriteReq { - addr: checked_cast(state.idx), + addr: addr, data: checked_cast(state.dpd_data), mask: TMP_RAM_REQ_MASK_ALL } @@ -252,25 +266,30 @@ pub proc FseTableCreator< let (_, pos) = recv_if(tok0, it_index_r, inner_for_get_pos, zero!()); let inner_for_write_sym = state.status == Status::INNER_FOR_WRITE_SYM; - let tok4 = send_if( tok0, fse_wr_req_s, inner_for_write_sym, - FseRamWriteReq { - addr: checked_cast(state.pos), - data: checked_cast(fse_record_as_bits), - mask: FSE_RAM_REQ_MASK_SYMBOL + let idx = if inner_for_write_sym { + checked_cast(state.idx) + } else { + uN[TMP2_RAM_DATA_WIDTH]:0 + }; + let tok4 = send_if( tok0, tmp2_wr_req_s, inner_for_write_sym, + Tmp2RamWriteReq { + addr: checked_cast(state.pos), + data: idx, + mask: TMP2_RAM_REQ_MASK_ALL, } ); - let (tok4, _) = recv_if(tok4, fse_wr_resp_r, inner_for_write_sym, FseRamWriteResp {}); + let (tok4, _) = recv_if(tok4, tmp2_wr_resp_r, inner_for_write_sym, FseRamWriteResp {}); let last_for = state.status == Status::LAST_FOR; - let tok8 = send_if(tok0, fse_rd_req_s, last_for, - FseRamReadReq { - addr: checked_cast(state.idx), - mask: FSE_RAM_REQ_MASK_SYMBOL + let tok8 = send_if(tok0, tmp2_rd_req_s, last_for, + Tmp2RamReadReq { + addr: checked_cast(state.idx), + mask: TMP2_RAM_REQ_MASK_ALL, } ); - let (tok8, fse_resp) = recv_if(tok8, fse_rd_resp_r, last_for, zero!()); - let fse_record = bits_to_fse_record(fse_resp.data); + let (tok8, fse_resp) = recv_if(tok8, tmp2_rd_resp_r, last_for, zero!()); + let fse_record_symbol = fse_resp.data; let get_state_desc = state.status == Status::GET_STATE_DESC; let symbol = state.curr_symbol; @@ -314,7 +333,7 @@ pub proc FseTableCreator< let send_finish = state.status == Status::SEND_FINISH; let tok11 = send_if(tok0, fse_table_finish_s, send_finish, ()); - trace_fmt!("fse lookup state: {:#x}", state); + // trace_fmt!("fse lookup state: {:#x}", state); if state.req && ( state.status == Status::TEST_NEGATIVE_PROB || @@ -337,21 +356,21 @@ pub proc FseTableCreator< if dpd_resp.data == s16:-1 as u16 { State { status: Status::HANDLE_NEGATIVE_PROB, ..state } } else { - let next_idx = state.idx + u8:1; - if next_idx < state.num_symbs { + let next_idx = state.idx + u10:1; + if next_idx < checked_cast(state.num_symbs) { State { status: Status::TEST_NEGATIVE_PROB, req: true, idx: next_idx, ..state } } else { - State { status: Status::START_ITERATING_POS, req: true, idx: u8:0, ..state } + State { status: Status::START_ITERATING_POS, req: true, idx: u10:0, ..state } } } }, Status::HANDLE_NEGATIVE_PROB => { // https://github.com/facebook/zstd/blob/9f42fa0a043aa389534cf10ff086976c4c6b10a6/doc/educational_decoder/zstd_decompress.c#L2143-L2146 - let next_idx = state.idx + u8:1; - if next_idx < state.num_symbs { + let next_idx = state.idx + u10:1; + if next_idx < checked_cast(state.num_symbs) { State { status: Status::TEST_NEGATIVE_PROB, req: true, idx: next_idx, high_threshold: decreased_high_threshold, ..state } } else { - State { status: Status::START_ITERATING_POS, req: true, idx: u8:0, high_threshold: decreased_high_threshold, ..state } + State { status: Status::START_ITERATING_POS, req: true, idx: u10:0, high_threshold: decreased_high_threshold, ..state } } }, Status::START_ITERATING_POS => { @@ -361,11 +380,11 @@ pub proc FseTableCreator< if dpd_resp.data as s16 > s16:0 { State { status: Status::HANDLE_POSITIVE_PROB, req: true, ..state } } else { - let next_idx = state.idx + u8:1; - if next_idx < state.num_symbs { + let next_idx = state.idx + u10:1; + if next_idx < checked_cast(state.num_symbs) { State { status: Status::TEST_POSITIVE_PROB, req: true, idx: next_idx, ..state } } else { - State { status: Status::LAST_FOR, idx: u8:0, ..state } + State { status: Status::LAST_FOR, idx: u10:0, ..state } } } }, @@ -386,24 +405,24 @@ pub proc FseTableCreator< State { status: Status::INNER_FOR_GET_POS, inner_for_idx: next_idx, ..state } } else { assert!(pos == IterIndex:0, "corruption_detected_while_decompressing"); - let next_idx = state.idx + u8:1; - if next_idx < state.num_symbs { + let next_idx = state.idx + u10:1; + if next_idx < checked_cast(state.num_symbs) { State { status: Status::TEST_POSITIVE_PROB, req: true, idx: next_idx, ..state } } else { - State { status: Status::LAST_FOR, idx: u8:0, ..state } + State { status: Status::LAST_FOR, idx: u10:0, ..state } } } }, Status::LAST_FOR => { // https://github.com/facebook/zstd/blob/9f42fa0a043aa389534cf10ff086976c4c6b10a6/doc/educational_decoder/zstd_decompress.c#L2183 - State { status: Status::GET_STATE_DESC, curr_symbol: fse_record.symbol, ..state } + State { status: Status::GET_STATE_DESC, curr_symbol: fse_record_symbol, ..state } }, Status::GET_STATE_DESC => { // https://github.com/facebook/zstd/blob/9f42fa0a043aa389534cf10ff086976c4c6b10a6/doc/educational_decoder/zstd_decompress.c#L2184 State { status: Status::SET_STATE_DESC, state_desc_for_symbol: tmp_resp.data, ..state } }, Status::SET_STATE_DESC => { - let next_idx = state.idx + u8:1; + let next_idx = state.idx + u10:1; if next_idx as u16 < size { State { status: Status::LAST_FOR, idx: next_idx, ..state } } else { @@ -425,9 +444,9 @@ const TEST_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( TEST_DPD_RAM_WORD_PARTITION_SIZE, TEST_DPD_RAM_DATA_WIDTH); const TEST_FSE_RAM_DATA_WIDTH = u32:32; -const TEST_FSE_RAM_SIZE = u32:256; +const TEST_FSE_RAM_SIZE = u32:1 << common::FSE_MAX_ACCURACY_LOG; const TEST_FSE_RAM_ADDR_WIDTH = std::clog2(TEST_FSE_RAM_SIZE); -const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_WIDTH / u32:3; +const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_WIDTH; const TEST_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( TEST_FSE_RAM_WORD_PARTITION_SIZE, TEST_FSE_RAM_DATA_WIDTH); @@ -438,6 +457,13 @@ const TEST_TMP_RAM_WORD_PARTITION_SIZE = TEST_TMP_RAM_DATA_WIDTH; const TEST_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( TEST_TMP_RAM_WORD_PARTITION_SIZE, TEST_TMP_RAM_DATA_WIDTH); +const TEST_TMP2_RAM_DATA_WIDTH = u32:8; +const TEST_TMP2_RAM_SIZE = u32:512; +const TEST_TMP2_RAM_ADDR_WIDTH = std::clog2(TEST_TMP2_RAM_SIZE); +const TEST_TMP2_RAM_WORD_PARTITION_SIZE = TEST_TMP2_RAM_DATA_WIDTH; +const TEST_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_TMP2_RAM_WORD_PARTITION_SIZE, TEST_TMP2_RAM_DATA_WIDTH); + proc FseTableCreatorInst { type DpdRamReadReq = ram::ReadReq; type DpdRamReadResp = ram::ReadResp; @@ -452,6 +478,11 @@ proc FseTableCreatorInst { type TmpRamReadReq = ram::ReadReq; type TmpRamReadResp = ram::ReadResp; + type Tmp2RamWriteReq = ram::WriteReq; + type Tmp2RamWriteResp = ram::WriteResp; + type Tmp2RamReadReq = ram::ReadReq; + type Tmp2RamReadResp = ram::ReadResp; + config( fse_table_start_r: chan in, fse_table_finish_s: chan<()> out, @@ -459,25 +490,30 @@ proc FseTableCreatorInst { dpd_rd_req_s: chan out, dpd_rd_resp_r: chan in, - fse_rd_req_s: chan out, - fse_rd_resp_r: chan in, fse_wr_req_s: chan out, fse_wr_resp_r: chan in, tmp_rd_req_s: chan out, tmp_rd_resp_r: chan in, tmp_wr_req_s: chan out, - tmp_wr_resp_r: chan in + tmp_wr_resp_r: chan in, + + tmp2_rd_req_s: chan out, + tmp2_rd_resp_r: chan in, + tmp2_wr_req_s: chan out, + tmp2_wr_resp_r: chan in, ) { spawn FseTableCreator< TEST_DPD_RAM_DATA_WIDTH, TEST_DPD_RAM_ADDR_WIDTH, TEST_DPD_RAM_NUM_PARTITIONS, TEST_FSE_RAM_DATA_WIDTH, TEST_FSE_RAM_ADDR_WIDTH, TEST_FSE_RAM_NUM_PARTITIONS, TEST_TMP_RAM_DATA_WIDTH, TEST_TMP_RAM_ADDR_WIDTH, TEST_TMP_RAM_NUM_PARTITIONS, + TEST_TMP2_RAM_DATA_WIDTH, TEST_TMP2_RAM_ADDR_WIDTH, TEST_TMP2_RAM_NUM_PARTITIONS, >( fse_table_start_r, fse_table_finish_s, dpd_rd_req_s, dpd_rd_resp_r, - fse_rd_req_s, fse_rd_resp_r, fse_wr_req_s, fse_wr_resp_r, - tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r + fse_wr_req_s, fse_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s,tmp2_wr_resp_r, ); } @@ -538,6 +574,11 @@ proc FseTableCreatorTest { type TmpRamWriteReq = ram::WriteReq; type TmpRamWriteResp = ram::WriteResp; + type Tmp2RamWriteReq = ram::WriteReq; + type Tmp2RamWriteResp = ram::WriteResp; + type Tmp2RamReadReq = ram::ReadReq; + type Tmp2RamReadResp = ram::ReadResp; + terminator: chan out; fse_table_start_s: chan out; fse_table_finish_r: chan<()> in; @@ -576,6 +617,15 @@ proc FseTableCreatorTest { TEST_TMP_RAM_DATA_WIDTH, TEST_TMP_RAM_SIZE, TEST_TMP_RAM_WORD_PARTITION_SIZE>( tmp_rd_req_r, tmp_rd_resp_s, tmp_wr_req_r, tmp_wr_resp_s); + let (tmp2_rd_req_s, tmp2_rd_req_r) = chan("tmp2_rd_req"); + let (tmp2_rd_resp_s, tmp2_rd_resp_r) = chan("tmp2_rd_resp"); + let (tmp2_wr_req_s, tmp2_wr_req_r) = chan("tmp2_wr_req"); + let (tmp2_wr_resp_s, tmp2_wr_resp_r) = chan("tmp2_wr_resp"); + + spawn ram::RamModel< + TEST_TMP2_RAM_DATA_WIDTH, TEST_TMP2_RAM_SIZE, TEST_TMP2_RAM_WORD_PARTITION_SIZE>( + tmp2_rd_req_r, tmp2_rd_resp_s, tmp2_wr_req_r, tmp2_wr_resp_s); + let (fse_table_start_s, fse_table_start_r) = chan("fse_table_start"); let (fse_table_finish_s, fse_table_finish_r) = chan<()>("fse_table_finish"); @@ -583,11 +633,13 @@ proc FseTableCreatorTest { TEST_DPD_RAM_DATA_WIDTH, TEST_DPD_RAM_ADDR_WIDTH, TEST_DPD_RAM_NUM_PARTITIONS, TEST_FSE_RAM_DATA_WIDTH, TEST_FSE_RAM_ADDR_WIDTH, TEST_FSE_RAM_NUM_PARTITIONS, TEST_TMP_RAM_DATA_WIDTH, TEST_TMP_RAM_ADDR_WIDTH, TEST_TMP_RAM_NUM_PARTITIONS, + TEST_TMP2_RAM_DATA_WIDTH, TEST_TMP2_RAM_ADDR_WIDTH, TEST_TMP2_RAM_NUM_PARTITIONS, >( fse_table_start_r, fse_table_finish_s, dpd_rd_req_s, dpd_rd_resp_r, - fse_rd_req_s, fse_rd_resp_r, fse_wr_req_s, fse_wr_resp_r, - tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r + fse_wr_req_s, fse_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s, tmp2_wr_resp_r ); ( diff --git a/xls/modules/zstd/ram_demux.x b/xls/modules/zstd/ram_demux.x index 2b188de92b..d7f99ed574 100644 --- a/xls/modules/zstd/ram_demux.x +++ b/xls/modules/zstd/ram_demux.x @@ -38,6 +38,7 @@ pub proc RamDemux< ADDR_WIDTH: u32, DATA_WIDTH: u32, NUM_PARTITIONS: u32, + INSTANCE: u32 = {u32:0}, INIT_SEL: u1 = {u1:0}, QUEUE_LEN: u32 = {u32:5} > { @@ -120,6 +121,7 @@ pub proc RamDemux< // is less or equal to queue length) let (tok1_0, rd_req, rd_req_valid) = recv_non_blocking(tok, rd_req_r, zero!()); let (sel_q_rd_end, sel_q_rd) = if rd_req_valid { + trace_fmt!("[{:x} Received read request: {:#x}", INSTANCE, rd_req); (sel_q_rd_end << u32:1, (sel_q_rd << u32:1) | ((sel as Queue) << u32:1)) } else { (sel_q_rd_end, sel_q_rd) @@ -127,6 +129,7 @@ pub proc RamDemux< let (tok1_1, wr_req, wr_req_valid) = recv_non_blocking(tok, wr_req_r, zero!()); let (sel_q_wr_end, sel_q_wr) = if wr_req_valid { + trace_fmt!("[{:x} Received write request: {:#x}", INSTANCE, wr_req); (sel_q_wr_end << u32:1, (sel_q_wr << u32:1) | ((sel as Queue) << u32:1)) } else { (sel_q_wr_end, sel_q_wr) @@ -135,16 +138,28 @@ pub proc RamDemux< // send requests to output channel 0 let rd_req0_cond = ((sel_q_rd >> u32:1) as u1 == u1:0 && rd_req_valid); let tok1_2 = send_if(tok, rd_req0_s, rd_req0_cond, rd_req); + if rd_req0_cond { + trace_fmt!("[{:x} Sent read request to channel 0: {:#x}", INSTANCE, rd_req); + } else {}; let wr_req0_cond = ((sel_q_wr >> u32:1) as u1 == u1:0 && wr_req_valid); let tok1_3 = send_if(tok, wr_req0_s, wr_req0_cond, wr_req); + if wr_req0_cond { + trace_fmt!("[{:x} Sent write request to channel 0: {:#x}", INSTANCE, wr_req); + } else {}; // send requests to output channel 1 let rd_req1_cond = ((sel_q_rd >> u32:1) as u1 == u1:1 && rd_req_valid); let tok1_4 = send_if(tok, rd_req1_s, rd_req1_cond, rd_req); + if rd_req1_cond { + trace_fmt!("[{:x} Sent read request to channel 1: {:#x}", INSTANCE, rd_req); + } else {}; let wr_req1_cond = ((sel_q_wr >> u32:1) as u1 == u1:1 && wr_req_valid); let tok1_5 = send_if(tok, wr_req1_s, wr_req1_cond, wr_req); + if wr_req1_cond { + trace_fmt!("[{:x} Sent write request to channel 1: {:#x}", INSTANCE, wr_req); + } else {}; // join tokens let tok1 = join(tok1_0, tok1_1, tok1_2, tok1_3, tok1_4, tok1_5); @@ -156,15 +171,28 @@ pub proc RamDemux< // receive responses from output channel 0 let (tok2_0, rd_resp0, rd_resp0_valid) = recv_if_non_blocking(tok1, rd_resp0_r, rd_resp_ch == u1:0, zero!()); + if rd_resp0_valid { + trace_fmt!("[{:x} Received read response on channel 0: {:#x}", INSTANCE, rd_resp0); + } else {}; let (tok2_1, wr_resp0, wr_resp0_valid) = recv_if_non_blocking(tok1, wr_resp0_r, wr_resp_ch == u1:0, zero!()); - + if wr_resp0_valid { + trace_fmt!("[{:x} Received write response on channel 0: {:#x}", INSTANCE, wr_resp0); + } else {}; + // receive responses from output channel 1 let (tok2_2, rd_resp1, rd_resp1_valid) = recv_if_non_blocking(tok1, rd_resp1_r, rd_resp_ch == u1:1, zero!()); + if rd_resp1_valid { + trace_fmt!("[{:x} Received read response on channel 1: {:#x}", INSTANCE, rd_resp1); + } else {}; + let (tok2_3, wr_resp1, wr_resp1_valid) = recv_if_non_blocking(tok1, wr_resp1_r, wr_resp_ch == u1:1, zero!()); - + if wr_resp1_valid { + trace_fmt!("[{:x} Received write response on channel 1: {:#x}", INSTANCE, wr_resp1); + } else {}; + // prepare read output values let (rd_resp, rd_resp_valid) = if rd_resp_ch == u1:0 { (rd_resp0, rd_resp0_valid) @@ -181,15 +209,29 @@ pub proc RamDemux< // send responses to input channel let tok2_4 = send_if(tok1, rd_resp_s, rd_resp_valid, rd_resp); + if rd_resp_valid { + trace_fmt!("[{:x} Sent read response: {:#x}", INSTANCE, rd_resp); + } else {}; + let sel_q_rd_end = if rd_resp_valid { sel_q_rd_end >> u32:1 } else { sel_q_rd_end }; let tok2_5 = send_if(tok1, wr_resp_s, wr_resp_valid, wr_resp); + if wr_resp_valid { + trace_fmt!("[{:x} Sent write response: {:#x}", INSTANCE, wr_resp); + } else {}; + let sel_q_wr_end = if wr_resp_valid { sel_q_wr_end >> u32:1 } else { sel_q_wr_end }; // handle select let (tok1_6, sel, sel_valid) = recv_non_blocking(tok, sel_req_r, sel); + if sel_valid { + trace_fmt!("[{:x} Received select: {:#x}", INSTANCE, sel); + } else {}; let tok1_7 = send_if(tok1_6, sel_resp_s, sel_valid, ()); + if sel_valid { + trace_fmt!("[{:x} Sent select response", INSTANCE); + } else {}; RamDemuxState { sel, sel_q_rd, sel_q_wr, sel_q_rd_end, sel_q_wr_end } } @@ -261,7 +303,7 @@ proc RamDemuxTest { let (wr_resp1_s, wr_resp1_r) = chan("wr_resp1"); spawn RamDemux< - TEST_RAM_ADDR_WIDTH, TEST_RAM_DATA_WIDTH, TEST_RAM_NUM_PARTITIONS, + TEST_RAM_ADDR_WIDTH, TEST_RAM_DATA_WIDTH, TEST_RAM_NUM_PARTITIONS, u32:0, TEST_DEMUX_INIT_SEL, TEST_DEMUX_QUEUE_LEN >( sel_req_r, sel_resp_s, diff --git a/xls/modules/zstd/ram_demux3.x b/xls/modules/zstd/ram_demux3.x index 1548c4aa03..3fd427002e 100644 --- a/xls/modules/zstd/ram_demux3.x +++ b/xls/modules/zstd/ram_demux3.x @@ -79,7 +79,7 @@ pub proc RamDemux3< let (tmp_wr_req_s, tmp_wr_req_r) = chan("tmp_wr_req"); let (tmp_wr_resp_s, tmp_wr_resp_r) = chan("tmp_wr_resp"); - spawn ram_demux::RamDemux( + spawn ram_demux::RamDemux( d1_sel_req_r, d1_sel_resp_s, rd_req_r, rd_resp_s, wr_req_r, wr_resp_s, rd_req0_s, rd_resp0_r, wr_req0_s, wr_resp0_r, @@ -89,7 +89,7 @@ pub proc RamDemux3< let (d2_sel_req_s, d2_sel_req_r) = chan("d2_sel_req"); let (d2_sel_resp_s, d2_sel_resp_r) = chan<(), CHANNEL_DEPTH>("d2_sel_resp"); - spawn ram_demux::RamDemux( + spawn ram_demux::RamDemux( d2_sel_req_r, d2_sel_resp_s, tmp_rd_req_r, tmp_rd_resp_s, tmp_wr_req_r, tmp_wr_resp_s, rd_req1_s, rd_resp1_r, wr_req1_s, wr_resp1_r, diff --git a/xls/modules/zstd/refilling_shift_buffer.x b/xls/modules/zstd/refilling_shift_buffer.x index 3be573f8ef..7e6c8bcac2 100644 --- a/xls/modules/zstd/refilling_shift_buffer.x +++ b/xls/modules/zstd/refilling_shift_buffer.x @@ -96,7 +96,7 @@ proc RefillingShiftBufferInternal< reader_req_s: chan out; reader_resp_r: chan in; - start_req_r: chan in; + start_req_r: chan in; stop_flush_req_r: chan<()> in; buffer_data_in_s: chan out; buffer_data_out_s: chan out; @@ -108,7 +108,7 @@ proc RefillingShiftBufferInternal< config( reader_req_s: chan out, reader_resp_r: chan in, - start_req_r: chan in, + start_req_r: chan in, stop_flush_req_r: chan<()> in, buffer_ctrl_r: chan in, buffer_data_out_s: chan out, diff --git a/xls/modules/zstd/sequence_dec.x b/xls/modules/zstd/sequence_dec.x index e6e1a2eea0..294c4855c8 100644 --- a/xls/modules/zstd/sequence_dec.x +++ b/xls/modules/zstd/sequence_dec.x @@ -68,11 +68,10 @@ struct SequenceDecoderState { conf_resp: sequence_conf_dec::SequenceConfDecoderResp, } -struct FseLookupCtrlReq { +struct FseLookupCtrlReq { ll: bool, ml: bool, of: bool, - addr: uN[AXI_ADDR_W], } type AccuracyLog = common::FseAccuracyLog; @@ -82,20 +81,19 @@ struct FseLookupCtrlResp { of_accuracy_log: AccuracyLog, } -struct FseLookupCtrlState { +struct FseLookupCtrlState { decode: bool[3], decode_valid: bool, - addr: uN[AXI_ADDR_W], resp: FseLookupCtrlResp, cnt: u2, } -pub proc FseLookupCtrl { - type Req = FseLookupCtrlReq; +pub proc FseLookupCtrl { + type Req = FseLookupCtrlReq; type Resp = FseLookupCtrlResp; - type State = FseLookupCtrlState; + type State = FseLookupCtrlState; - type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; + type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; type FseLookupDecoderResp = fse_lookup_dec::FseLookupDecoderResp; req_r: chan in; @@ -135,7 +133,6 @@ pub proc FseLookupCtrl { decode: bool[3]:[req.ll, req.of, req.ml], decode_valid: true, cnt: u2:0, - addr: req.addr, ..zero!() } } else { @@ -159,14 +156,11 @@ pub proc FseLookupCtrl { } else {}; // trace_fmt!("Received response from demux"); - let fld_req = FseLookupDecoderReq { addr: state.addr }; - let tok3 = send_if(tok2, fld_req_s, do_set, fld_req); + let tok3 = send_if(tok2, fld_req_s, do_set, FseLookupDecoderReq {}); if do_set { - trace_fmt!("[SequenceDecoderCtrl/FseLookupCtrl]: Sent FseLookupDecoder req {:#x}", fld_req); + trace_fmt!("[SequenceDecoderCtrl/FseLookupCtrl]: Sent FseLookupDecoder req"); } else {}; - // FIXME: Extract RefillingShiftBuffer from the FseLookupDecoder - // to support decoding multiple FSE Tables let (tok4, fld_resp) = recv_if(tok3, fld_resp_r, do_set, zero!()); if do_set { trace_fmt!("[SequenceDecoderCtrl/FseLookupCtrl]: Received FseLookupDecoder resp {:#x}", fld_resp); @@ -191,13 +185,11 @@ pub proc FseLookupCtrl { } } -const INST_AXI_ADDR_W = u32:32; - pub proc FseLookupCtrlInst { - type Req = FseLookupCtrlReq; + type Req = FseLookupCtrlReq; type Resp = FseLookupCtrlResp; - type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; + type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; type FseLookupDecoderResp = fse_lookup_dec::FseLookupDecoderResp; init { } @@ -212,7 +204,7 @@ pub proc FseLookupCtrlInst { demux_req_s: chan out, demux_resp_r: chan<()> in, ) { - spawn FseLookupCtrl( + spawn FseLookupCtrl( req_r, resp_s, fld_req_s, fld_resp_r, demux_req_s, demux_resp_r, @@ -232,7 +224,7 @@ const TEST_FLC_AXI_ADDR_W = u32:32; // // type Addr = uN[TEST_FLC_AXI_ADDR_W]; // -// type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; +// type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; // type FseLookupDecoderResp = fse_lookup_dec::FseLookupDecoderResp; // type FseLookupDecoderStatus = fse_lookup_dec::FseLookupDecoderStatus; // @@ -390,7 +382,7 @@ pub proc SequenceDecoderCtrl< type SequenceConfDecoderReq = sequence_conf_dec::SequenceConfDecoderReq; type SequenceConfDecoderResp = sequence_conf_dec::SequenceConfDecoderResp; - type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; + type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; type FseLookupDecoderResp = fse_lookup_dec::FseLookupDecoderResp; type RefillingShiftBufferStart = refilling_shift_buffer::RefillStart; @@ -423,6 +415,10 @@ pub proc SequenceDecoderCtrl< fd_rsb_stop_flush_req_s: chan<()> out; fd_rsb_flushing_done_r: chan<()> in; + fld_rsb_start_req_s: chan out; + fld_rsb_stop_flush_req_s: chan<()> out; + fld_rsb_flushing_done_r: chan<()> in; + fd_ctrl_s: chan out; fd_finish_r: chan in; @@ -454,6 +450,10 @@ pub proc SequenceDecoderCtrl< fd_rsb_stop_flush_req_s: chan<()> out, fd_rsb_flushing_done_r: chan<()> in, + fld_rsb_start_req_s: chan out, + fld_rsb_stop_flush_req_s: chan<()> out, + fld_rsb_flushing_done_r: chan<()> in, + fd_ctrl_s: chan out, fd_finish_r: chan in, ) { @@ -462,7 +462,7 @@ pub proc SequenceDecoderCtrl< let (flc_req_s, flc_req_r) = chan("flc_req"); let (flc_resp_s, flc_resp_r) = chan("flc_resp"); - spawn FseLookupCtrl( + spawn FseLookupCtrl( flc_req_r, flc_resp_s, fld_req_s, fld_resp_r, fld_demux_req_s, fld_demux_resp_r, @@ -476,6 +476,7 @@ pub proc SequenceDecoderCtrl< of_demux_req_s, of_demux_resp_r, ml_demux_req_s, ml_demux_resp_r, fd_rsb_start_req_s, fd_rsb_stop_flush_req_s, fd_rsb_flushing_done_r, + fld_rsb_start_req_s, fld_rsb_stop_flush_req_s, fld_rsb_flushing_done_r, fd_ctrl_s, fd_finish_r, ) } @@ -502,9 +503,13 @@ pub proc SequenceDecoderCtrl< assert!(conf_resp.header.offset_mode != CompressionMode::RLE, "unsupported_fse_table_mode"); } else {}; + // Start RefillingShiftBuffer for decoding lookups + let tok_dec_lookup = send(tok_recv_scd, fld_rsb_start_req_s, RefillingShiftBufferStart { + start_addr: req.start_addr + conf_resp.length as Addr, + }); + // Request decoding lookups let flc_req = FseLookupCtrlReq { - addr: req.start_addr + conf_resp.length as Addr, ll: (conf_resp.header.literals_mode == CompressionMode::COMPRESSED), ml: (conf_resp.header.match_mode == CompressionMode::COMPRESSED), of: (conf_resp.header.offset_mode == CompressionMode::COMPRESSED), @@ -515,9 +520,13 @@ pub proc SequenceDecoderCtrl< trace_fmt!("[SequenceDecoderCtrl]: Sent FseLookupCtrl request: {:#x}", flc_req); } else {}; - // Receive response about deoded lookups + // Receive response about decoded lookups let (tok_recv_ctrl, flc_resp) = recv_if(tok_send_ctrl, flc_resp_r, !zero_sequences, zero!()); + // We've finished decoding lookups - flush the corresponding refilling shift buffer + let tok_fld_rsb_flush = send(tok_recv_ctrl, fld_rsb_stop_flush_req_s, ()); + let (tok_fld_rsb_flush_done, ()) = recv(tok_fld_rsb_flush, fld_rsb_flushing_done_r); + // Set proper LL lookup through demux let ll_demux_sel = (conf_resp.header.literals_mode != CompressionMode::PREDEFINED); let tok_ll_demux = send_if(tok_recv_scd, ll_demux_req_s, !zero_sequences, ll_demux_sel); @@ -559,11 +568,13 @@ pub proc SequenceDecoderCtrl< let tok_rsb_flush = send_if(tok_fse_dec, fd_rsb_stop_flush_req_s, !zero_sequences, ()); trace_fmt!("[SequenceDecoderCtrl]: Send flush request"); - let (tok_rsb_flush_done, _) = recv_if(tok_rsb_flush, fd_rsb_flushing_done_r, !zero_sequences, ()); + let (tok_rsb_flush_done, ()) = recv_if(tok_rsb_flush, fd_rsb_flushing_done_r, !zero_sequences, ()); trace_fmt!("[SequenceDecoderCtrl]: Flush done"); + // Send response once both refilling shift buffers have been flushed let resp = SequenceDecoderResp { status: Status::OK }; - send(tok_rsb_flush_done, sd_resp_s, resp); + let tok_flush_done = join(tok_fld_rsb_flush_done, tok_rsb_flush_done); + send(tok_flush_done, sd_resp_s, resp); } } @@ -587,7 +598,7 @@ const SDC_TEST_REFILLING_SB_LENGTH_W = refilling_shift_buffer::length_width(SDC_ // type SequenceConfDecoderResp = sequence_conf_dec::SequenceConfDecoderResp; // type SequenceConfDecoderStatus = sequence_conf_dec::SequenceConfDecoderStatus; // -// type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; +// type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; // type FseLookupDecoderResp = fse_lookup_dec::FseLookupDecoderResp; // type FseLookupDecoderStatus = fse_lookup_dec::FseLookupDecoderStatus; // @@ -745,6 +756,7 @@ pub proc SequenceDecoder< AXI_ADDR_W: u32, AXI_DATA_W: u32, AXI_DEST_W: u32, AXI_ID_W: u32, DPD_RAM_ADDR_W: u32, DPD_RAM_DATA_W: u32, DPD_RAM_NUM_PARTITIONS: u32, TMP_RAM_ADDR_W: u32, TMP_RAM_DATA_W: u32, TMP_RAM_NUM_PARTITIONS: u32, + TMP2_RAM_ADDR_W: u32, TMP2_RAM_DATA_W: u32, TMP2_RAM_NUM_PARTITIONS: u32, FSE_RAM_ADDR_W: u32, FSE_RAM_DATA_W: u32, FSE_RAM_NUM_PARTITIONS: u32, AXI_DATA_W_DIV8: u32 = {AXI_DATA_W / u32:8}, @@ -767,7 +779,7 @@ pub proc SequenceDecoder< type SequenceConfDecoderReq = sequence_conf_dec::SequenceConfDecoderReq; type SequenceConfDecoderResp = sequence_conf_dec::SequenceConfDecoderResp; - type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; + type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; type FseLookupDecoderResp = fse_lookup_dec::FseLookupDecoderResp; type FseDecoderCtrl = fse_dec::FseDecoderCtrl; @@ -788,6 +800,11 @@ pub proc SequenceDecoder< type TmpRamWrReq = ram::WriteReq; type TmpRamWrResp = ram::WriteResp; + type Tmp2RamRdReq = ram::ReadReq; + type Tmp2RamRdResp = ram::ReadResp; + type Tmp2RamWrReq = ram::WriteReq; + type Tmp2RamWrResp = ram::WriteResp; + type FseRamRdReq = ram::ReadReq; type FseRamRdResp = ram::ReadResp; type FseRamWrReq = ram::WriteReq; @@ -801,13 +818,15 @@ pub proc SequenceDecoder< fd_rsb_ctrl_r: chan in; fd_rsb_data_s: chan out; - ll_wr_req_s: chan out; - ll_wr_resp_s: chan in; - ml_wr_req_s: chan out; - ml_wr_resp_s: chan in; - of_wr_req_s: chan out; - of_wr_resp_s: chan in; + dummy_ll_wr_req_s: chan out; + dummy_ll_wr_resp_r: chan in; + dummy_ml_wr_req_s: chan out; + dummy_ml_wr_resp_r: chan in; + dummy_of_wr_req_s: chan out; + dummy_of_wr_resp_r: chan in; + dummy_fse_rd_req_s: chan out; + dummy_fse_rd_resp_r: chan in; dummy_ll_fse_rd_req_r: chan in; dummy_ll_fse_rd_resp_s: chan out; dummy_ll_fse_wr_req_r: chan in; @@ -852,6 +871,11 @@ pub proc SequenceDecoder< tmp_wr_req_s: chan out, tmp_wr_resp_r: chan in, + tmp2_rd_req_s: chan out, + tmp2_rd_resp_r: chan in, + tmp2_wr_req_s: chan out, + tmp2_wr_resp_r: chan in, + ll_def_fse_rd_req_s: chan out, ll_def_fse_rd_resp_r: chan in, ll_def_fse_wr_req_s: chan out, @@ -883,7 +907,7 @@ pub proc SequenceDecoder< of_fse_wr_resp_r: chan in, ) { const CHANNEL_DEPTH = u32:1; - const READ_BACKWORD = true; + const READ_BACKWARD = true; // Sequence Section Decoder @@ -916,34 +940,64 @@ pub proc SequenceDecoder< let (fld_req_s, fld_req_r) = chan("fse_req"); let (fld_resp_s, fld_resp_r) = chan("fse_resp"); - let (fse_rd_req_s, fse_rd_req_r) = chan("fse_rd_req"); - let (fse_rd_resp_s, fse_rd_resp_r) = chan("fse_rd_resp"); + // FseLookupDecoder is not expected to read anything from the lookup + let (dummy_fse_rd_req_s, dummy_fse_rd_req_r) = chan("dummy_fse_rd_req"); + let (dummy_fse_rd_resp_s, dummy_fse_rd_resp_r) = chan("dummy_fse_rd_resp"); let (fse_wr_req_s, fse_wr_req_r) = chan("fse_wr_req"); let (fse_wr_resp_s, fse_wr_resp_r) = chan("fse_wr_resp"); + let (fld_rsb_start_req_s, fld_rsb_start_req_r) = chan("start_req"); + let (fld_rsb_stop_flush_req_s, fld_rsb_stop_flush_req_r) = chan<(), CHANNEL_DEPTH>("stop_flush_req"); + let (fld_rsb_buffer_ctrl_s, fld_rsb_buffer_ctrl_r) = chan("buffer_ctrl"); + let (fld_rsb_buffer_data_out_s, fld_rsb_buffer_data_out_r) = chan("buffer_data_out"); + let (fld_rsb_flushing_done_s, fld_rsb_flushing_done_r) = chan<(), CHANNEL_DEPTH>("flushing_done"); spawn fse_lookup_dec::FseLookupDecoder< - AXI_DATA_W, AXI_ADDR_W, + AXI_DATA_W, DPD_RAM_DATA_W, DPD_RAM_ADDR_W, DPD_RAM_NUM_PARTITIONS, TMP_RAM_DATA_W, TMP_RAM_ADDR_W, TMP_RAM_NUM_PARTITIONS, + TMP2_RAM_DATA_W, TMP2_RAM_ADDR_W, TMP2_RAM_NUM_PARTITIONS, FSE_RAM_DATA_W, FSE_RAM_ADDR_W, FSE_RAM_NUM_PARTITIONS, >( fld_req_r, fld_resp_s, - fld_mem_rd_req_s, fld_mem_rd_resp_r, dpd_rd_req_s, dpd_rd_resp_r, dpd_wr_req_s, dpd_wr_resp_r, tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, - fse_rd_req_s, fse_rd_resp_r, fse_wr_req_s, fse_wr_resp_r, + tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s, tmp2_wr_resp_r, + fse_wr_req_s, fse_wr_resp_r, + fld_rsb_buffer_ctrl_s, fld_rsb_buffer_data_out_r, + ); + + spawn refilling_shift_buffer::RefillingShiftBuffer( + fld_mem_rd_req_s, fld_mem_rd_resp_r, + fld_rsb_start_req_r, fld_rsb_stop_flush_req_r, + fld_rsb_buffer_ctrl_r, fld_rsb_buffer_data_out_s, + fld_rsb_flushing_done_s, ); // RamDemux3 + + // Dummy channels + let (dummy_ll_fse_rd_req_s, dummy_ll_fse_rd_req_r) = chan("dummy_ll_fse_rd_req"); + let (dummy_ll_fse_rd_resp_s, dummy_ll_fse_rd_resp_r) = chan("dummy_ll_fse_rd_resp"); + let (dummy_ll_fse_wr_req_s, dummy_ll_fse_wr_req_r) = chan("dummy_ll_fse_wr_req"); + let (dummy_ll_fse_wr_resp_s, dummy_ll_fse_wr_resp_r) = chan("dummy_ll_fse_wr_resp"); + let (dummy_ml_fse_rd_req_s, dummy_ml_fse_rd_req_r) = chan("dummy_ml_fse_rd_req"); + let (dummy_ml_fse_rd_resp_s, dummy_ml_fse_rd_resp_r) = chan("dummy_ml_fse_rd_resp"); + let (dummy_ml_fse_wr_req_s, dummy_ml_fse_wr_req_r) = chan("dummy_ml_fse_wr_req"); + let (dummy_ml_fse_wr_resp_s, dummy_ml_fse_wr_resp_r) = chan("dummy_ml_fse_wr_resp"); + let (dummy_of_fse_rd_req_s, dummy_of_fse_rd_req_r) = chan("dummy_of_fse_rd_req"); + let (dummy_of_fse_rd_resp_s, dummy_of_fse_rd_resp_r) = chan("dummy_of_fse_rd_resp"); + let (dummy_of_fse_wr_req_s, dummy_of_fse_wr_req_r) = chan("dummy_of_fse_wr_req"); + let (dummy_of_fse_wr_resp_s, dummy_of_fse_wr_resp_r) = chan("dummy_of_fse_wr_resp"); + let (fse_demux_req_s, fse_demux_req_r) = chan("fse_demux_req"); let (fse_demux_resp_s, fse_demux_resp_r) = chan<(), CHANNEL_DEPTH>("fse_demux_resp"); spawn ram_demux3::RamDemux3( fse_demux_req_r, fse_demux_resp_s, - fse_rd_req_r, fse_rd_resp_s, fse_wr_req_r, fse_wr_resp_s, - ll_fse_rd_req_s, ll_fse_rd_resp_r, ll_fse_wr_req_s, ll_fse_wr_resp_r, - of_fse_rd_req_s, of_fse_rd_resp_r, of_fse_wr_req_s, of_fse_wr_resp_r, - ml_fse_rd_req_s, ml_fse_rd_resp_r, ml_fse_wr_req_s, ml_fse_wr_resp_r, + dummy_fse_rd_req_r, dummy_fse_rd_resp_s, fse_wr_req_r, fse_wr_resp_s, + dummy_ll_fse_rd_req_s, dummy_ll_fse_rd_resp_r, ll_fse_wr_req_s, ll_fse_wr_resp_r, + dummy_of_fse_rd_req_s, dummy_of_fse_rd_resp_r, of_fse_wr_req_s, of_fse_wr_resp_r, + dummy_ml_fse_rd_req_s, dummy_ml_fse_rd_resp_r, ml_fse_wr_req_s, ml_fse_wr_resp_r, ); let (ll_demux_req_s, ll_demux_req_r) = chan("ll_demux_req"); @@ -951,21 +1005,16 @@ pub proc SequenceDecoder< let (ll_rd_req_s, ll_rd_req_r) = chan("ll_rd_req"); let (ll_rd_resp_s, ll_rd_resp_r) = chan("ll_rd_resp"); - let (ll_wr_req_s, ll_wr_req_r) = chan("ll_wr_req"); - let (ll_wr_resp_s, ll_wr_resp_r) = chan("ll_wr_resp"); - - let (dummy_ll_fse_rd_req_s, dummy_ll_fse_rd_req_r) = chan("dummy_ll_fse_rd_req"); - let (dummy_ll_fse_rd_resp_s, dummy_ll_fse_rd_resp_r) = chan("dummy_ll_fse_rd_resp"); - let (dummy_ll_fse_wr_req_s, dummy_ll_fse_wr_req_r) = chan("dummy_ll_fse_wr_req"); - let (dummy_ll_fse_wr_resp_s, dummy_ll_fse_wr_resp_r) = chan("dummy_ll_fse_wr_resp"); + let (dummy_ll_wr_req_s, dummy_ll_wr_req_r) = chan("dummy_ll_wr_req"); + let (dummy_ll_wr_resp_s, dummy_ll_wr_resp_r) = chan("dummy_ll_wr_resp"); spawn ram_demux::RamDemux< - FSE_RAM_ADDR_W, FSE_RAM_DATA_W, FSE_RAM_NUM_PARTITIONS + FSE_RAM_ADDR_W, FSE_RAM_DATA_W, FSE_RAM_NUM_PARTITIONS, u32:1 > ( ll_demux_req_r, ll_demux_resp_s, - ll_rd_req_r, ll_rd_resp_s, ll_wr_req_r, ll_wr_resp_s, + ll_rd_req_r, ll_rd_resp_s, dummy_ll_wr_req_r, dummy_ll_wr_resp_s, ll_def_fse_rd_req_s, ll_def_fse_rd_resp_r, ll_def_fse_wr_req_s, ll_def_fse_wr_resp_r, - dummy_ll_fse_rd_req_s, dummy_ll_fse_rd_resp_r, dummy_ll_fse_wr_req_s, dummy_ll_fse_wr_resp_r, + ll_fse_rd_req_s, ll_fse_rd_resp_r, dummy_ll_fse_wr_req_s, dummy_ll_fse_wr_resp_r, ); let (ml_demux_req_s, ml_demux_req_r) = chan("ml_demux_req"); @@ -973,21 +1022,16 @@ pub proc SequenceDecoder< let (ml_rd_req_s, ml_rd_req_r) = chan("ml_rd_req"); let (ml_rd_resp_s, ml_rd_resp_r) = chan("ml_rd_resp"); - let (ml_wr_req_s, ml_wr_req_r) = chan("ml_wr_req"); - let (ml_wr_resp_s, ml_wr_resp_r) = chan("ml_wr_resp"); - - let (dummy_ml_fse_rd_req_s, dummy_ml_fse_rd_req_r) = chan("dummy_ml_fse_rd_req"); - let (dummy_ml_fse_rd_resp_s, dummy_ml_fse_rd_resp_r) = chan("dummy_ml_fse_rd_resp"); - let (dummy_ml_fse_wr_req_s, dummy_ml_fse_wr_req_r) = chan("dummy_ml_fse_wr_req"); - let (dummy_ml_fse_wr_resp_s, dummy_ml_fse_wr_resp_r) = chan("dummy_ml_fse_wr_resp"); + let (dummy_ml_wr_req_s, dummy_ml_wr_req_r) = chan("dummy_ml_wr_req"); + let (dummy_ml_wr_resp_s, dummy_ml_wr_resp_r) = chan("dummy_ml_wr_resp"); spawn ram_demux::RamDemux< - FSE_RAM_ADDR_W, FSE_RAM_DATA_W, FSE_RAM_NUM_PARTITIONS + FSE_RAM_ADDR_W, FSE_RAM_DATA_W, FSE_RAM_NUM_PARTITIONS, u32:2 > ( ml_demux_req_r, ml_demux_resp_s, - ml_rd_req_r, ml_rd_resp_s, ml_wr_req_r, ml_wr_resp_s, + ml_rd_req_r, ml_rd_resp_s, dummy_ml_wr_req_r, dummy_ml_wr_resp_s, ml_def_fse_rd_req_s, ml_def_fse_rd_resp_r, ml_def_fse_wr_req_s, ml_def_fse_wr_resp_r, - dummy_ml_fse_rd_req_s, dummy_ml_fse_rd_resp_r, dummy_ml_fse_wr_req_s, dummy_ml_fse_wr_resp_r, + ml_fse_rd_req_s, ml_fse_rd_resp_r, dummy_ml_fse_wr_req_s, dummy_ml_fse_wr_resp_r, ); let (of_demux_req_s, of_demux_req_r) = chan("of_demux_req"); @@ -995,21 +1039,16 @@ pub proc SequenceDecoder< let (of_rd_req_s, of_rd_req_r) = chan("of_rd_req"); let (of_rd_resp_s, of_rd_resp_r) = chan("of_rd_resp"); - let (of_wr_req_s, of_wr_req_r) = chan("of_wr_req"); - let (of_wr_resp_s, of_wr_resp_r) = chan("of_wr_resp"); - - let (dummy_of_fse_rd_req_s, dummy_of_fse_rd_req_r) = chan("dummy_of_fse_rd_req"); - let (dummy_of_fse_rd_resp_s, dummy_of_fse_rd_resp_r) = chan("dummy_of_fse_rd_resp"); - let (dummy_of_fse_wr_req_s, dummy_of_fse_wr_req_r) = chan("dummy_of_fse_wr_req"); - let (dummy_of_fse_wr_resp_s, dummy_of_fse_wr_resp_r) = chan("dummy_of_fse_wr_resp"); + let (dummy_of_wr_req_s, dummy_of_wr_req_r) = chan("dummy_of_wr_req"); + let (dummy_of_wr_resp_s, dummy_of_wr_resp_r) = chan("dummy_of_wr_resp"); spawn ram_demux::RamDemux< - FSE_RAM_ADDR_W, FSE_RAM_DATA_W, FSE_RAM_NUM_PARTITIONS + FSE_RAM_ADDR_W, FSE_RAM_DATA_W, FSE_RAM_NUM_PARTITIONS, u32:3 > ( of_demux_req_r, of_demux_resp_s, - of_rd_req_r, of_rd_resp_s, of_wr_req_r, of_wr_resp_s, + of_rd_req_r, of_rd_resp_s, dummy_of_wr_req_r, dummy_of_wr_resp_s, of_def_fse_rd_req_s, of_def_fse_rd_resp_r, of_def_fse_wr_req_s, of_def_fse_wr_resp_r, - dummy_of_fse_rd_req_s, dummy_of_fse_rd_resp_r, dummy_of_fse_wr_req_s, dummy_of_fse_wr_resp_r, + of_fse_rd_req_s, of_fse_rd_resp_r, dummy_of_fse_wr_req_s, dummy_of_fse_wr_resp_r, ); let (fd_mem_rd_req_s, fd_mem_rd_req_r) = chan("fd_mem_rd_req"); @@ -1026,7 +1065,7 @@ pub proc SequenceDecoder< let (fd_rsb_data_s, fd_rsb_data_r) = chan("fd_rsb_data"); let (fd_rsb_flushing_done_s, fd_rsb_flushing_done_r) = chan<(), CHANNEL_DEPTH>("fd_rsb_flushing_done"); - spawn refilling_shift_buffer::RefillingShiftBuffer ( + spawn refilling_shift_buffer::RefillingShiftBuffer ( fd_mem_rd_req_s, fd_mem_rd_resp_r, fd_rsb_start_req_r, fd_rsb_stop_flush_req_r, fd_rsb_ctrl_r, fd_rsb_data_s, @@ -1056,16 +1095,22 @@ pub proc SequenceDecoder< of_demux_req_s, of_demux_resp_r, ml_demux_req_s, ml_demux_resp_r, fd_rsb_start_req_s, fd_rsb_stop_flush_req_s, fd_rsb_flushing_done_r, + fld_rsb_start_req_s, fld_rsb_stop_flush_req_s, fld_rsb_flushing_done_r, fd_ctrl_s, fd_finish_r, ); ( fd_ctrl_s, fd_finish_r, fd_rsb_ctrl_r, fd_rsb_data_s, - ll_wr_req_s, ll_wr_resp_r, - ml_wr_req_s, ml_wr_resp_r, - of_wr_req_s, of_wr_resp_r, - dummy_ll_fse_rd_req_r, + dummy_ll_wr_req_s, + dummy_ll_wr_resp_r, + dummy_ml_wr_req_s, + dummy_ml_wr_resp_r, + dummy_of_wr_req_s, + dummy_of_wr_resp_r, + dummy_fse_rd_req_s, + dummy_fse_rd_resp_r, + dummy_ll_fse_rd_req_r, dummy_ll_fse_rd_resp_s, dummy_ll_fse_wr_req_r, dummy_ll_fse_wr_resp_s, @@ -1077,30 +1122,31 @@ pub proc SequenceDecoder< dummy_of_fse_rd_resp_s, dummy_of_fse_wr_req_r, dummy_of_fse_wr_resp_s, - ) } next(state: ()) { - send_if(join(), ll_wr_req_s, false, zero!()); - recv_if(join(), ll_wr_resp_s, false, zero!()); - send_if(join(), ml_wr_req_s, false, zero!()); - recv_if(join(), ml_wr_resp_s, false, zero!()); - send_if(join(), of_wr_req_s, false, zero!()); - recv_if(join(), of_wr_resp_s, false, zero!()); - - recv_if(join(), dummy_ll_fse_rd_req_r, false, zero!()); - send_if(join(), dummy_ll_fse_rd_resp_s, false, zero!()); - recv_if(join(), dummy_ll_fse_wr_req_r, false, zero!()); - send_if(join(), dummy_ll_fse_wr_resp_s, false, zero!()); - recv_if(join(), dummy_ml_fse_rd_req_r, false, zero!()); - send_if(join(), dummy_ml_fse_rd_resp_s, false, zero!()); - recv_if(join(), dummy_ml_fse_wr_req_r, false, zero!()); - send_if(join(), dummy_ml_fse_wr_resp_s, false, zero!()); - recv_if(join(), dummy_of_fse_rd_req_r, false, zero!()); - send_if(join(), dummy_of_fse_rd_resp_s, false, zero!()); - recv_if(join(), dummy_of_fse_wr_req_r, false, zero!()); - send_if(join(), dummy_of_fse_wr_resp_s, false, zero!()); + let tok = join(); + send_if(tok, dummy_ll_wr_req_s, false, zero!()); + recv_if(tok, dummy_ll_wr_resp_r, false, zero!()); + send_if(tok, dummy_ml_wr_req_s, false, zero!()); + recv_if(tok, dummy_ml_wr_resp_r, false, zero!()); + send_if(tok, dummy_of_wr_req_s, false, zero!()); + recv_if(tok, dummy_of_wr_resp_r, false, zero!()); + send_if(tok, dummy_fse_rd_req_s, false, zero!()); + recv_if(tok, dummy_fse_rd_resp_r, false, zero!()); + recv_if(tok, dummy_ll_fse_rd_req_r, false, zero!()); + send_if(tok, dummy_ll_fse_rd_resp_s, false, zero!()); + recv_if(tok, dummy_ll_fse_wr_req_r, false, zero!()); + send_if(tok, dummy_ll_fse_wr_resp_s, false, zero!()); + recv_if(tok, dummy_ml_fse_rd_req_r, false, zero!()); + send_if(tok, dummy_ml_fse_rd_resp_s, false, zero!()); + recv_if(tok, dummy_ml_fse_wr_req_r, false, zero!()); + send_if(tok, dummy_ml_fse_wr_resp_s, false, zero!()); + recv_if(tok, dummy_of_fse_rd_req_r, false, zero!()); + send_if(tok, dummy_of_fse_rd_resp_s, false, zero!()); + recv_if(tok, dummy_of_fse_wr_req_r, false, zero!()); + send_if(tok, dummy_of_fse_wr_resp_s, false, zero!()); } } @@ -1125,9 +1171,9 @@ const TEST_DPD_RAM_WORD_PARTITION_SIZE = TEST_DPD_RAM_DATA_W; const TEST_DPD_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_DPD_RAM_WORD_PARTITION_SIZE, TEST_DPD_RAM_DATA_W); const TEST_FSE_RAM_DATA_W = u32:32; -const TEST_FSE_RAM_SIZE = u32:256; +const TEST_FSE_RAM_SIZE = u32:1 << common::FSE_MAX_ACCURACY_LOG; const TEST_FSE_RAM_ADDR_W = std::clog2(TEST_FSE_RAM_SIZE); -const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_W / u32:3; +const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_W; const TEST_FSE_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_FSE_RAM_WORD_PARTITION_SIZE, TEST_FSE_RAM_DATA_W); const TEST_FSE_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; @@ -1137,151 +1183,340 @@ const TEST_TMP_RAM_ADDR_W = std::clog2(TEST_TMP_RAM_SIZE); const TEST_TMP_RAM_WORD_PARTITION_SIZE = TEST_TMP_RAM_DATA_W; const TEST_TMP_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_TMP_RAM_WORD_PARTITION_SIZE, TEST_TMP_RAM_DATA_W); -const TEST_RAM_DATA = u64[40]:[ - u64:0x0, u64:0x0, // 0x000 - u64:0x0, u64:0x0, // 0x010 - u64:0x0, u64:0x0, // 0x020 - u64:0x0, u64:0x0, // 0x030 - u64:0x0, u64:0x0, // 0x040 - u64:0x0, u64:0x0, // 0x050 - u64:0x0, u64:0x0, // 0x060 - u64:0x0, u64:0x0, // 0x070 - u64:0x0, u64:0x0, // 0x080 - u64:0x0, u64:0x0, // 0x090 - u64:0x0, u64:0x0, // 0x0A0 - u64:0x0, u64:0x0, // 0x0B0 - u64:0x0, u64:0x0, // 0x0C0 - u64:0x0, u64:0x0, // 0x0D0 - u64:0x0, u64:0x0, // 0x0E0 - u64:0x0, u64:0x0, // 0x0F0 - u64:0x0016A400E6C3000A, // 0x100 - u64:0x225100295B0012D6, // 0x138 - u64:0x1E00123CB813DB80, // 0x140 - u64:0x1026064002C4800A, // 0x148 - u64:0x0, ... -]; - -const EXPECTED_OUTPUT = SequenceExecutorPacket[20]:[ - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::LITERAL, - length: u64:0x0000, - content: u64:0x0, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::SEQUENCE, - length: u64:0x0003, - content: u64:0x001c, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::LITERAL, - length: u64:0x000a, - content: u64:0x0, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::SEQUENCE, - length: u64:0x0003, - content: u64:0x0389, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::LITERAL, - length: u64:0x0000, - content: u64:0x0, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::SEQUENCE, - length: u64:0x0003, - content: u64:0x013c, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::LITERAL, - length: u64:0x0000, - content: u64:0x0, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::SEQUENCE, - length: u64:0x0003, - content: u64:0x0479, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::LITERAL, - length: u64:0x0009, - content: u64:0x0, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::SEQUENCE, - length: u64:0x0003, - content: u64:0x001d, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::LITERAL, - length: u64:0x0001, - content: u64:0x0, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::SEQUENCE, - length: u64:0x0003, - content: u64:0x024a, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::LITERAL, - length: u64:0x0001, - content: u64:0x0, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::SEQUENCE, - length: u64:0x0003, - content: u64:0x032b, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::LITERAL, - length: u64:0x0001, - content: u64:0x0, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::SEQUENCE, - length: u64:0x0003, - content: u64:0x02b5, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::LITERAL, - length: u64:0x0002, - content: u64:0x0, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::SEQUENCE, - length: u64:0x0003, - content: u64:0x03a9, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::LITERAL, - length: u64:0x0, - content: u64:0x0, - last: false, - }, - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::SEQUENCE, - length: u64:0x0004, - content: u64:0x06c3, - last: true, - }, +const TEST_TMP2_RAM_DATA_W = u32:8; +const TEST_TMP2_RAM_SIZE = u32:512; +const TEST_TMP2_RAM_ADDR_W = std::clog2(TEST_TMP2_RAM_SIZE); +const TEST_TMP2_RAM_WORD_PARTITION_SIZE = TEST_TMP2_RAM_DATA_W; +const TEST_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_TMP2_RAM_WORD_PARTITION_SIZE, TEST_TMP2_RAM_DATA_W); + +// testcase format: +// - sequences section length +// - sequences section as it appears in memory +// - expected output size +// - expected output +const SEQ_DEC_TESTCASES: (u32, u64[32], u32, SequenceExecutorPacket[64])[2] = [ + // Test case 0 + // raw literals with sequences with 3 predefined tables + // ./decodecorpus -pdata2.out -odata2.in -s35304 --block-type=2 --content-size --literal-type=0 --max-block-size-log=7 + ( + u32:17, + u64[32]:[ + u64:0x0, u64:0x0, + u64:0xBC7C2BA0B0430006, + u64:0x2157643002EA92AA, + u64:0x0000000000000002, + u64:0x0, ... + ], + u32:12, + SequenceExecutorPacket[64]:[ + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0004, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0005, + content: u64:0x000b, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0004, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0006, + content: u64:0x0001, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0005, + content: u64:0x0032, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0006, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0009, + content: u64:0x003e, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0009, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0006, + content: u64:0x003d, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x001a, + content: u64:0x0043, + last: true, + }, + zero!(), ... + ] + ), + // testcase 1 + // 2 custom tables with accuracy log 5 and 5, 1 predefined table + ( + u32:32, + u64[32]:[ + u64:0x0, u64:0x0, + u64:0x3D2321013010280D, + u64:0x6B3F7AC0F0D11F40, + u64:0xE80100C6012D0310, + u64:0x6CBFAEE1A0DDEF00, + u64:0x0, ... + ], + u32:26, + SequenceExecutorPacket[64]:[ + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0001, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x0003, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0001, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x000e, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0004, + content: u64:0x0023, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0001, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0004, + content: u64:0x00de, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x003a, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x0110, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0004, + content: u64:0x00b0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x00da, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0004, + content: u64:0x0044, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x013f, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0001, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x001b, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0004, + content: u64:0x0003, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0004, + content: u64:0x0031, + last: true, + }, + zero!(), ... + ] + ), + // Test case 2 (WARNING: long test running time) + // 3 custom lookup tables with accuracy log 9, 8 and 9 + // decodecorpus -pdata.out -odata.in -s58745 --block-type=2 --content-size --literal-type=0 --max-block-size-log=7 + // ( + // u32:32, + // u64[32]:[ + // u64:0x0, u64:0x0, + // u64:0xFC0502602814A804, + // u64:0x505040131FF60604, + // u64:0xFE01C080140FE030, + // u64:0x4040E65B84521B01, + // u64:0x0, ... + // ], + // u32:7, + // SequenceExecutorPacket[64]:[ + // SequenceExecutorPacket { + // msg_type: SequenceExecutorMessageType::LITERAL, + // length: u64:0x0005, + // content: u64:0x0, + // last: false, + // }, + // SequenceExecutorPacket { + // msg_type: SequenceExecutorMessageType::SEQUENCE, + // length: u64:0x0004, + // content: u64:0x0006, + // last: false, + // }, + // SequenceExecutorPacket { + // msg_type: SequenceExecutorMessageType::SEQUENCE, + // length: u64:0x0004, + // content: u64:0x0002, + // last: false, + // }, + // SequenceExecutorPacket { + // msg_type: SequenceExecutorMessageType::LITERAL, + // length: u64:0x0011, + // content: u64:0x0, + // last: false, + // }, + // SequenceExecutorPacket { + // msg_type: SequenceExecutorMessageType::SEQUENCE, + // length: u64:0x0004, + // content: u64:0x000a, + // last: false, + // }, + // SequenceExecutorPacket { + // msg_type: SequenceExecutorMessageType::LITERAL, + // length: u64:0x002b, + // content: u64:0x0, + // last: false, + // }, + // SequenceExecutorPacket { + // msg_type: SequenceExecutorMessageType::SEQUENCE, + // length: u64:0x0006, + // content: u64:0x0023, + // last: true, + // }, + // zero!(), ... + // ] + // ), ]; type Base = u16; @@ -1483,6 +1718,11 @@ proc SequenceDecoderTest { type TmpRamWrReq = ram::WriteReq; type TmpRamWrResp = ram::WriteResp; + type Tmp2RamRdReq = ram::ReadReq; + type Tmp2RamRdResp = ram::ReadResp; + type Tmp2RamWrReq = ram::WriteReq; + type Tmp2RamWrResp = ram::WriteResp; + type FseAddr = uN[TEST_FSE_RAM_ADDR_W]; type FseData = uN[TEST_FSE_RAM_DATA_W]; type FseMask = uN[TEST_FSE_RAM_NUM_PARTITIONS]; @@ -1564,6 +1804,17 @@ proc SequenceDecoderTest { TEST_TMP_RAM_WORD_PARTITION_SIZE >(tmp_rd_req_r, tmp_rd_resp_s, tmp_wr_req_r, tmp_wr_resp_s); + let (tmp2_rd_req_s, tmp2_rd_req_r) = chan("tmp2_rd_req"); + let (tmp2_rd_resp_s, tmp2_rd_resp_r) = chan("tmp2_rd_resp"); + let (tmp2_wr_req_s, tmp2_wr_req_r) = chan("tmp2_wr_req"); + let (tmp2_wr_resp_s, tmp2_wr_resp_r) = chan("tmp2_wr_resp"); + + spawn ram::RamModel< + TEST_TMP2_RAM_DATA_W, + TEST_TMP2_RAM_SIZE, + TEST_TMP2_RAM_WORD_PARTITION_SIZE + >(tmp2_rd_req_r, tmp2_rd_resp_s, tmp2_wr_req_r, tmp2_wr_resp_s); + // RAM with default FSE lookup for Literal Lengths let (ll_sel_test_s, ll_sel_test_r) = chan("ll_test_sel"); @@ -1803,6 +2054,7 @@ proc SequenceDecoderTest { TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, TEST_DPD_RAM_ADDR_W, TEST_DPD_RAM_DATA_W, TEST_DPD_RAM_NUM_PARTITIONS, TEST_TMP_RAM_ADDR_W, TEST_TMP_RAM_DATA_W, TEST_TMP_RAM_NUM_PARTITIONS, + TEST_TMP2_RAM_ADDR_W, TEST_TMP2_RAM_DATA_W, TEST_TMP2_RAM_NUM_PARTITIONS, TEST_FSE_RAM_ADDR_W, TEST_FSE_RAM_DATA_W, TEST_FSE_RAM_NUM_PARTITIONS, > ( ss_axi_ar_s, ss_axi_r_r, @@ -1814,6 +2066,7 @@ proc SequenceDecoderTest { dpd_rd_req_s, dpd_rd_resp_r, dpd_wr_req_s, dpd_wr_resp_r, tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s, tmp2_wr_resp_r, ll_def_fse_rd_req_s, ll_def_fse_rd_resp_r, ll_def_fse_wr_req_s, ll_def_fse_wr_resp_r, ll_fse_rd_req_s, ll_fse_rd_resp_r, ll_fse_wr_req_s, ll_fse_wr_resp_r, @@ -1848,22 +2101,6 @@ proc SequenceDecoderTest { next(state: ()) { let tok = join(); - // FILL THE TEST DATA - let tok = unroll_for! (i, tok): (u32, token) in range(u32:0, array_size(TEST_RAM_DATA)) { - let req = InputRamWrReq { - addr: i as InputAddr, - data: TEST_RAM_DATA[i] as InputData, - mask: !InputMask:0, - }; - let tok = send(tok, input0_wr_req_s, req); - let (tok, _) = recv(tok, input0_wr_resp_r); - let tok = send(tok, input1_wr_req_s, req); - let (tok, _) = recv(tok, input1_wr_resp_r); - let tok = send(tok, input2_wr_req_s, req); - let (tok, _) = recv(tok, input2_wr_resp_r); - tok - }(tok); - // FILL THE LL DEFAULT RAM let tok = send(tok, ll_sel_test_s, u1:0); let tok = unroll_for! (i, tok): (u32, token) in range(u32:0, array_size(DEFAULT_LL_TABLE)) { @@ -1906,69 +2143,110 @@ proc SequenceDecoderTest { }(tok); let tok = send(tok, ml_sel_test_s, u1:1); - // START DECODING - let tok = send(tok, req_s, Req { - sync: BlockSyncData { - id: u32:0, - last_block: false, - }, - start_addr: uN[TEST_AXI_ADDR_W]:0x100, - end_addr: uN[TEST_AXI_ADDR_W]:0x120, - literals_count: u20:24, - }); - - let tok = for ((i, output), tok): ((u32, SequenceExecutorPacket), token) in enumerate(EXPECTED_OUTPUT) { + // LOAD TESTCASES + let tok = unroll_for! (test_i, tok): (u32, token) in range(u32:0, array_size(SEQ_DEC_TESTCASES)) { + let (seq_len, seq_data, expected_len, expected_data) = SEQ_DEC_TESTCASES[test_i]; + let ADDR_OFFSET = uN[TEST_AXI_ADDR_W]:0x10; + let seq_len_words = std::ceil_div(seq_len + ADDR_OFFSET, u32:8); + // FILL THE TEST DATA + let tok = for (i, tok): (u32, token) in range(u32:0, seq_len_words) { + let req = InputRamWrReq { + addr: i as InputAddr, + data: seq_data[i] as InputData, + mask: !InputMask:0, + }; + let tok = send(tok, input0_wr_req_s, req); + let (tok, _) = recv(tok, input0_wr_resp_r); + let tok = send(tok, input1_wr_req_s, req); + let (tok, _) = recv(tok, input1_wr_resp_r); + let tok = send(tok, input2_wr_req_s, req); + let (tok, _) = recv(tok, input2_wr_resp_r); + tok + }(tok); + + // COUNT THE AMOUNT OF LITERALS + let (tok, literals_count) = for (i, (tok, literals_count)): (u32, (token, u20)) in range(u32:0, expected_len) { + let literals_count = match expected_data[i].msg_type { + SequenceExecutorMessageType::SEQUENCE => literals_count, + SequenceExecutorMessageType::LITERAL => literals_count + expected_data[i].length as u20, + }; + (tok, literals_count) + }((tok, u20:0)); + + // START DECODING + let tok = send(tok, req_s, Req { + sync: BlockSyncData { + id: u32:0, + last_block: false, + }, + start_addr: ADDR_OFFSET, + end_addr: ADDR_OFFSET + seq_len as uN[TEST_AXI_ADDR_W], + literals_count: literals_count, + }); + + let tok = for (i, tok): (u32, token) in range(u32:0, expected_len) { + let output = expected_data[i]; + let (tok, recv_output) = recv(tok, fd_command_r); + trace_fmt!("[{}]: Expected: {:#x}\nGot: {:#x}\n", i, output, recv_output); + assert_eq(output, recv_output.data); + tok + }(tok); + + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: SequenceDecoderStatus::OK + }); + trace_fmt!("DECODE RESPONSE"); + + // START DECODING - ask for more literals - expecting additional empty output packet with + // last set + let ADDITIONAL_LITERALS = u20:123; + let tok = send(tok, req_s, Req { + sync: BlockSyncData { + id: u32:0, + last_block: false, + }, + start_addr: ADDR_OFFSET, + end_addr: ADDR_OFFSET + seq_len as uN[TEST_AXI_ADDR_W], + literals_count: literals_count + ADDITIONAL_LITERALS, + }); + + // Don't read the last output packet from the expected output array + let tok = for (i, tok): (u32, token) in range(u32:0, expected_len - u32:1) { + let output = expected_data[i]; + let (tok, recv_output) = recv(tok, fd_command_r); + trace_fmt!("[{}]: Expected: {:#x}\nGot: {:#x}\n", i, output, recv_output); + assert_eq(output, recv_output.data); + tok + }(tok); + + // The last packet from the expected output array is now expected to have last not set + let expected = SequenceExecutorPacket { + last: false, + ..expected_data[expected_len - u32:1] + }; let (tok, recv_output) = recv(tok, fd_command_r); - trace_fmt!("[{}]: Expected: {:#x}\nGot: {:#x}\n", i, output, recv_output); - assert_eq(output, recv_output.data); - tok - }(tok); - - let (tok, _) = recv(tok, resp_r); - - // START DECODING - ask for more literals - expecting additional empty output packet with - // last set - let tok = send(tok, req_s, Req { - sync: BlockSyncData { - id: u32:0, - last_block: false, - }, - start_addr: uN[TEST_AXI_ADDR_W]:0x100, - end_addr: uN[TEST_AXI_ADDR_W]:0x120, - literals_count: u20:26, - }); - - // Don't read the last output packet from the expected output array - let tok = for (i, tok): (u32, token) in u32:0..(array_size(EXPECTED_OUTPUT) - u32:1) { - let output = EXPECTED_OUTPUT[i]; + trace_fmt!("[LAST-1]: Expected: {:#x}\nGot: {:#x}\n", expected, recv_output); + assert_eq(expected, recv_output.data); + + // This is the actual last output packet + let expected = SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: ADDITIONAL_LITERALS as u64, + content: u64:0x0, + last: true, + }; let (tok, recv_output) = recv(tok, fd_command_r); - trace_fmt!("[{}]: Expected: {:#x}\nGot: {:#x}\n", i, output, recv_output); - assert_eq(output, recv_output.data); + trace_fmt!("[LAST]: Expected: {:#x}\nGot: {:#x}\n", expected, recv_output); + assert_eq(expected, recv_output.data); + + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: SequenceDecoderStatus::OK + }); tok }(tok); - // The last packet from the expected output array is now expected to have last not set - let expected = SequenceExecutorPacket { - last: false, - ..EXPECTED_OUTPUT[array_size(EXPECTED_OUTPUT) - u32:1] - }; - let (tok, recv_output) = recv(tok, fd_command_r); - trace_fmt!("[LAST-1]: Expected: {:#x}\nGot: {:#x}\n", expected, recv_output); - assert_eq(expected, recv_output.data); - - // This is the actual last output packet - let expected = SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::LITERAL, - length: u64:0x2, - content: u64:0x0, - last: true, - }; - let (tok, recv_output) = recv(tok, fd_command_r); - trace_fmt!("[LAST]: Expected: {:#x}\nGot: {:#x}\n", expected, recv_output); - assert_eq(expected, recv_output.data); - - let (tok, _) = recv(tok, resp_r); - send(tok, terminator, true); } diff --git a/xls/modules/zstd/zstd_dec.x b/xls/modules/zstd/zstd_dec.x index ba1c3f961c..a7c2a9faba 100644 --- a/xls/modules/zstd/zstd_dec.x +++ b/xls/modules/zstd/zstd_dec.x @@ -946,6 +946,7 @@ pub proc ZstdDecoder< DPD_RAM_ADDR_W: u32, DPD_RAM_DATA_W: u32, DPD_RAM_NUM_PARTITIONS: u32, TMP_RAM_ADDR_W: u32, TMP_RAM_DATA_W: u32, TMP_RAM_NUM_PARTITIONS: u32, + TMP2_RAM_ADDR_W: u32, TMP2_RAM_DATA_W: u32, TMP2_RAM_NUM_PARTITIONS: u32, FSE_RAM_ADDR_W: u32, FSE_RAM_DATA_W: u32, FSE_RAM_NUM_PARTITIONS: u32, HISTORY_BUFFER_SIZE_KB: u32, @@ -1028,6 +1029,11 @@ pub proc ZstdDecoder< type TmpRamWrReq = ram::WriteReq; type TmpRamWrResp = ram::WriteResp; + type Tmp2RamRdReq = ram::ReadReq; + type Tmp2RamRdResp = ram::ReadResp; + type Tmp2RamWrReq = ram::WriteReq; + type Tmp2RamWrResp = ram::WriteResp; + type FseRamRdReq = ram::ReadReq; type FseRamRdResp = ram::ReadResp; type FseRamWrReq = ram::WriteReq; @@ -1085,6 +1091,11 @@ pub proc ZstdDecoder< tmp_wr_req_s: chan out, tmp_wr_resp_r: chan in, + tmp2_rd_req_s: chan out, + tmp2_rd_resp_r: chan in, + tmp2_wr_req_s: chan out, + tmp2_wr_resp_r: chan in, + fse_rd_req_s: chan[u32:6] out, fse_rd_resp_r: chan[u32:6] in, fse_wr_req_s: chan[u32:6] out, @@ -1257,6 +1268,7 @@ pub proc ZstdDecoder< // FSE lookup table RAMs DPD_RAM_ADDR_W, DPD_RAM_DATA_W, DPD_RAM_NUM_PARTITIONS, TMP_RAM_ADDR_W, TMP_RAM_DATA_W, TMP_RAM_NUM_PARTITIONS, + TMP2_RAM_ADDR_W, TMP2_RAM_DATA_W, TMP2_RAM_NUM_PARTITIONS, FSE_RAM_ADDR_W, FSE_RAM_DATA_W, FSE_RAM_NUM_PARTITIONS, >( // MAIN IOs @@ -1272,6 +1284,7 @@ pub proc ZstdDecoder< comp_axi_ram_ar_s[2], comp_axi_ram_r_r[2], dpd_rd_req_s, dpd_rd_resp_r, dpd_wr_req_s, dpd_wr_resp_r, tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s, tmp2_wr_resp_r, fse_rd_req_s[0], fse_rd_resp_r[0], fse_wr_req_s[0], fse_wr_resp_r[0], fse_rd_req_s[1], fse_rd_resp_r[1], fse_wr_req_s[1], fse_wr_resp_r[1], fse_rd_req_s[2], fse_rd_resp_r[2], fse_wr_req_s[2], fse_wr_resp_r[2], @@ -1408,7 +1421,7 @@ const INST_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( const INST_FSE_RAM_DATA_W = u32:32; const INST_FSE_RAM_SIZE = u32:256; const INST_FSE_RAM_ADDR_W = std::clog2(INST_FSE_RAM_SIZE); -const INST_FSE_RAM_WORD_PARTITION_SIZE = INST_FSE_RAM_DATA_W / u32:3; +const INST_FSE_RAM_WORD_PARTITION_SIZE = INST_FSE_RAM_DATA_W; const INST_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( INST_FSE_RAM_WORD_PARTITION_SIZE, INST_FSE_RAM_DATA_W); @@ -1419,6 +1432,13 @@ const INST_TMP_RAM_WORD_PARTITION_SIZE = INST_TMP_RAM_DATA_W; const INST_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( INST_TMP_RAM_WORD_PARTITION_SIZE, INST_TMP_RAM_DATA_W); +const INST_TMP2_RAM_DATA_W = u32:8; +const INST_TMP2_RAM_SIZE = u32:512; +const INST_TMP2_RAM_ADDR_W = std::clog2(INST_TMP2_RAM_SIZE); +const INST_TMP2_RAM_WORD_PARTITION_SIZE = INST_TMP2_RAM_DATA_W; +const INST_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions( + INST_TMP2_RAM_WORD_PARTITION_SIZE, INST_TMP2_RAM_DATA_W); + const HUFFMAN_WEIGHTS_RAM_ADDR_W: u32 = huffman_literals_dec::WEIGHTS_ADDR_WIDTH; const HUFFMAN_WEIGHTS_RAM_DATA_W: u32 = huffman_literals_dec::WEIGHTS_DATA_WIDTH; const HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::WEIGHTS_NUM_PARTITIONS; @@ -1550,6 +1570,11 @@ proc ZstdDecoderInst { type TmpRamWrReq = ram::WriteReq; type TmpRamWrResp = ram::WriteResp; + type Tmp2RamRdReq = ram::ReadReq; + type Tmp2RamRdResp = ram::ReadResp; + type Tmp2RamWrReq = ram::WriteReq; + type Tmp2RamWrResp = ram::WriteResp; + type FseRamRdReq = ram::ReadReq; type FseRamRdResp = ram::ReadResp; type FseRamWrReq = ram::WriteReq; @@ -1604,6 +1629,11 @@ proc ZstdDecoderInst { tmp_wr_req_s: chan out, tmp_wr_resp_r: chan in, + tmp2_rd_req_s: chan out, + tmp2_rd_resp_r: chan in, + tmp2_wr_req_s: chan out, + tmp2_wr_resp_r: chan in, + fse_rd_req_s: chan[u32:6] out, fse_rd_resp_r: chan[u32:6] in, fse_wr_req_s: chan[u32:6] out, @@ -1674,6 +1704,7 @@ proc ZstdDecoderInst { INST_HB_ADDR_W, INST_HB_DATA_W, INST_HB_NUM_PARTITIONS, INST_HB_SIZE_KB, INST_DPD_RAM_ADDR_W, INST_DPD_RAM_DATA_W, INST_DPD_RAM_NUM_PARTITIONS, INST_TMP_RAM_ADDR_W, INST_TMP_RAM_DATA_W, INST_TMP_RAM_NUM_PARTITIONS, + INST_TMP2_RAM_ADDR_W, INST_TMP2_RAM_DATA_W, INST_TMP2_RAM_NUM_PARTITIONS, INST_FSE_RAM_ADDR_W, INST_FSE_RAM_DATA_W, INST_FSE_RAM_NUM_PARTITIONS, INST_HISTORY_BUFFER_SIZE_KB, INST_AXI_CHAN_N, @@ -1687,6 +1718,8 @@ proc ZstdDecoderInst { dpd_wr_req_s, dpd_wr_resp_r, tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + tmp2_rd_req_s, tmp2_rd_resp_r, + tmp2_wr_req_s, tmp2_wr_resp_r, fse_rd_req_s, fse_rd_resp_r, fse_wr_req_s, fse_wr_resp_r, litbuf_rd_req_s, litbuf_rd_resp_r, diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x index 70c1969973..43c49b2a01 100644 --- a/xls/modules/zstd/zstd_dec_test.x +++ b/xls/modules/zstd/zstd_dec_test.x @@ -71,9 +71,9 @@ const TEST_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( TEST_DPD_RAM_WORD_PARTITION_SIZE, TEST_DPD_RAM_DATA_W); const TEST_FSE_RAM_DATA_W = u32:32; -const TEST_FSE_RAM_SIZE = u32:256; +const TEST_FSE_RAM_SIZE = u32:1 << common::FSE_MAX_ACCURACY_LOG; const TEST_FSE_RAM_ADDR_W = std::clog2(TEST_FSE_RAM_SIZE); -const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_W / u32:3; +const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_W; const TEST_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( TEST_FSE_RAM_WORD_PARTITION_SIZE, TEST_FSE_RAM_DATA_W); @@ -84,6 +84,13 @@ const TEST_TMP_RAM_WORD_PARTITION_SIZE = TEST_TMP_RAM_DATA_W; const TEST_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( TEST_TMP_RAM_WORD_PARTITION_SIZE, TEST_TMP_RAM_DATA_W); +const TEST_TMP2_RAM_DATA_W = u32:8; +const TEST_TMP2_RAM_SIZE = u32:512; +const TEST_TMP2_RAM_ADDR_W = std::clog2(TEST_TMP2_RAM_SIZE); +const TEST_TMP2_RAM_WORD_PARTITION_SIZE = TEST_TMP2_RAM_DATA_W; +const TEST_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_TMP2_RAM_WORD_PARTITION_SIZE, TEST_TMP2_RAM_DATA_W); + // Huffman weights memory parameters const HUFFMAN_WEIGHTS_RAM_SIZE: u32 = huffman_literals_dec::RAM_SIZE; const HUFFMAN_WEIGHTS_RAM_ADDR_W: u32 = huffman_literals_dec::WEIGHTS_ADDR_WIDTH; @@ -172,6 +179,11 @@ proc ZstdDecoderTest { type TmpRamWrReq = ram::WriteReq; type TmpRamWrResp = ram::WriteResp; + type Tmp2RamRdReq = ram::ReadReq; + type Tmp2RamRdResp = ram::ReadResp; + type Tmp2RamWrReq = ram::WriteReq; + type Tmp2RamWrResp = ram::WriteResp; + type FseRamRdReq = ram::ReadReq; type FseRamRdResp = ram::ReadResp; type FseRamWrReq = ram::WriteReq; @@ -381,6 +393,17 @@ proc ZstdDecoderTest { tmp_rd_req_r, tmp_rd_resp_s, tmp_wr_req_r, tmp_wr_resp_s, ); + let (tmp2_rd_req_s, tmp2_rd_req_r) = chan("tmp2_rd_req"); + let (tmp2_rd_resp_s, tmp2_rd_resp_r) = chan("tmp2_rd_resp"); + let (tmp2_wr_req_s, tmp2_wr_req_r) = chan("tmp2_wr_req"); + let (tmp2_wr_resp_s, tmp2_wr_resp_r) = chan("tmp2_wr_resp"); + spawn ram::RamModel< + TEST_TMP2_RAM_DATA_W, TEST_TMP2_RAM_SIZE, TEST_TMP2_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED + >( + tmp2_rd_req_r, tmp2_rd_resp_s, tmp2_wr_req_r, tmp2_wr_resp_s, + ); + // FSE RAMs let (fse_rd_req_s, fse_rd_req_r) = chan[u32:6]("fse_rd_req"); let (fse_rd_resp_s, fse_rd_resp_r) = chan[u32:6]("fse_rd_resp"); @@ -476,6 +499,7 @@ proc ZstdDecoderTest { TEST_HB_ADDR_W, TEST_HB_DATA_W, TEST_HB_NUM_PARTITIONS, TEST_HB_SIZE_KB, TEST_DPD_RAM_ADDR_W, TEST_DPD_RAM_DATA_W, TEST_DPD_RAM_NUM_PARTITIONS, TEST_TMP_RAM_ADDR_W, TEST_TMP_RAM_DATA_W, TEST_TMP_RAM_NUM_PARTITIONS, + TEST_TMP2_RAM_ADDR_W, TEST_TMP2_RAM_DATA_W, TEST_TMP2_RAM_NUM_PARTITIONS, TEST_FSE_RAM_ADDR_W, TEST_FSE_RAM_DATA_W, TEST_FSE_RAM_NUM_PARTITIONS, HISTORY_BUFFER_SIZE_KB, AXI_CHAN_N, >( @@ -488,6 +512,8 @@ proc ZstdDecoderTest { dpd_wr_req_s, dpd_wr_resp_r, tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + tmp2_rd_req_s, tmp2_rd_resp_r, + tmp2_wr_req_s, tmp2_wr_resp_r, // Channels for accessing FSE tables with muxed default FSE tables [ll_def_fse_rd_req_s, fse_rd_req_s[1], ml_def_fse_rd_req_s, fse_rd_req_s[3], of_def_fse_rd_req_s, fse_rd_req_s[5]], From 73da53983648708e51256ba8ea780b0fe509fafa Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Thu, 16 Jan 2025 14:48:44 +0100 Subject: [PATCH 55/85] Add test frame for compressed fse tables Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 5 +++-- xls/modules/zstd/comp_frame_fse_comp.x | 21 +++++++++++++++++++++ xls/modules/zstd/comp_frame_huffman.x | 0 xls/modules/zstd/zstd_dec_test.x | 5 +++-- 4 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 xls/modules/zstd/comp_frame_fse_comp.x mode change 100755 => 100644 xls/modules/zstd/comp_frame_huffman.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index d8f9a033ff..e3f078fc00 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1276,9 +1276,10 @@ xls_dslx_test( srcs = [ "zstd_dec.x", "zstd_dec_test.x", - # "zstd_frame_testcases.x", - # "comp_frame.x", + "zstd_frame_testcases.x", + "comp_frame.x", "comp_frame_huffman.x", + "comp_frame_fse_comp.x", ], tags = ["manual"], deps = zstd_dec_deps, diff --git a/xls/modules/zstd/comp_frame_fse_comp.x b/xls/modules/zstd/comp_frame_fse_comp.x new file mode 100644 index 0000000000..1eb07c06e7 --- /dev/null +++ b/xls/modules/zstd/comp_frame_fse_comp.x @@ -0,0 +1,21 @@ +pub struct DataArray{ + data: uN[BITS_PER_WORD][LENGTH], + length: u32, + array_length: u32 +} +pub const FRAMES:DataArray< + u32:64, + u32:9 +>[1] = [DataArray<64, 9>{ + length: u32:66, + array_length: u32:9, + data: uN[64][9]:[uN[64]:0x00545084fd2fb528, uN[64]:0x4236d000018d0000, uN[64]:0x1d98357537f4050f, uN[64]:0x8d92b5aed6d7791b, uN[64]:0x51538ed729019574, uN[64]:0x701101fb8611a803, uN[64]:0x8acfff857107d159, uN[64]:0x548604b38e0a63fd, uN[64]:0xc551] +}]; +pub const DECOMPRESSED_FRAMES:DataArray< + u32:64, + u32:11 +>[1] = [DataArray<64, 11>{ + length: u32:84, + array_length: u32:11, + data: uN[64][11]:[uN[64]:0x373737f4050f4236, uN[64]:0x3737373737373737, uN[64]:0x3737373737373737, uN[64]:0x3737373737373737, uN[64]:0x373737f4050f4237, uN[64]:0x3737373737373737, uN[64]:0x3737373737373737, uN[64]:0x3737373737373737, uN[64]:0xd6d7791b1d983575, uN[64]:0x290195748d92b5ae, uN[64]:0x51538ed7] +}]; diff --git a/xls/modules/zstd/comp_frame_huffman.x b/xls/modules/zstd/comp_frame_huffman.x old mode 100755 new mode 100644 diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x index 43c49b2a01..9b2f2016df 100644 --- a/xls/modules/zstd/zstd_dec_test.x +++ b/xls/modules/zstd/zstd_dec_test.x @@ -29,8 +29,9 @@ import xls.modules.zstd.literals_buffer; import xls.modules.zstd.fse_table_creator; import xls.modules.zstd.ram_mux; // import xls.modules.zstd.zstd_frame_testcases as comp_frame; -// import xls.modules.zstd.comp_frame; -import xls.modules.zstd.comp_frame_huffman as comp_frame; +// import xls.modules.zstd.comp_frame_huffman as comp_frame; +// import xls.modules.zstd.comp_frame_fse_comp as comp_frame; +import xls.modules.zstd.comp_frame; const TEST_WINDOW_LOG_MAX = u32:30; From 6044dcdb41fb0ae171f3e5cbb40080a535642cc4 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Mon, 20 Jan 2025 13:30:39 +0100 Subject: [PATCH 56/85] modules/zstd/zstd_dec: don't use channel arrays for FSE tables access Internal-tag: [#71791] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/zstd_dec.x | 49 ++++++++++++++++++++++++-------- xls/modules/zstd/zstd_dec_test.x | 8 +++--- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/xls/modules/zstd/zstd_dec.x b/xls/modules/zstd/zstd_dec.x index a7c2a9faba..31ab2d643f 100644 --- a/xls/modules/zstd/zstd_dec.x +++ b/xls/modules/zstd/zstd_dec.x @@ -1096,10 +1096,33 @@ pub proc ZstdDecoder< tmp2_wr_req_s: chan out, tmp2_wr_resp_r: chan in, - fse_rd_req_s: chan[u32:6] out, - fse_rd_resp_r: chan[u32:6] in, - fse_wr_req_s: chan[u32:6] out, - fse_wr_resp_r: chan[u32:6] in, + ll_def_fse_rd_req_s: chan out, + ll_fse_rd_req_s: chan out, + ml_def_fse_rd_req_s: chan out, + ml_fse_rd_req_s: chan out, + of_def_fse_rd_req_s: chan out, + of_fse_rd_req_s: chan out, + + ll_def_fse_rd_resp_r: chan in, + ll_fse_rd_resp_r: chan in, + ml_def_fse_rd_resp_r: chan in, + ml_fse_rd_resp_r: chan in, + of_def_fse_rd_resp_r: chan in, + of_fse_rd_resp_r: chan in, + + ll_def_fse_wr_req_s: chan out, + ll_fse_wr_req_s: chan out, + ml_def_fse_wr_req_s: chan out, + ml_fse_wr_req_s: chan out, + of_def_fse_wr_req_s: chan out, + of_fse_wr_req_s: chan out, + + ll_def_fse_wr_resp_r: chan in, + ll_fse_wr_resp_r: chan in, + ml_def_fse_wr_resp_r: chan in, + ml_fse_wr_resp_r: chan in, + of_def_fse_wr_resp_r: chan in, + of_fse_wr_resp_r: chan in, litbuf_rd_req_s: chan[u32:8] out, litbuf_rd_resp_r: chan[u32:8] in, @@ -1285,12 +1308,12 @@ pub proc ZstdDecoder< dpd_rd_req_s, dpd_rd_resp_r, dpd_wr_req_s, dpd_wr_resp_r, tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s, tmp2_wr_resp_r, - fse_rd_req_s[0], fse_rd_resp_r[0], fse_wr_req_s[0], fse_wr_resp_r[0], - fse_rd_req_s[1], fse_rd_resp_r[1], fse_wr_req_s[1], fse_wr_resp_r[1], - fse_rd_req_s[2], fse_rd_resp_r[2], fse_wr_req_s[2], fse_wr_resp_r[2], - fse_rd_req_s[3], fse_rd_resp_r[3], fse_wr_req_s[3], fse_wr_resp_r[3], - fse_rd_req_s[4], fse_rd_resp_r[4], fse_wr_req_s[4], fse_wr_resp_r[4], - fse_rd_req_s[5], fse_rd_resp_r[5], fse_wr_req_s[5], fse_wr_resp_r[5], + ll_def_fse_rd_req_s, ll_def_fse_rd_resp_r, ll_def_fse_wr_req_s, ll_def_fse_wr_resp_r, + ll_fse_rd_req_s, ll_fse_rd_resp_r, ll_fse_wr_req_s, ll_fse_wr_resp_r, + ml_def_fse_rd_req_s, ml_def_fse_rd_resp_r, ml_def_fse_wr_req_s, ml_def_fse_wr_resp_r, + ml_fse_rd_req_s, ml_fse_rd_resp_r, ml_fse_wr_req_s, ml_fse_wr_resp_r, + of_def_fse_rd_req_s, of_def_fse_rd_resp_r, of_def_fse_wr_req_s, of_def_fse_wr_resp_r, + of_fse_rd_req_s, of_fse_rd_resp_r, of_fse_wr_req_s, of_fse_wr_resp_r, // LITERALS DECODING @@ -1720,8 +1743,10 @@ proc ZstdDecoderInst { tmp_wr_req_s, tmp_wr_resp_r, tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s, tmp2_wr_resp_r, - fse_rd_req_s, fse_rd_resp_r, - fse_wr_req_s, fse_wr_resp_r, + fse_rd_req_s[0], fse_rd_req_s[1], fse_rd_req_s[2], fse_rd_req_s[3], fse_rd_req_s[4], fse_rd_req_s[5], + fse_rd_resp_r[0], fse_rd_resp_r[1], fse_rd_resp_r[2], fse_rd_resp_r[3], fse_rd_resp_r[4], fse_rd_resp_r[5], + fse_wr_req_s[0], fse_wr_req_s[1], fse_wr_req_s[2], fse_wr_req_s[3], fse_wr_req_s[4], fse_wr_req_s[5], + fse_wr_resp_r[0], fse_wr_resp_r[1], fse_wr_resp_r[2], fse_wr_resp_r[3], fse_wr_resp_r[4], fse_wr_resp_r[5], litbuf_rd_req_s, litbuf_rd_resp_r, litbuf_wr_req_s, litbuf_wr_resp_r, huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_resp_r, diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x index 9b2f2016df..92aac6a0f6 100644 --- a/xls/modules/zstd/zstd_dec_test.x +++ b/xls/modules/zstd/zstd_dec_test.x @@ -517,10 +517,10 @@ proc ZstdDecoderTest { tmp2_wr_req_s, tmp2_wr_resp_r, // Channels for accessing FSE tables with muxed default FSE tables - [ll_def_fse_rd_req_s, fse_rd_req_s[1], ml_def_fse_rd_req_s, fse_rd_req_s[3], of_def_fse_rd_req_s, fse_rd_req_s[5]], - [ll_def_fse_rd_resp_r, fse_rd_resp_r[1], ml_def_fse_rd_resp_r, fse_rd_resp_r[3], of_def_fse_rd_resp_r, fse_rd_resp_r[5]], - [ll_def_fse_wr_req_s, fse_wr_req_s[1], ml_def_fse_wr_req_s, fse_wr_req_s[3], of_def_fse_wr_req_s, fse_wr_req_s[5]], - [ll_def_fse_wr_resp_r, fse_wr_resp_r[1], ml_def_fse_wr_resp_r, fse_wr_resp_r[3], of_def_fse_wr_resp_r, fse_wr_resp_r[5]], + ll_def_fse_rd_req_s, fse_rd_req_s[1], ml_def_fse_rd_req_s, fse_rd_req_s[3], of_def_fse_rd_req_s, fse_rd_req_s[5], + ll_def_fse_rd_resp_r, fse_rd_resp_r[1], ml_def_fse_rd_resp_r, fse_rd_resp_r[3], of_def_fse_rd_resp_r, fse_rd_resp_r[5], + ll_def_fse_wr_req_s, fse_wr_req_s[1], ml_def_fse_wr_req_s, fse_wr_req_s[3], of_def_fse_wr_req_s, fse_wr_req_s[5], + ll_def_fse_wr_resp_r, fse_wr_resp_r[1], ml_def_fse_wr_resp_r, fse_wr_resp_r[3], of_def_fse_wr_resp_r, fse_wr_resp_r[5], litbuf_rd_req_s, litbuf_rd_resp_r, litbuf_wr_req_s, litbuf_wr_resp_r, From c425bc8fee8d02926ef9596677be0228159834fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Ob=C5=82onczek?= Date: Mon, 20 Jan 2025 15:48:01 +0000 Subject: [PATCH 57/85] Fix sequence decoding with predefined offset table, add regression test --- xls/modules/zstd/sequence_dec.x | 143 +++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 3 deletions(-) diff --git a/xls/modules/zstd/sequence_dec.x b/xls/modules/zstd/sequence_dec.x index 294c4855c8..a33911ef3b 100644 --- a/xls/modules/zstd/sequence_dec.x +++ b/xls/modules/zstd/sequence_dec.x @@ -540,7 +540,7 @@ pub proc SequenceDecoderCtrl< let (tok_ml_demux, _) = recv_if(tok_ml_demux, ml_demux_resp_r, !zero_sequences, ()); // Set proper OF lookup through demux - let of_demux_sel = (conf_resp.header.match_mode != CompressionMode::PREDEFINED); + let of_demux_sel = (conf_resp.header.offset_mode != CompressionMode::PREDEFINED); let tok_of_demux = send_if(tok_recv_scd, of_demux_req_s, !zero_sequences, of_demux_sel); // Receive response from OF lookup demux let (tok_of_demux, _) = recv_if(tok_of_demux, of_demux_resp_r, !zero_sequences, ()); @@ -1194,7 +1194,7 @@ const TEST_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_TMP2_RAM_WORD_PART // - sequences section as it appears in memory // - expected output size // - expected output -const SEQ_DEC_TESTCASES: (u32, u64[32], u32, SequenceExecutorPacket[64])[2] = [ +const SEQ_DEC_TESTCASES: (u32, u64[32], u32, SequenceExecutorPacket[64])[3] = [ // Test case 0 // raw literals with sequences with 3 predefined tables // ./decodecorpus -pdata2.out -odata2.in -s35304 --block-type=2 --content-size --literal-type=0 --max-block-size-log=7 @@ -1457,7 +1457,144 @@ const SEQ_DEC_TESTCASES: (u32, u64[32], u32, SequenceExecutorPacket[64])[2] = [ zero!(), ... ] ), - // Test case 2 (WARNING: long test running time) + // test case 2 + // LL - compressed, OF - predefined, ML - compressed + ( + u32:25, + u64[32]:[ + u64:0x0, u64:0x0, + u64:0xDEF00EB70AB0880A, + u64:0xE428228113B02D01, + u64:0x748A16EBB16B9BEC, + u64:0x000000000000003E, + u64:0x0, ... + ], + u32:20, + SequenceExecutorPacket[64]:[ + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0002, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0005, + content: u64:0x0004, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0003, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x000d, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0000, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x0002, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0003, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0004, + content: u64:0x000d, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0002, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0004, + content: u64:0x0017, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0000, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x0019, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0000, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0004, + content: u64:0x0021, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0001, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x0020, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0006, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0004, + content: u64:0x001b, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0000, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0004, + content: u64:0x000d, + last: true, + }, + zero!(), ... + ] + ), + // Test case 3 (WARNING: long test running time) // 3 custom lookup tables with accuracy log 9, 8 and 9 // decodecorpus -pdata.out -odata.in -s58745 --block-type=2 --content-size --literal-type=0 --max-block-size-log=7 // ( From d016956bd99c0065404b52471ab1e3065fb66b09 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 22 Jan 2025 21:56:29 +0100 Subject: [PATCH 58/85] Add support for RLE and Repeated fse tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Krzysztof Obłonczek Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 61 +- xls/modules/zstd/common.x | 11 + xls/modules/zstd/comp_lookup_dec.x | 1832 +++++++++++++++++ xls/modules/zstd/fse_lookup_dec.x | 1576 ++------------ xls/modules/zstd/fse_table_creator.x | 8 +- xls/modules/zstd/refilling_shift_buffer_mux.x | 252 +++ xls/modules/zstd/rle_lookup_dec.x | 190 ++ xls/modules/zstd/sequence_dec.x | 231 ++- 8 files changed, 2711 insertions(+), 1450 deletions(-) create mode 100644 xls/modules/zstd/comp_lookup_dec.x create mode 100644 xls/modules/zstd/refilling_shift_buffer_mux.x create mode 100644 xls/modules/zstd/rle_lookup_dec.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index e3f078fc00..4fc18e5743 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1346,9 +1346,9 @@ place_and_route( ) xls_dslx_library( - name = "fse_lookup_dec_dslx", + name = "comp_lookup_dec_dslx", srcs = [ - "fse_lookup_dec.x", + "comp_lookup_dec.x", ], deps = [ "//xls/examples:ram_dslx", @@ -1362,6 +1362,45 @@ xls_dslx_library( ] ) +xls_dslx_test( + name = "comp_lookup_dec_dslx_test", + library = ":comp_lookup_dec_dslx", + tags = ["manual"], +) + +xls_dslx_library( + name = "rle_lookup_dec_dslx", + srcs = [ + "rle_lookup_dec.x", + ], + deps = [ + ":common_dslx", + "//xls/examples:ram_dslx", + "//xls/modules/zstd:refilling_shift_buffer_dslx", + "//xls/modules/zstd:fse_table_creator_dslx", + ] +) + +xls_dslx_test( + name = "rle_lookup_dec_dslx_test", + library = ":rle_lookup_dec_dslx", + tags = ["manual"], +) + +xls_dslx_library( + name = "fse_lookup_dec_dslx", + srcs = [ + "fse_lookup_dec.x", + ], + deps = [ + ":comp_lookup_dec_dslx", + ":rle_lookup_dec_dslx", + ":ram_mux_dslx", + ":refilling_shift_buffer_dslx", + ":refilling_shift_buffer_mux_dslx", + ] +) + xls_dslx_test( name = "fse_lookup_dec_dslx_test", library = ":fse_lookup_dec_dslx", @@ -1985,12 +2024,13 @@ xls_dslx_library( ], deps = [ ":sequence_conf_dec_dslx", - ":fse_lookup_dec_dslx", + ":comp_lookup_dec_dslx", ":ram_demux3_dslx", ":common_dslx", ":fse_dec_dslx", ":ram_mux_dslx", ":fse_table_creator_dslx", + ":fse_lookup_dec_dslx", "//xls/modules/zstd/memory:mem_reader_dslx", "//xls/modules/zstd/memory:axi_ram_dslx", "//xls/modules/zstd:refilling_shift_buffer_dslx", @@ -3166,3 +3206,18 @@ place_and_route( tags = ["manual"], target_die_utilization_percentage = "10", ) + +xls_dslx_library( + name = "refilling_shift_buffer_mux_dslx", + srcs = [ + "refilling_shift_buffer_mux.x" + ], + deps = [ + ":refilling_shift_buffer_dslx" + ] +) + +xls_dslx_test( + name = "refilling_shift_buffer_mux_dslx_test", + library = ":refilling_shift_buffer_mux_dslx", +) diff --git a/xls/modules/zstd/common.x b/xls/modules/zstd/common.x index 725d74a713..8c1a48b143 100644 --- a/xls/modules/zstd/common.x +++ b/xls/modules/zstd/common.x @@ -297,3 +297,14 @@ pub struct LiteralsBufferCtrl { last: bool, } +pub enum LookupDecoderStatus: u1 { + OK = u1:0, + ERROR = u1:1, +} + +pub struct LookupDecoderReq {} + +pub struct LookupDecoderResp { + status: LookupDecoderStatus, + accuracy_log: FseAccuracyLog, +} diff --git a/xls/modules/zstd/comp_lookup_dec.x b/xls/modules/zstd/comp_lookup_dec.x new file mode 100644 index 0000000000..628326edf9 --- /dev/null +++ b/xls/modules/zstd/comp_lookup_dec.x @@ -0,0 +1,1832 @@ +// 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; +import xls.modules.zstd.common; +import xls.modules.zstd.memory.axi; +import xls.modules.zstd.memory.mem_reader; +import xls.modules.zstd.memory.axi_ram; +import xls.modules.zstd.fse_table_creator; +import xls.modules.zstd.refilling_shift_buffer; +import xls.modules.zstd.fse_proba_freq_dec; +import xls.modules.shift_buffer.shift_buffer; + +type AccuracyLog = common::FseAccuracyLog; + +pub proc CompLookupDecoder< + AXI_DATA_W: u32, + DPD_RAM_DATA_W: u32, DPD_RAM_ADDR_W: u32, DPD_RAM_NUM_PARTITIONS: u32, + TMP_RAM_DATA_W: u32, TMP_RAM_ADDR_W: u32, TMP_RAM_NUM_PARTITIONS: u32, + TMP2_RAM_DATA_W: u32, TMP2_RAM_ADDR_W: u32, TMP2_RAM_NUM_PARTITIONS: u32, + FSE_RAM_DATA_W: u32, FSE_RAM_ADDR_W: u32, FSE_RAM_NUM_PARTITIONS: u32, + SB_LENGTH_W: u32 = {refilling_shift_buffer::length_width(AXI_DATA_W)}, +> { + type Req = common::LookupDecoderReq; + type Resp = common::LookupDecoderResp; + type Status = common::LookupDecoderStatus; + + type FseTableStart = fse_table_creator::FseStartMsg; + + type DpdRamWrReq = ram::WriteReq; + type DpdRamWrResp = ram::WriteResp; + type DpdRamRdReq = ram::ReadReq; + type DpdRamRdResp = ram::ReadResp; + + type FseRamWrReq = ram::WriteReq; + type FseRamWrResp = ram::WriteResp; + + type TmpRamRdReq = ram::ReadReq; + type TmpRamRdResp = ram::ReadResp; + type TmpRamWrReq = ram::WriteReq; + type TmpRamWrResp = ram::WriteResp; + + type Tmp2RamRdReq = ram::ReadReq; + type Tmp2RamRdResp = ram::ReadResp; + type Tmp2RamWrReq = ram::WriteReq; + type Tmp2RamWrResp = ram::WriteResp; + + type SBOutput = refilling_shift_buffer::RefillingShiftBufferOutput; + type SBCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + + type FsePFDecReq = fse_proba_freq_dec::FseProbaFreqDecoderReq; + type FsePFDecResp = fse_proba_freq_dec::FseProbaFreqDecoderResp; + type FsePFDecStatus = fse_proba_freq_dec::FseProbaFreqDecoderStatus; + + req_r: chan in; + resp_s: chan out; + + fse_pf_dec_req_s: chan out; + fse_pf_dec_resp_r: chan in; + fse_table_start_s: chan out; + fse_table_finish_r: chan<()> in; + + init {} + + config( + req_r: chan in, + resp_s: chan out, + + dpd_rd_req_s: chan out, + dpd_rd_resp_r: chan in, + dpd_wr_req_s: chan out, + dpd_wr_resp_r: chan in, + + tmp_rd_req_s: chan out, + tmp_rd_resp_r: chan in, + tmp_wr_req_s: chan out, + tmp_wr_resp_r: chan in, + + tmp2_rd_req_s: chan out, + tmp2_rd_resp_r: chan in, + tmp2_wr_req_s: chan out, + tmp2_wr_resp_r: chan in, + + fse_wr_req_s: chan out, + fse_wr_resp_r: chan in, + + buffer_ctrl_s: chan out, + buffer_data_out_r: chan in, + ) { + const CHANNEL_DEPTH = u32:1; + + let (fse_table_start_s, fse_table_start_r) = chan("fse_table_start"); + let (fse_table_finish_s, fse_table_finish_r) = chan<(), CHANNEL_DEPTH>("fse_table_finish"); + + spawn fse_table_creator::FseTableCreator< + DPD_RAM_DATA_W, DPD_RAM_ADDR_W, DPD_RAM_NUM_PARTITIONS, + FSE_RAM_DATA_W, FSE_RAM_ADDR_W, FSE_RAM_NUM_PARTITIONS, + TMP_RAM_DATA_W, TMP_RAM_ADDR_W, TMP_RAM_NUM_PARTITIONS, + TMP2_RAM_DATA_W, TMP2_RAM_ADDR_W, TMP2_RAM_NUM_PARTITIONS, + >( + fse_table_start_r, fse_table_finish_s, + dpd_rd_req_s, dpd_rd_resp_r, + fse_wr_req_s, fse_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s, tmp2_wr_resp_r, + ); + + let (fse_pf_dec_req_s, fse_pf_dec_req_r) = chan("fse_pf_dec_req"); + let (fse_pf_dec_resp_s, fse_pf_dec_resp_r) = chan("fse_pf_dec_resp"); + + spawn fse_proba_freq_dec::FseProbaFreqDecoder< + DPD_RAM_DATA_W, DPD_RAM_ADDR_W, DPD_RAM_NUM_PARTITIONS, + >( + fse_pf_dec_req_r, fse_pf_dec_resp_s, + buffer_ctrl_s, buffer_data_out_r, + dpd_wr_req_s, dpd_wr_resp_r, + ); + + ( + req_r, resp_s, + fse_pf_dec_req_s, fse_pf_dec_resp_r, + fse_table_start_s, fse_table_finish_r, + ) + } + + next(state: ()) { + let tok = join(); + let (tok, start_req) = recv(tok, req_r); + + // start FSE probability frequency decoder + let tok = send(tok, fse_pf_dec_req_s, FsePFDecReq {}); + + // wait for completion from FSE probability frequency decoder + let (tok, pf_dec_res) = recv(tok, fse_pf_dec_resp_r); + trace_fmt!("FSE prob decoded: {:#x}", pf_dec_res); + + let pf_dec_ok = pf_dec_res.status == FsePFDecStatus::OK; + // run FSE Table creation conditional or previous processing succeeding + let tok = send_if(tok, fse_table_start_s, pf_dec_ok, FseTableStart { + num_symbs: pf_dec_res.symbol_count, + accuracy_log: pf_dec_res.accuracy_log, + }); + // wait for completion from FSE table creator + let (tok, ()) = recv_if(tok, fse_table_finish_r, pf_dec_ok, ()); + trace_fmt!("FSE table created"); + + let resp = if pf_dec_ok { + Resp { status: Status::OK, accuracy_log: pf_dec_res.accuracy_log } + } else { + Resp { status: Status::ERROR, ..zero!() } + }; + send(tok, resp_s, resp); + } +} + + +const TEST_AXI_DATA_WIDTH = u32:64; +const TEST_AXI_ADDR_WIDTH = u32:32; +const TEST_AXI_ID_WIDTH = u32:8; +const TEST_AXI_DEST_WIDTH = u32:8; +const TEST_SB_LENGTH_WIDTH = refilling_shift_buffer::length_width(TEST_AXI_DATA_WIDTH); + +const TEST_CASE_RAM_DATA_WIDTH = u32:64; +const TEST_CASE_RAM_SIZE = u32:256; +const TEST_CASE_RAM_ADDR_WIDTH = std::clog2(TEST_CASE_RAM_SIZE); +const TEST_CASE_RAM_WORD_PARTITION_SIZE = TEST_CASE_RAM_DATA_WIDTH; +const TEST_CASE_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_CASE_RAM_WORD_PARTITION_SIZE, TEST_CASE_RAM_DATA_WIDTH); +const TEST_CASE_RAM_BASE_ADDR = u32:0; + +const TEST_DPD_RAM_DATA_WIDTH = u32:16; +const TEST_DPD_RAM_SIZE = u32:256; +const TEST_DPD_RAM_ADDR_WIDTH = std::clog2(TEST_DPD_RAM_SIZE); +const TEST_DPD_RAM_WORD_PARTITION_SIZE = TEST_DPD_RAM_DATA_WIDTH; +const TEST_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_DPD_RAM_WORD_PARTITION_SIZE, TEST_DPD_RAM_DATA_WIDTH); + +const TEST_FSE_RAM_DATA_WIDTH = u32:32; +const TEST_FSE_RAM_SIZE = u32:1 << common::FSE_MAX_ACCURACY_LOG; +const TEST_FSE_RAM_ADDR_WIDTH = std::clog2(TEST_FSE_RAM_SIZE); +const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_WIDTH; +const TEST_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_FSE_RAM_WORD_PARTITION_SIZE, TEST_FSE_RAM_DATA_WIDTH); + +const TEST_TMP_RAM_DATA_WIDTH = u32:16; +const TEST_TMP_RAM_SIZE = u32:256; +const TEST_TMP_RAM_ADDR_WIDTH = std::clog2(TEST_TMP_RAM_SIZE); +const TEST_TMP_RAM_WORD_PARTITION_SIZE = TEST_TMP_RAM_DATA_WIDTH; +const TEST_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_TMP_RAM_WORD_PARTITION_SIZE, TEST_TMP_RAM_DATA_WIDTH); + +const TEST_TMP2_RAM_DATA_WIDTH = u32:8; +const TEST_TMP2_RAM_SIZE = u32:512; +const TEST_TMP2_RAM_ADDR_WIDTH = std::clog2(TEST_TMP2_RAM_SIZE); +const TEST_TMP2_RAM_WORD_PARTITION_SIZE = TEST_TMP2_RAM_DATA_WIDTH; +const TEST_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_TMP2_RAM_WORD_PARTITION_SIZE, TEST_TMP2_RAM_DATA_WIDTH); + +const TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_RAM_INITIALIZED = true; + +type FseTableRecord = common::FseTableRecord; + +const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE], common::LookupDecoderResp)[12] = [ + ( + u64[64]:[u64:0x72AAAAABBB1D25C0, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x16 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1a }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1e }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x1 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x2 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x3 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x5 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x6 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x7 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x9 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xa }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xb }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xd }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xe }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xf }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x11 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x12 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x13 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x15 }, + zero!(), ... + ], + common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + ), + ( + u64[64]:[u64:0x1861862062081932, u64:0xC18628A106184184, u64:0x850720FACC49238, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x11, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x5, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x5, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x5, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x5, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x7, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x13, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x7, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x7, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x11, num_of_bits: u8:0x5, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x5, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x7, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x5, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x5, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x13, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x11, num_of_bits: u8:0x5, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x11, num_of_bits: u8:0x5, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x5, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x5, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x70 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x3, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x3, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x5, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x6, base: u16:0x40 }, + zero!(), ... + ], + common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:7 } + ), + ( + u64[64]:[u64:0x60C3082082085072, u64:0x1C06F8077D850F20, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x5, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x38 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x7, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x3, base: u16:0x70 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x5, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x5, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x48 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x3, base: u16:0x78 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x28 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x5, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x50 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x4, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x5, base: u16:0x20 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x58 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x4, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x5, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x5, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x68 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x7, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x70 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x78 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x7, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x50 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x5, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x38 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x3c }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x44 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x7, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x48 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x7, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x4c }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x4, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x70 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x50 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x4, base: u16:0x50 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x54 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x58 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x5c }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x60 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x64 }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x68 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x6c }, + FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x58 }, + zero!(), ... + ], + common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:7 } + ), + ( + u64[64]:[u64:0x41081C158003A5D0, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1a }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1e }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x1 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x2 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x3 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x5 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x6 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x7 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x9 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xa }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xb }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xd }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xe }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xf }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x11 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x12 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x13 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x15 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x16 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x17 }, + zero!(), ... + ], + common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + ), + ( + u64[64]:[u64:0x1101141108088A1, u64:0xA210842108421011, u64:0xAC90E792007A5B4, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x11, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x3, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1c, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1c, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x13, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x3, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x3, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x3, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x1c, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x1c, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x6, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x1c, num_of_bits: u8:0x3, base: u16:0x8 }, + zero!(), ... + ], + common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:6 } + ), + ( + u64[64]:[u64:0x4AF830AC90E7920, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x2 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x6 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0xa }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0xc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0xe }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x12 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x16 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0x2 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1a }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1e }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0x6 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x1 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0xa }, + zero!(), ... + ], + common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + ), + ( + u64[64]:[u64:0xF47FFEBBFF1D25C0, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x16 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1a }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1e }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x1 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x2 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x3 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x5 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x6 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x7 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x9 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xa }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xb }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xd }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xe }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xf }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x11 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x12 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x13 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x15 }, + zero!(), ... + ], + common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + ), + ( + u64[64]:[u64:0xA84DF134544CA40, u64:0xEEC609988403B0C, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x3, base: u16:0x8 }, + zero!(), ... + ], + common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + ), + ( + u64[64]:[u64:0x38100EEC60998840, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x2, base: u16:0xc }, + zero!(), ... + ], + common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + ), + ( + u64[64]:[u64:0x6B1CA24D0CE43810, u64:0x6651065104A4DFFD, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x3, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x3, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x24, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x4, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x24, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x3, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x4, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x3, base: u16:0x8 }, + FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x3, base: u16:0x8 }, + zero!(), ... + ], + common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + ), + ( + u64[64]:[u64:0x604FC0502602814, u64:0xE030505040131FF6, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x3c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x3c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x3c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x3c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x44 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x44 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x44 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x44 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x4c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x4c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x4c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x4c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x54 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x54 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x54 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x54 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x5c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x5c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x5c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x5c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x64 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x64 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x64 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x64 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x6c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x6c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x6c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x6c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x74 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x74 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x74 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x74 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x7c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x7c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x7c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x80 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x80 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x7c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x84 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x84 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x80 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x80 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x88 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x84 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x84 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x8c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x88 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x88 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x88 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x8c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x8c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x8c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x90 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x90 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x90 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x94 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x94 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x94 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x90 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x98 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x98 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x94 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x9c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x9c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x98 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x98 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xa0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x9c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x9c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xa4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xa0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xa0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xa0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xa4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xa4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xa4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xa8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xa8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xa8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xac }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xac }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xac }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xa8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xb0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xb0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xac }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xb4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xb4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xb0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xb0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xb8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xb4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xb4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xbc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xb8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xb8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xb8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xbc }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xbc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xbc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xc0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xc0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xc0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xc4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xc4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xc4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xc8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xc8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xc8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xcc }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xcc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xcc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xd0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xcc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xd0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xd4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xd0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xd0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xd4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xd4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xd4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xd8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xd8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xd8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xd8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xdc }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xdc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xdc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xdc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xe0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xe0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xe0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xe4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xe4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xe0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xe4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xe8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xe4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xe8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xec }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xe8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xe8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xec }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xec }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xec }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xf0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xf0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xf0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xf0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xf4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xf4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xf4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xf4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xf8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xf8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xf8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xfc }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xfc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xf8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xfc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x100 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x100 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x100 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x104 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x104 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xfc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x104 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x108 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x100 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x108 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x10c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x108 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x104 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x10c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x10c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x108 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x110 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x110 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x110 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x10c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x114 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x114 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x110 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x114 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x118 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x118 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x118 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x11c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x11c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x114 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x11c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x120 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x118 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x120 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x124 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x120 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x11c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x124 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x124 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x120 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x128 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x128 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x128 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x124 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x12c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x12c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x128 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x12c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x130 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x130 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x130 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x134 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x134 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x12c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x134 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x138 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x130 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x138 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x13c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x138 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x134 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x13c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x13c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x138 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x140 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x140 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x140 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x13c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x144 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x144 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x140 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x148 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x144 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x148 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x144 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x148 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x14c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x148 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x14c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x14c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x150 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x150 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x150 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x154 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x14c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x154 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x154 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x150 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x158 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x158 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x158 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x154 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x15c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x15c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x158 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x160 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x15c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x160 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x15c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x160 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x164 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x160 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x164 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x164 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x168 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x168 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x168 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x16c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x164 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x16c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x16c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x168 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x170 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x170 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x170 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x16c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x174 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x174 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x170 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x178 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x174 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x178 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x174 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x178 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x17c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x178 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x17c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x17c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x180 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x17c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x180 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x184 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x180 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x180 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x184 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x188 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x184 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x188 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x18c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x184 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x188 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x18c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x188 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x18c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x190 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x190 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x18c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x190 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x194 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x190 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x194 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x194 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x198 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x194 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x198 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x19c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x198 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x198 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x19c }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1a0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x19c }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1a0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1a4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x19c }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1a0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1a4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1a0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1a4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1a8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1a8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1a4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1a8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1ac }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1a8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1ac }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1ac }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1b0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1ac }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1b0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1b4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1b0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1b0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1b4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1b8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1b4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1b8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1bc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1b4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1b8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1bc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1b8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1bc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1c0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1c0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1bc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1c0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1c4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1c0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1c4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1c8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1c4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1c4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1c8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1c8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1c8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1cc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1cc }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1cc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1cc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1d0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1d0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1d0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1d0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1d4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1d4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1d4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1d8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1d8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1d4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1d8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1dc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1d8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1dc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1e0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1dc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1dc }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1e0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1e0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1e0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1e4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1e4 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1e4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1e4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1e8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1e8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1e8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1e8 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1ec }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1ec }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1ec }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1f0 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1f0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1ec }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1f0 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1f4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1f0 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1f4 }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1f8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1f4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1f4 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1f8 }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1f8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1f8 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1fc }, + FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1fc }, + FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1fc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1fc }, + ], + common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:9 } + ), + ( + u64[64]:[u64:0x140FE03050504013, u64:0, ...], + FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x10 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x14 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x18 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x20 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x24 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x28 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x3c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x44 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x2c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x3c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x3c }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x30 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x34 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x38 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x3c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x44 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x4c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x54 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x5c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x44 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x4c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x54 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x44 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x4c }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x54 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x48 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x4c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x50 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x54 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x5c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x64 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x6c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x5c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x64 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x6c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x5c }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x64 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x6c }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x60 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x64 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x68 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x6c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x70 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x74 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x74 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x7c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x80 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x84 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x88 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x74 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x7c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x80 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x84 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x74 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x7c }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x80 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x84 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x88 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x78 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x7c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x80 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x84 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x88 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x8c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x8c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x90 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x94 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x98 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x9c }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xa0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x88 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x8c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x90 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x94 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x98 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x9c }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x8c }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x90 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x94 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x98 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x9c }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xa0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x90 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x94 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x98 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x9c }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xa0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xa4 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xa8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xac }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xb0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xb4 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xb8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xa0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xa4 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xa8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xac }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xb0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xb4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xa4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xa8 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xac }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xb0 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xb4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xb8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xa4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xa8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xac }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xb0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xb4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xb8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xbc }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xc0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xc4 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xc8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xcc }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xb8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xbc }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xc0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xc4 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xc8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xcc }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xbc }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xc0 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xc4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xc8 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xcc }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xd0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xbc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xcc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xd0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xd0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xd4 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xd8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xdc }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xe0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xe4 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xd0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xd4 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xd8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xdc }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xe0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xe4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xd4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xd8 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xdc }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xe0 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xe4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xd4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xd8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xdc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xe0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xe4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xe8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xe8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xec }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xf0 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xf4 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xf8 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xfc }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xe8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xec }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xf0 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xf4 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xf8 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xfc }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xe8 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xec }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xf0 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xf4 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xf8 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xfc }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xec }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xf0 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xf4 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xf8 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xfc }, + zero!(), ... + ], + common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:8 } + ), +]; + +#[test_proc] +proc CompLookupDecoderTest { + type Req = common::LookupDecoderReq; + type Resp = common::LookupDecoderResp; + type Status = common::LookupDecoderStatus; + + type MemReaderReq = mem_reader::MemReaderReq; + type MemReaderResp = mem_reader::MemReaderResp; + + type DpdRamWrReq = ram::WriteReq; + type DpdRamWrResp = ram::WriteResp; + type DpdRamRdReq = ram::ReadReq; + type DpdRamRdResp = ram::ReadResp; + + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; + type FseRamWrReq = ram::WriteReq; + type FseRamWrResp = ram::WriteResp; + + type TmpRamRdReq = ram::ReadReq; + type TmpRamRdResp = ram::ReadResp; + type TmpRamWrReq = ram::WriteReq; + type TmpRamWrResp = ram::WriteResp; + + type Tmp2RamRdReq = ram::ReadReq; + type Tmp2RamRdResp = ram::ReadResp; + type Tmp2RamWrReq = ram::WriteReq; + type Tmp2RamWrResp = ram::WriteResp; + + type TestcaseRamRdReq = ram::ReadReq; + type TestcaseRamRdResp = ram::ReadResp; + type TestcaseRamWrReq = ram::WriteReq; + type TestcaseRamWrResp = ram::WriteResp; + + type RefillStartReq = refilling_shift_buffer::RefillStart; + type SBOutput = refilling_shift_buffer::RefillingShiftBufferOutput; + type SBCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + + type AxiR = axi::AxiR; + type AxiAr = axi::AxiAr; + + terminator: chan out; + req_s: chan out; + resp_r: chan in; + fse_rd_req_s: chan out; + fse_rd_resp_r: chan in; + fse_wr_req_s: chan out; + fse_wr_resp_r: chan in; + testcase_wr_req_s: chan out; + testcase_wr_resp_r: chan in; + refill_req_s: chan out; + stop_flush_req_s: chan<()> out; + flushing_done_r: chan<()> in; + + config(terminator: chan out) { + let (req_s, req_r) = chan("req"); + let (resp_s, resp_r) = chan("resp"); + let (mem_rd_req_s, mem_rd_req_r) = chan("mem_rd_req"); + let (mem_rd_resp_s, mem_rd_resp_r) = chan("mem_rd_resp"); + + let (dpd_rd_req_s, dpd_rd_req_r) = chan("dpd_rd_req"); + let (dpd_rd_resp_s, dpd_rd_resp_r) = chan("dpd_rd_resp"); + let (dpd_wr_req_s, dpd_wr_req_r) = chan("dpd_wr_req"); + let (dpd_wr_resp_s, dpd_wr_resp_r) = chan("dpd_wr_resp"); + + let (tmp_rd_req_s, tmp_rd_req_r) = chan("tmp_rd_req"); + let (tmp_rd_resp_s, tmp_rd_resp_r) = chan("tmp_rd_resp"); + let (tmp_wr_req_s, tmp_wr_req_r) = chan("tmp_wr_req"); + let (tmp_wr_resp_s, tmp_wr_resp_r) = chan("tmp_wr_resp"); + + let (tmp2_rd_req_s, tmp2_rd_req_r) = chan("tmp2_rd_req"); + let (tmp2_rd_resp_s, tmp2_rd_resp_r) = chan("tmp2_rd_resp"); + let (tmp2_wr_req_s, tmp2_wr_req_r) = chan("tmp2_wr_req"); + let (tmp2_wr_resp_s, tmp2_wr_resp_r) = chan("tmp2_wr_resp"); + + let (fse_rd_req_s, fse_rd_req_r) = chan("fse_rd_req"); + let (fse_rd_resp_s, fse_rd_resp_r) = chan("fse_rd_resp"); + let (fse_wr_req_s, fse_wr_req_r) = chan("fse_wr_req"); + let (fse_wr_resp_s, fse_wr_resp_r) = chan("fse_wr_resp"); + + let (testcase_rd_req_s, testcase_rd_req_r) = chan("testcase_rd_req"); + let (testcase_rd_resp_s, testcase_rd_resp_r) = chan("testcase_rd_resp"); + let (testcase_wr_req_s, testcase_wr_req_r) = chan("testcase_wr_req"); + let (testcase_wr_resp_s, testcase_wr_resp_r) = chan("testcase_wr_resp"); + + let (buffer_ctrl_s, buffer_ctrl_r) = chan("buffer_ctrl"); + let (buffer_data_out_s, buffer_data_out_r) = chan("buffer_data_out"); + + spawn CompLookupDecoder< + TEST_AXI_DATA_WIDTH, + TEST_DPD_RAM_DATA_WIDTH, TEST_DPD_RAM_ADDR_WIDTH, TEST_DPD_RAM_NUM_PARTITIONS, + TEST_TMP_RAM_DATA_WIDTH, TEST_TMP_RAM_ADDR_WIDTH, TEST_TMP_RAM_NUM_PARTITIONS, + TEST_TMP2_RAM_DATA_WIDTH, TEST_TMP2_RAM_ADDR_WIDTH, TEST_TMP2_RAM_NUM_PARTITIONS, + TEST_FSE_RAM_DATA_WIDTH, TEST_FSE_RAM_ADDR_WIDTH, TEST_FSE_RAM_NUM_PARTITIONS, + >( + req_r, resp_s, + dpd_rd_req_s, dpd_rd_resp_r, dpd_wr_req_s, dpd_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s, tmp2_wr_resp_r, + fse_wr_req_s, fse_wr_resp_r, + buffer_ctrl_s, buffer_data_out_r, + ); + + spawn ram::RamModel< + TEST_DPD_RAM_DATA_WIDTH, TEST_DPD_RAM_SIZE, TEST_DPD_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, + >(dpd_rd_req_r, dpd_rd_resp_s, dpd_wr_req_r, dpd_wr_resp_s); + + spawn ram::RamModel< + TEST_FSE_RAM_DATA_WIDTH, TEST_FSE_RAM_SIZE, TEST_FSE_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, + >(fse_rd_req_r, fse_rd_resp_s, fse_wr_req_r, fse_wr_resp_s); + + spawn ram::RamModel< + TEST_TMP_RAM_DATA_WIDTH, TEST_TMP_RAM_SIZE, TEST_TMP_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, + >(tmp_rd_req_r, tmp_rd_resp_s, tmp_wr_req_r, tmp_wr_resp_s); + + spawn ram::RamModel< + TEST_TMP2_RAM_DATA_WIDTH, TEST_TMP2_RAM_SIZE, TEST_TMP2_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, + >(tmp2_rd_req_r, tmp2_rd_resp_s, tmp2_wr_req_r, tmp2_wr_resp_s); + + spawn ram::RamModel< + TEST_CASE_RAM_DATA_WIDTH, TEST_CASE_RAM_SIZE, TEST_CASE_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, + >(testcase_rd_req_r, testcase_rd_resp_s, testcase_wr_req_r, testcase_wr_resp_s); + + let (testcase_axi_r_s, testcase_axi_r_r) = chan("testcase_axi_r"); + let (testcase_axi_ar_s, testcase_axi_ar_r) = chan("testcase_axi_ar"); + + spawn axi_ram::AxiRamReader< + TEST_AXI_ADDR_WIDTH, TEST_AXI_DATA_WIDTH, TEST_AXI_DEST_WIDTH, TEST_AXI_ID_WIDTH, + TEST_CASE_RAM_SIZE, TEST_CASE_RAM_BASE_ADDR, TEST_CASE_RAM_DATA_WIDTH, + TEST_CASE_RAM_ADDR_WIDTH, TEST_CASE_RAM_NUM_PARTITIONS, + >(testcase_axi_ar_r, testcase_axi_r_s, testcase_rd_req_s, testcase_rd_resp_r); + + spawn mem_reader::MemReader< + TEST_AXI_DATA_WIDTH, TEST_AXI_ADDR_WIDTH, TEST_AXI_DEST_WIDTH, TEST_AXI_ID_WIDTH + >(mem_rd_req_r, mem_rd_resp_s, testcase_axi_ar_s, testcase_axi_r_r); + + let (refill_req_s, refill_req_r) = chan("start_req"); + let (stop_flush_req_s, stop_flush_req_r) = chan<()>("stop_flush_req"); + let (flushing_done_s, flushing_done_r) = chan<()>("flushing_done"); + + spawn refilling_shift_buffer::RefillingShiftBuffer( + mem_rd_req_s, mem_rd_resp_r, + refill_req_r, stop_flush_req_r, + buffer_ctrl_r, buffer_data_out_s, + flushing_done_s, + ); + + ( + terminator, req_s, resp_r, fse_rd_req_s, fse_rd_resp_r, + fse_wr_req_s, fse_wr_resp_r, testcase_wr_req_s, testcase_wr_resp_r, + refill_req_s, stop_flush_req_s, flushing_done_r, + ) + } + + init {} + + next(_: ()) { + let tok = join(); + // This has to be outside of unroll_for!, otherwise typechecker reports type mismatch on identical types + let req_start = Req {}; + + let tok = unroll_for!(test_i, tok): (u32, token) in range(u32:0, array_size(COMP_LOOKUP_DECODER_TESTCASES)) { + let (input, output, resp_ok) = COMP_LOOKUP_DECODER_TESTCASES[test_i]; + + trace_fmt!("Loading testcase {:x}", test_i); + let tok = for ((i, input_data), tok): ((u32, u64), token) in enumerate(input) { + let req = TestcaseRamWrReq { + addr: i as uN[TEST_CASE_RAM_ADDR_WIDTH], + data: input_data as uN[TEST_CASE_RAM_DATA_WIDTH], + mask: uN[TEST_CASE_RAM_NUM_PARTITIONS]:0x1 + }; + let tok = send(tok, testcase_wr_req_s, req); + let (tok, _) = recv(tok, testcase_wr_resp_r); + tok + }(tok); + + trace_fmt!("Running FSE lookup decoder on testcase {:x}", test_i); + let tok = send(tok, refill_req_s, RefillStartReq { + start_addr: uN[TEST_AXI_ADDR_WIDTH]:0x0 + }); + let tok = send(tok, req_s, req_start); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, resp_ok); + + let tok = for ((i, output_data), tok): ((u32, FseTableRecord), token) in enumerate(output) { + let req = FseRamRdReq { + addr: i as uN[TEST_FSE_RAM_ADDR_WIDTH], + mask: std::unsigned_max_value(), + }; + let tok = send(tok, fse_rd_req_s, req); + let (tok, resp) = recv(tok, fse_rd_resp_r); + assert_eq(fse_table_creator::bits_to_fse_record(resp.data), output_data); + + // erase output for next test to start with clean memory + let clear_req = FseRamWrReq { + addr: i as uN[TEST_FSE_RAM_ADDR_WIDTH], + mask: std::unsigned_max_value(), + data: uN[TEST_FSE_RAM_DATA_WIDTH]:0x0, + }; + let tok = send(tok, fse_wr_req_s, clear_req); + let (tok, _) = recv(tok, fse_wr_resp_r); + tok + }(tok); + + let tok = send(tok, stop_flush_req_s, ()); + let (tok, ()) = recv(tok, flushing_done_r); + + tok + }(tok); + + send(tok, terminator, true); + } +} diff --git a/xls/modules/zstd/fse_lookup_dec.x b/xls/modules/zstd/fse_lookup_dec.x index 7518c0d2bc..581dcac5d2 100644 --- a/xls/modules/zstd/fse_lookup_dec.x +++ b/xls/modules/zstd/fse_lookup_dec.x @@ -23,20 +23,16 @@ import xls.modules.zstd.fse_table_creator; import xls.modules.zstd.refilling_shift_buffer; import xls.modules.zstd.fse_proba_freq_dec; import xls.modules.shift_buffer.shift_buffer; +import xls.modules.zstd.comp_lookup_dec; +import xls.modules.zstd.rle_lookup_dec; +import xls.modules.zstd.refilling_shift_buffer_mux; +import xls.modules.zstd.ram_mux; type AccuracyLog = common::FseAccuracyLog; -pub enum FseLookupDecoderStatus: u1 { - OK = 0, - ERROR = 1, -} - -pub struct FseLookupDecoderReq {} - -pub struct FseLookupDecoderResp { - status: FseLookupDecoderStatus, - accuracy_log: common::FseAccuracyLog -} +pub struct FseLookupDecoderReq { is_rle: bool } +pub type FseLookupDecoderStatus = common::LookupDecoderStatus; +pub type FseLookupDecoderResp = common::LookupDecoderResp; pub proc FseLookupDecoder< AXI_DATA_W: u32, @@ -48,15 +44,14 @@ pub proc FseLookupDecoder< > { type Req = FseLookupDecoderReq; type Resp = FseLookupDecoderResp; - type Status = FseLookupDecoderStatus; - - type FseTableStart = fse_table_creator::FseStartMsg; - type DpdRamWrReq = ram::WriteReq; - type DpdRamWrResp = ram::WriteResp; type DpdRamRdReq = ram::ReadReq; type DpdRamRdResp = ram::ReadResp; + type DpdRamWrReq = ram::WriteReq; + type DpdRamWrResp = ram::WriteResp; + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; type FseRamWrReq = ram::WriteReq; type FseRamWrResp = ram::WriteResp; @@ -73,23 +68,38 @@ pub proc FseLookupDecoder< type SBOutput = refilling_shift_buffer::RefillingShiftBufferOutput; type SBCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; - type FsePFDecReq = fse_proba_freq_dec::FseProbaFreqDecoderReq; - type FsePFDecResp = fse_proba_freq_dec::FseProbaFreqDecoderResp; - type FsePFDecStatus = fse_proba_freq_dec::FseProbaFreqDecoderStatus; + type LookupDecoderReq = common::LookupDecoderReq; + type LookupDecoderResp = common::LookupDecoderResp; - req_r: chan in; - resp_s: chan out; - - fse_pf_dec_req_s: chan out; - fse_pf_dec_resp_r: chan in; - fse_table_start_s: chan out; - fse_table_finish_r: chan<()> in; init {} + fse_lookup_dec_req_r: chan in; + fse_lookup_dec_resp_s: chan out; + + comp_lookup_req_s: chan out; + comp_lookup_resp_r: chan in; + + rle_lookup_req_s: chan out; + rle_lookup_resp_r: chan in; + + shift_buffer_sel_req_s: chan out; + shift_buffer_sel_resp_r: chan<()> in; + + fse_ram_sel_req_s: chan out; + + fse_rd_req0_s: chan out; + fse_rd_resp0_r: chan in; + + fse_rd_req1_s: chan out; + fse_rd_resp1_r: chan in; + + fse_rd_req_r: chan in; + fse_rd_resp_s: chan out; + config( - req_r: chan in, - resp_s: chan out, + fse_lookup_dec_req_r: chan in, + fse_lookup_dec_resp_s: chan out, dpd_rd_req_s: chan out, dpd_rd_resp_r: chan in, @@ -109,72 +119,130 @@ pub proc FseLookupDecoder< fse_wr_req_s: chan out, fse_wr_resp_r: chan in, - buffer_ctrl_s: chan out, - buffer_data_out_r: chan in, + shift_buffer_ctrl_s: chan out, + shift_buffer_data_r: chan in, ) { const CHANNEL_DEPTH = u32:1; - let (fse_table_start_s, fse_table_start_r) = chan("fse_table_start"); - let (fse_table_finish_s, fse_table_finish_r) = chan<(), CHANNEL_DEPTH>("fse_table_finish"); + let (shift_buffer_sel_req_s, shift_buffer_sel_req_r) = chan("shift_buffer_sel_req"); + let (shift_buffer_sel_resp_s, shift_buffer_sel_resp_r) = chan<(), CHANNEL_DEPTH>("shift_buffer_sel_resp"); + + let (shift_buffer_ctrl0_s, shift_buffer_ctrl0_r) = chan("shift_buffer_ctrl0"); + let (shift_buffer_data0_s, shift_buffer_data0_r) = chan("shift_buffer_data0"); + + let (shift_buffer_ctrl1_s, shift_buffer_ctrl1_r) = chan("shift_buffer_ctrl1"); + let (shift_buffer_data1_s, shift_buffer_data1_r) = chan("shift_buffer_data1"); + + spawn refilling_shift_buffer_mux::RefillingShiftBufferMux( + shift_buffer_sel_req_r, shift_buffer_sel_resp_s, + shift_buffer_ctrl0_r, shift_buffer_data0_s, + shift_buffer_ctrl1_r, shift_buffer_data1_s, + shift_buffer_ctrl_s, shift_buffer_data_r, + ); + + let (fse_ram_sel_req_s, fse_ram_sel_req_r) = chan("fse_ram_sel_req"); + + let (fse_rd_req_s, fse_rd_req_r) = chan("fse_rd_req"); + let (fse_rd_resp_s, fse_rd_resp_r) = chan("fse_rd_resp"); - spawn fse_table_creator::FseTableCreator< + let (fse_rd_req0_s, fse_rd_req0_r) = chan("fse_rd_req0"); + let (fse_rd_resp0_s, fse_rd_resp0_r) = chan("fse_rd_resp0"); + let (fse_wr_req0_s, fse_wr_req0_r) = chan("fse_wr_req0"); + let (fse_wr_resp0_s, fse_wr_resp0_r) = chan("fse_wr_resp0"); + + let (fse_rd_req1_s, fse_rd_req1_r) = chan("fse_wr_req1"); + let (fse_rd_resp1_s, fse_rd_resp1_r) = chan("fse_wr_resp1"); + let (fse_wr_req1_s, fse_wr_req1_r) = chan("fse_wr_req1"); + let (fse_wr_resp1_s, fse_wr_resp1_r) = chan("fse_wr_resp1"); + + spawn ram_mux::RamMux< + FSE_RAM_ADDR_W, FSE_RAM_DATA_W, FSE_RAM_NUM_PARTITIONS, + >( + fse_ram_sel_req_r, + fse_rd_req0_r, fse_rd_resp0_s, fse_wr_req0_r, fse_wr_resp0_s, + fse_rd_req1_r, fse_rd_resp1_s, fse_wr_req1_r, fse_wr_resp1_s, + fse_rd_req_s, fse_rd_resp_r, fse_wr_req_s, fse_wr_resp_r, + ); + + let (comp_lookup_req_s, comp_lookup_req_r) = chan("comp_lookup_req"); + let (comp_lookup_resp_s, comp_lookup_resp_r) = chan("comp_lookup_resp"); + + spawn comp_lookup_dec::CompLookupDecoder< + AXI_DATA_W, DPD_RAM_DATA_W, DPD_RAM_ADDR_W, DPD_RAM_NUM_PARTITIONS, - FSE_RAM_DATA_W, FSE_RAM_ADDR_W, FSE_RAM_NUM_PARTITIONS, TMP_RAM_DATA_W, TMP_RAM_ADDR_W, TMP_RAM_NUM_PARTITIONS, TMP2_RAM_DATA_W, TMP2_RAM_ADDR_W, TMP2_RAM_NUM_PARTITIONS, + FSE_RAM_DATA_W, FSE_RAM_ADDR_W, FSE_RAM_NUM_PARTITIONS, >( - fse_table_start_r, fse_table_finish_s, - dpd_rd_req_s, dpd_rd_resp_r, - fse_wr_req_s, fse_wr_resp_r, + comp_lookup_req_r, comp_lookup_resp_s, + dpd_rd_req_s, dpd_rd_resp_r, dpd_wr_req_s, dpd_wr_resp_r, tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s, tmp2_wr_resp_r, + + fse_wr_req0_s, fse_wr_resp0_r, + shift_buffer_ctrl0_s, shift_buffer_data0_r, ); - let (fse_pf_dec_req_s, fse_pf_dec_req_r) = chan("fse_pf_dec_req"); - let (fse_pf_dec_resp_s, fse_pf_dec_resp_r) = chan("fse_pf_dec_resp"); + let (rle_lookup_req_s, rle_lookup_req_r) = chan("rle_lookup_req"); + let (rle_lookup_resp_s, rle_lookup_resp_r) = chan("rle_lookup_resp"); - spawn fse_proba_freq_dec::FseProbaFreqDecoder< - DPD_RAM_DATA_W, DPD_RAM_ADDR_W, DPD_RAM_NUM_PARTITIONS, + spawn rle_lookup_dec::RleLookupDecoder< + AXI_DATA_W, FSE_RAM_DATA_W, FSE_RAM_ADDR_W, FSE_RAM_NUM_PARTITIONS, >( - fse_pf_dec_req_r, fse_pf_dec_resp_s, - buffer_ctrl_s, buffer_data_out_r, - dpd_wr_req_s, dpd_wr_resp_r, + rle_lookup_req_r, rle_lookup_resp_s, + fse_wr_req1_s, fse_wr_resp1_r, + shift_buffer_ctrl1_s, shift_buffer_data1_r, ); ( - req_r, resp_s, - fse_pf_dec_req_s, fse_pf_dec_resp_r, - fse_table_start_s, fse_table_finish_r, + fse_lookup_dec_req_r, fse_lookup_dec_resp_s, + comp_lookup_req_s, comp_lookup_resp_r, + rle_lookup_req_s, rle_lookup_resp_r, + + shift_buffer_sel_req_s, shift_buffer_sel_resp_r, + fse_ram_sel_req_s, + + fse_rd_req0_s, fse_rd_resp0_r, + fse_rd_req1_s, fse_rd_resp1_r, + + fse_rd_req_r, fse_rd_resp_s, ) } next(state: ()) { - let tok = join(); - let (tok, start_req) = recv(tok, req_r); - - // start FSE probability frequency decoder - let tok = send(tok, fse_pf_dec_req_s, FsePFDecReq {}); - - // wait for completion from FSE probability frequency decoder - let (tok, pf_dec_res) = recv(tok, fse_pf_dec_resp_r); - trace_fmt!("FSE prob decoded"); - - let pf_dec_ok = pf_dec_res.status == FsePFDecStatus::OK; - // run FSE Table creation conditional or previous processing succeeding - let tok = send_if(tok, fse_table_start_s, pf_dec_ok, FseTableStart { - num_symbs: pf_dec_res.symbol_count, - accuracy_log: pf_dec_res.accuracy_log, - }); - // wait for completion from FSE table creator - let (tok, ()) = recv_if(tok, fse_table_finish_r, pf_dec_ok, ()); - trace_fmt!("FSE table created"); - - let resp = if pf_dec_ok { - Resp { status: Status::OK, accuracy_log: pf_dec_res.accuracy_log } - } else { - Resp { status: Status::ERROR, ..zero!() } - }; - send(tok, resp_s, resp); + let tok0 = join(); + + let (tok1, req) = recv(tok0, fse_lookup_dec_req_r); + + let sel = (req.is_rle == true); + let tok2_0 = send(tok1, shift_buffer_sel_req_s, sel); + let (tok3_0, _) = recv(tok2_0, shift_buffer_sel_resp_r); + + let tok2_1 = send(tok1, fse_ram_sel_req_s, sel); + // let (tok, _) = recv(tok, fse_ram_sel_resp_r); + + let tok3 = join(tok2_1, tok3_0); + + let tok4_0 = send_if(tok3, rle_lookup_req_s, req.is_rle, LookupDecoderReq {}); + let (tok5_0, rle_lookup_resp) = recv_if(tok4_0, rle_lookup_resp_r, req.is_rle, zero!()); + + let tok4_1 = send_if(tok3, comp_lookup_req_s, !req.is_rle, LookupDecoderReq {}); + let (tok5_1, comp_lookup_resp) = recv_if(tok4_1, comp_lookup_resp_r, !req.is_rle, zero!()); + + let tok5 = join(tok5_0, tok5_1); + + let resp = if req.is_rle { rle_lookup_resp } else { comp_lookup_resp }; + let tok6 = send(tok5, fse_lookup_dec_resp_s, resp); + + // unused channels + send_if(tok0, fse_rd_req0_s, false, zero!()); + recv_if(tok0, fse_rd_resp0_r, false, zero!()); + + send_if(tok0, fse_rd_req1_s, false, zero!()); + recv_if(tok0, fse_rd_resp1_r, false, zero!()); + + send_if(tok0, fse_rd_resp_s, false, zero!()); + recv_if(tok0, fse_rd_req_r, false, zero!()); } } @@ -226,1409 +294,123 @@ const TEST_RAM_INITIALIZED = true; type FseTableRecord = common::FseTableRecord; -const FSE_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE], FseLookupDecoderResp)[12] = [ +const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE], FseLookupDecoderReq, FseLookupDecoderResp)[5] = [ + // RLE ( - u64[64]:[u64:0x72AAAAABBB1D25C0, u64:0, ...], + u64[64]:[u64:0xA, u64:0, ...], FseTableRecord[TEST_FSE_RAM_SIZE]:[ - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x16 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1a }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1c }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x5, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1e }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x1 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x2 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x5, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x3 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x5 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x5, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x6 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x7 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x9 }, - FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x5, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xa }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xb }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xc }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xd }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xe }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xf }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x11 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x12 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x13 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x14 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x15 }, + FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x0, base: u16:0x0 }, zero!(), ... ], - FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + FseLookupDecoderReq { is_rle: true }, + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:0 } ), ( - u64[64]:[u64:0x1861862062081932, u64:0xC18628A106184184, u64:0x850720FACC49238, u64:0, ...], + u64[64]:[u64:0x2, u64:0, ...], FseTableRecord[TEST_FSE_RAM_SIZE]:[ - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x11, num_of_bits: u8:0x5, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x28 }, - FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x5, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x5, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x5, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x28 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x38 }, - FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x5, base: u16:0x60 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x6, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x5, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x6, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x48 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x20 }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x7, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x38 }, - FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x50 }, - FseTableRecord { symbol: u8:0x13, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x6, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x50 }, - FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x7, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x60 }, - FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x58 }, - FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x7, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x70 }, - FseTableRecord { symbol: u8:0x11, num_of_bits: u8:0x5, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x48 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x60 }, - FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x6, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x3, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x5, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x50 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x68 }, - FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x5, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x30 }, - FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x7, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x58 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x70 }, - FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x5, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x78 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x40 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x60 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x5, base: u16:0x60 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x30 }, - FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x68 }, - FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x3, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x13, num_of_bits: u8:0x6, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x5, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x70 }, - FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x3, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x11, num_of_bits: u8:0x5, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x6, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x50 }, - FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x6, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x50 }, - FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x3, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x11, num_of_bits: u8:0x5, base: u16:0x60 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x78 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x4, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x6, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x60 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x50 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x5, base: u16:0x60 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x14 }, - FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x4, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x5, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x4, base: u16:0x70 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x60 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x6, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x6, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x1c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x70 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x6, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x3, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x60 }, - FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x6, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x3, base: u16:0x28 }, - FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x6, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x5, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x24 }, - FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x70 }, - FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x6, base: u16:0x40 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x0, base: u16:0x0 }, zero!(), ... ], - FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:7 } + FseLookupDecoderReq { is_rle: true }, + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:0 } ), ( - u64[64]:[u64:0x60C3082082085072, u64:0x1C06F8077D850F20, u64:0, ...], + u64[64]:[u64:0x7, u64:0, ...], FseTableRecord[TEST_FSE_RAM_SIZE]:[ - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x5, base: u16:0x40 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x38 }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x7, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x3, base: u16:0x70 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x40 }, - FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x5, base: u16:0x60 }, - FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x5, base: u16:0x40 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x48 }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x3, base: u16:0x78 }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x28 }, - FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x5, base: u16:0x60 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x50 }, - FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x4, base: u16:0x60 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x20 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x5, base: u16:0x20 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x58 }, - FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x4, base: u16:0x70 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x30 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x38 }, - FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x5, base: u16:0x40 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x60 }, - FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x5, base: u16:0x60 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x10 }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x14 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x68 }, - FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x18 }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x48 }, - FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x7, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x1c }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x70 }, - FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x10 }, - FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x3, base: u16:0x78 }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x50 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x7, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x20 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x58 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x40 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x24 }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x60 }, - FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x4, base: u16:0x20 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x28 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x50 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x2c }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x68 }, - FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x5, base: u16:0x60 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x30 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x60 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x34 }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x70 }, - FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x38 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x10 }, - FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x28 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x3c }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x3, base: u16:0x78 }, - FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x40 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x14 }, - FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x4, base: u16:0x20 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x44 }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x7, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x48 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x18 }, - FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x4, base: u16:0x30 }, - FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x4, base: u16:0x20 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x1c }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x7, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x4c }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x20 }, - FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x4, base: u16:0x40 }, - FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x4, base: u16:0x30 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x24 }, - FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x38 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x70 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x50 }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x4, base: u16:0x50 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x54 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x28 }, - FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x58 }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x5c }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x2c }, - FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x48 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x8 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x60 }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x2, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x6, base: u16:0x40 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x64 }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x30 }, - FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x50 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x4, base: u16:0x30 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x68 }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x2, base: u16:0x14 }, - FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x6, base: u16:0x40 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x2, base: u16:0x6c }, - FseTableRecord { symbol: u8:0xd, num_of_bits: u8:0x2, base: u16:0x34 }, - FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x58 }, + FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x0, base: u16:0x0 }, zero!(), ... ], - FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:7 } + FseLookupDecoderReq { is_rle: true }, + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:0 } ), + + // COMPRESSED ( - u64[64]:[u64:0x41081C158003A5D0, u64:0, ...], + u64[64]:[u64:0x72AAAAABBB1D25C0, u64:0, ...], FseTableRecord[TEST_FSE_RAM_SIZE]:[ + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x16 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x18 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1a }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1c }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x5, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1e }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x5, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x1 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x2 }, + FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x5, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x3 }, - FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x5, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x4 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x5 }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x5, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x6 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x7 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x8 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x9 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x5, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xa }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xb }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xc }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xd }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xe }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xf }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x10 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x11 }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x12 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x13 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x5, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x14 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x15 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x16 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x17 }, zero!(), ... ], + FseLookupDecoderReq { is_rle: false }, FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } ), ( - u64[64]:[u64:0x1101141108088A1, u64:0xA210842108421011, u64:0xAC90E792007A5B4, u64:0, ...], - FseTableRecord[TEST_FSE_RAM_SIZE]:[ - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xe, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x11, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x28 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x3, base: u16:0x38 }, - FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x3, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x3, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x3, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x1c, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x1c, num_of_bits: u8:0x4, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x28 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xc, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x13, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x3, base: u16:0x28 }, - FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x3, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x3, base: u16:0x38 }, - FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x1c, num_of_bits: u8:0x4, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x1c, num_of_bits: u8:0x3, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x38 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x15, num_of_bits: u8:0x6, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x18, num_of_bits: u8:0x4, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x14 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x19, num_of_bits: u8:0x2, base: u16:0x1c }, - FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x1a, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x28 }, - FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x1b, num_of_bits: u8:0x3, base: u16:0x38 }, - FseTableRecord { symbol: u8:0x1c, num_of_bits: u8:0x3, base: u16:0x8 }, - zero!(), ... - ], - FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:6 } - ), - ( - u64[64]:[u64:0x4AF830AC90E7920, u64:0, ...], + u64[64]:[u64:0x41081C158003A5D0, u64:0, ...], FseTableRecord[TEST_FSE_RAM_SIZE]:[ - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x2 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x6 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0xa }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x14 }, - FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x5, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0xc }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0xe }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x1c }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x12 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x14 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x16 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0x2 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0x4 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1a }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1c }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1e }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0x6 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x1 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0xa }, - zero!(), ... - ], - FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } - ), - ( - u64[64]:[u64:0xF47FFEBBFF1D25C0, u64:0, ...], - FseTableRecord[TEST_FSE_RAM_SIZE]:[ - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x16 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1a }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1c }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x5, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x1, base: u16:0x1e }, + FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x5, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x1 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x2 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x5, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x3 }, + FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x5, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x4 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x5 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x5, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x6 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x7 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x8 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x9 }, - FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x5, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xa }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xb }, + FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xc }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xd }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xe }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0xf }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x10 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x11 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x5, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x12 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x13 }, + FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x5, base: u16:0x0 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x14 }, FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x15 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x16 }, + FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x17 }, zero!(), ... ], + FseLookupDecoderReq { is_rle: false }, FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } ), - ( - u64[64]:[u64:0xA84DF134544CA40, u64:0xEEC609988403B0C, u64:0, ...], - FseTableRecord[TEST_FSE_RAM_SIZE]:[ - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x3, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x3, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x3, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x3, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x16, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x3, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x3, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x3, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x3, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x3, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x3, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x3, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x3, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x3, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x3, base: u16:0x8 }, - zero!(), ... - ], - FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } - ), - ( - u64[64]:[u64:0x38100EEC60998840, u64:0, ...], - FseTableRecord[TEST_FSE_RAM_SIZE]:[ - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x3, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x3, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x3, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x3, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x3, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x3, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x3, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x3, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x3, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x3, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x3, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x8, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x2, base: u16:0xc }, - zero!(), ... - ], - FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } - ), - ( - u64[64]:[u64:0x6B1CA24D0CE43810, u64:0x6651065104A4DFFD, u64:0, ...], - FseTableRecord[TEST_FSE_RAM_SIZE]:[ - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x3, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x3, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x3, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x24, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x3, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x6, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x4, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x3, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x24, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x3, base: u16:0x0 }, - FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x3, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x3, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x4, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x3, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x12, num_of_bits: u8:0x4, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x3, base: u16:0x8 }, - FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x3, base: u16:0x8 }, - zero!(), ... - ], - FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } - ), - ( - u64[64]:[u64:0x604FC0502602814, u64:0xE030505040131FF6, u64:0, ...], - FseTableRecord[TEST_FSE_RAM_SIZE]:[ - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x14 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x14 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x14 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x14 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x24 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x24 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x24 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x28 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x24 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x28 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x28 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x28 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x2c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x2c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x2c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x2c }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x34 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x34 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x34 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x34 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x38 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x38 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x38 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x38 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x3c }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x3c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x3c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x3c }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x44 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x44 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x44 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x48 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x48 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x48 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x44 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x4c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x4c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x4c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x48 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x50 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x50 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x4c }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x54 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x50 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x54 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x50 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x54 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x58 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x54 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x58 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x58 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x5c }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x5c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x5c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x60 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x58 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x60 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x60 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x5c }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x64 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x64 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x64 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x60 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x68 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x68 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x64 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x6c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x68 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x6c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x68 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x6c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x70 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x6c }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x70 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x70 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x74 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x74 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x74 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x78 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x70 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x78 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x78 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x74 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x7c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x7c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x7c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x78 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x80 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x80 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x7c }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x84 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x84 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x80 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x80 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x88 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x84 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x84 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x8c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x88 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x88 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x88 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x8c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x8c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x8c }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x90 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x90 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x90 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x94 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x94 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x94 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x90 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x98 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x98 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x94 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x9c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x9c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x98 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x98 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xa0 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x9c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x9c }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xa4 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xa0 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xa0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xa0 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xa4 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xa4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xa4 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xa8 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xa8 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xa8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xac }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xac }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xac }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xa8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xb0 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xb0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xac }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xb4 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xb4 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xb0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xb0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xb8 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xb4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xb4 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xbc }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xb8 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xb8 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xb8 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xbc }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xbc }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xbc }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xc0 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xc0 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xc0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc0 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xc4 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xc4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc4 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xc4 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xc8 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xc8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xc8 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xcc }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xcc }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xcc }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xd0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xcc }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xd0 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xd4 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xd0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xd0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xd4 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xd4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xd4 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xd8 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xd8 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xd8 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xd8 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xdc }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xdc }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xdc }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xdc }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xe0 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xe0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xe0 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xe4 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xe4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xe0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xe4 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xe8 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xe4 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xe8 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xec }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xe8 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xe8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xec }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xec }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xec }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xf0 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xf0 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xf0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xf0 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xf4 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xf4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xf4 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xf4 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xf8 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xf8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xf8 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0xfc }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0xfc }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xf8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0xfc }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x100 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x100 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x100 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x104 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x104 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xfc }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x104 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x108 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x100 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x108 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x10c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x108 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x104 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x10c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x10c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x108 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x110 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x110 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x110 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x10c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x114 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x114 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x110 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x114 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x118 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x118 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x118 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x11c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x11c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x114 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x11c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x120 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x118 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x120 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x124 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x120 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x11c }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x124 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x124 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x120 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x128 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x128 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x128 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x124 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x12c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x12c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x128 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x12c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x130 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x130 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x130 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x134 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x134 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x12c }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x134 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x138 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x130 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x138 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x13c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x138 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x134 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x13c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x13c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x138 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x140 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x140 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x140 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x13c }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x144 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x144 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x140 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x148 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x144 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x148 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x144 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x148 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x14c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x148 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x14c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x14c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x150 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x150 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x150 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x154 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x14c }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x154 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x154 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x150 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x158 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x158 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x158 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x154 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x15c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x15c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x158 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x160 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x15c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x160 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x15c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x160 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x164 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x160 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x164 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x164 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x168 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x168 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x168 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x16c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x164 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x16c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x16c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x168 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x170 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x170 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x170 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x16c }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x174 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x174 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x170 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x178 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x174 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x178 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x174 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x178 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x17c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x178 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x17c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x17c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x180 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x17c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x180 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x184 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x180 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x180 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x184 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x188 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x184 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x188 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x18c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x184 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x188 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x18c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x188 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x18c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x190 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x190 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x18c }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x190 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x194 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x190 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x194 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x194 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x198 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x194 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x198 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x19c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x198 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x198 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x19c }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1a0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x19c }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1a0 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1a4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x19c }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1a0 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1a4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1a0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1a4 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1a8 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1a8 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1a4 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1a8 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1ac }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1a8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1ac }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1ac }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1b0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1ac }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1b0 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1b4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1b0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1b0 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1b4 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1b8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1b4 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1b8 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1bc }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1b4 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1b8 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1bc }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1b8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1bc }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1c0 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1c0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1bc }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1c0 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1c4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1c0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1c4 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1c8 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1c4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1c4 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1c8 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1c8 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1c8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1cc }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1cc }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1cc }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1cc }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1d0 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1d0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1d0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1d0 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1d4 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1d4 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1d4 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1d8 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1d8 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1d4 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1d8 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1dc }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1d8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1dc }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1e0 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1dc }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1dc }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1e0 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1e0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1e0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1e4 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1e4 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1e4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1e4 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1e8 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1e8 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1e8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1e8 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1ec }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1ec }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1ec }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1f0 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1f0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1ec }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1f0 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1f4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1f0 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1f4 }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1f8 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1f4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1f4 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1f8 }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1f8 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1f8 }, - FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x2, base: u16:0x1fc }, - FseTableRecord { symbol: u8:0x17, num_of_bits: u8:0x2, base: u16:0x1fc }, - FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1fc }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1fc }, - ], - FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:9 } - ), - ( - u64[64]:[u64:0x140FE03050504013, u64:0, ...], - FseTableRecord[TEST_FSE_RAM_SIZE]:[ - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x14 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x14 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x14 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x8 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x10 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x14 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x1c }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x24 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x28 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x2c }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x1c }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x24 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x28 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x1c }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x24 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x28 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x2c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x18 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x20 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x24 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x28 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x2c }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x34 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x38 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x3c }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x44 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x2c }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x34 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x38 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x3c }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x34 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x38 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x3c }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x30 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x34 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x38 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x3c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x40 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x44 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x48 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x4c }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x50 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x54 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x58 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x5c }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x44 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x48 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x4c }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x50 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x54 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x58 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x44 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x48 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x4c }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x50 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x54 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x58 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x48 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x4c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x50 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x54 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x58 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x5c }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x60 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x64 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x68 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x6c }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x70 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x5c }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x60 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x64 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x68 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x6c }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x70 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x5c }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x60 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x64 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x68 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x6c }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x70 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x60 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x64 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x68 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x6c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x70 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x74 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x74 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x78 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x7c }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x80 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x84 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x88 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x74 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x78 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x7c }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x80 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x84 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x74 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x78 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x7c }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x80 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x84 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x88 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x78 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x7c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x80 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x84 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x88 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x8c }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x8c }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x90 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x94 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x98 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0x9c }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xa0 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x88 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x8c }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x90 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x94 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x98 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0x9c }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x8c }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x90 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x94 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x98 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0x9c }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xa0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x90 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x94 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x98 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x9c }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xa0 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xa4 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xa8 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xac }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xb0 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xb4 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xb8 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xa0 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xa4 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xa8 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xac }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xb0 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xb4 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xa4 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xa8 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xac }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xb0 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xb4 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xb8 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xa4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xa8 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xac }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xb0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xb4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xb8 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xbc }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xc0 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xc4 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xc8 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xcc }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xb8 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xbc }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xc0 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xc4 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xc8 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xcc }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xbc }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xc0 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xc4 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xc8 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xcc }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xd0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xbc }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xc8 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xcc }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xd0 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xd0 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xd4 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xd8 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xdc }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xe0 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xe4 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xd0 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xd4 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xd8 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xdc }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xe0 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xe4 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xd4 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xd8 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xdc }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xe0 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xe4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xd4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xd8 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xdc }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xe0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xe4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xe8 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xe8 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xec }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xf0 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xf4 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xf8 }, - FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x2, base: u16:0xfc }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xe8 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xec }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xf0 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xf4 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xf8 }, - FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x2, base: u16:0xfc }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xe8 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xec }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xf0 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xf4 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xf8 }, - FseTableRecord { symbol: u8:0x3, num_of_bits: u8:0x2, base: u16:0xfc }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xec }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xf0 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xf4 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xf8 }, - FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xfc }, - zero!(), ... - ], - FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:8 } - ), ]; #[test_proc] proc FseLookupDecoderTest { type Req = FseLookupDecoderReq; type Resp = FseLookupDecoderResp; - type Status = FseLookupDecoderStatus; type MemReaderReq = mem_reader::MemReaderReq; type MemReaderResp = mem_reader::MemReaderResp; @@ -1787,13 +569,12 @@ proc FseLookupDecoderTest { next(_: ()) { let tok = join(); - // This has to be outside of unroll_for!, otherwise typechecker reports type mismatch on identical types - let req_start = Req {}; - let tok = unroll_for!(test_i, tok): (u32, token) in range(u32:0, array_size(FSE_LOOKUP_DECODER_TESTCASES)) { - let (input, output, resp_ok) = FSE_LOOKUP_DECODER_TESTCASES[test_i]; + let tok = unroll_for!(test_i, tok): (u32, token) in range(u32:0, array_size(COMP_LOOKUP_DECODER_TESTCASES)) { + let (input, output, req, exp_resp) = COMP_LOOKUP_DECODER_TESTCASES[test_i]; trace_fmt!("Loading testcase {:x}", test_i); + let tok = for ((i, input_data), tok): ((u32, u64), token) in enumerate(input) { let req = TestcaseRamWrReq { addr: i as uN[TEST_CASE_RAM_ADDR_WIDTH], @@ -1809,9 +590,10 @@ proc FseLookupDecoderTest { let tok = send(tok, refill_req_s, RefillStartReq { start_addr: uN[TEST_AXI_ADDR_WIDTH]:0x0 }); - let tok = send(tok, req_s, req_start); + + let tok = send(tok, req_s, req); let (tok, resp) = recv(tok, resp_r); - assert_eq(resp, resp_ok); + assert_eq(resp, exp_resp); let tok = for ((i, output_data), tok): ((u32, FseTableRecord), token) in enumerate(output) { let req = FseRamRdReq { diff --git a/xls/modules/zstd/fse_table_creator.x b/xls/modules/zstd/fse_table_creator.x index 2537f13948..5b903d049f 100644 --- a/xls/modules/zstd/fse_table_creator.x +++ b/xls/modules/zstd/fse_table_creator.x @@ -321,13 +321,13 @@ pub proc FseTableCreator< base: new_state_base }; let complete_record_as_bits = fse_record_to_bits(complete_record); - let tok10 = send_if(tok8, fse_wr_req_s, set_state_desc, - FseRamWriteReq { + + let fse_wr_req = FseRamWriteReq { addr: checked_cast(state.idx), data: checked_cast(complete_record_as_bits), mask: FSE_RAM_REQ_MASK_ALL - } - ); + }; + let tok10 = send_if(tok8, fse_wr_req_s, set_state_desc, fse_wr_req); let (tok10, _) = recv_if(tok10, fse_wr_resp_r, set_state_desc, FseRamWriteResp {}); let send_finish = state.status == Status::SEND_FINISH; diff --git a/xls/modules/zstd/refilling_shift_buffer_mux.x b/xls/modules/zstd/refilling_shift_buffer_mux.x new file mode 100644 index 0000000000..e04d5d4b8e --- /dev/null +++ b/xls/modules/zstd/refilling_shift_buffer_mux.x @@ -0,0 +1,252 @@ +// 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 a RamMux implementation that can be used to connect +// a single proc with two RAM instances, by using a single RAM interface and +// switching between the RAMs, when requested. The switching occurs only after +// each request has received the corresponding response. +// Additionally, a "naive" implementation is provided that does not ensure +// any synchronization when switching RAMs. + +import xls.modules.zstd.refilling_shift_buffer; + +struct RefillingShiftBufferMuxState { + sel: u1, +} + +pub proc RefillingShiftBufferMux< + AXI_DATA_W: u32, SB_LENGTH_W: u32, + INIT_SEL: u1 = {u1:0}, +>{ + type State = RefillingShiftBufferMuxState; + + type SBOutput = refilling_shift_buffer::RefillingShiftBufferOutput; + type SBCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + + init { + State { sel: INIT_SEL } + } + + sel_req_r: chan in; + sel_resp_s: chan<()> out; + + ctrl0_r: chan in; + data0_s: chan out; + + ctrl1_r: chan in; + data1_s: chan out; + + ctrl_s: chan out; + data_r: chan in; + + + config ( + sel_req_r: chan in, + sel_resp_s: chan<()> out, + + ctrl0_r: chan in, + data0_s: chan out, + + ctrl1_r: chan in, + data1_s: chan out, + + ctrl_s: chan out, + data_r: chan in, + ) { + ( + sel_req_r, sel_resp_s, + ctrl0_r, data0_s, + ctrl1_r, data1_s, + ctrl_s, data_r, + ) + } + + next (state: State) { + let tok0 = join(); + + let (tok1, sel, sel_valid) = recv_non_blocking(tok0, sel_req_r, state.sel); + let tok2_0 = send(tok0, sel_resp_s, ()); + + let (tok2_0, ctrl0, ctrl0_valid) = recv_if_non_blocking(tok1, ctrl0_r, sel == u1:0, zero!()); + let (tok2_1, ctrl1, ctrl1_valid) = recv_if_non_blocking(tok1, ctrl1_r, sel == u1:1, zero!()); + let tok2 = join(tok2_0, tok2_1); + + let (ctrl, ctrl_valid) = if ctrl0_valid { + (ctrl0, true) + } else if ctrl1_valid { + (ctrl1, true) + } else { + (zero!(), false) + }; + + let tok3 = send_if(tok2, ctrl_s, ctrl_valid, ctrl); + let (tok4, data) = recv_if(tok3, data_r, ctrl_valid, zero!()); + + let do_recv_data0 = (sel == u1:0) && ctrl_valid; + send_if(tok4, data0_s, do_recv_data0, data); + + let do_recv_data1 = (sel == u1:1) && ctrl_valid; + send_if(tok4, data1_s, do_recv_data1, data); + + State { sel } + } +} + +const TEST_AXI_DATA_W = u32:64; +const TEST_SB_LENGTH_W = u32:32; + +proc RefillingShiftBufferStub< + AXI_DATA_W: u32, SB_LENGTH_W: u32 +> { + type SBOutput = refilling_shift_buffer::RefillingShiftBufferOutput; + type SBCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + + type Length = uN[SB_LENGTH_W]; + type Data = uN[AXI_DATA_W]; + + ctrl_r: chan in; + data_s: chan out; + + init { u32:0 } + + config ( + ctrl_r: chan in, + data_s: chan out, + ) { + (ctrl_r, data_s) + } + + next(cnt: u32) { + let tok = join(); + let (tok, ctrl) = recv(tok, ctrl_r); + let tok = send(tok, data_s, SBOutput { data: cnt as Data, length: ctrl.length, error: false }); + cnt + u32:1 + } +} + +#[test_proc] +proc RefillingShitBufferMuxTest +{ + type SBOutput = refilling_shift_buffer::RefillingShiftBufferOutput; + type SBCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + + type Length = uN[TEST_SB_LENGTH_W]; + type Data = uN[TEST_AXI_DATA_W]; + + terminator: chan out; + + sel_req_s: chan out; + sel_resp_r: chan<()> in; + + ctrl0_s: chan out; + data0_r: chan in; + + ctrl1_s: chan out; + data1_r: chan in; + + init {} + + config(terminator: chan out) { + let (sel_req_s, sel_req_r) = chan("sel_req"); + let (sel_resp_s, sel_resp_r) = chan<()>("sel_resp"); + + let (ctrl_s, ctrl_r) = chan("ctrl"); + let (data_s, data_r) = chan("data"); + + let (ctrl0_s, ctrl0_r) = chan("ctrl0"); + let (data0_s, data0_r) = chan("data0"); + + let (ctrl1_s, ctrl1_r) = chan("ctrl1"); + let (data1_s, data1_r) = chan("data1"); + + spawn RefillingShiftBufferMux( + sel_req_r, sel_resp_s, + ctrl0_r, data0_s, + ctrl1_r, data1_s, + ctrl_s, data_r, + ); + + spawn RefillingShiftBufferStub ( + ctrl_r, data_s, + ); + + ( + terminator, + sel_req_s, sel_resp_r, + ctrl0_s, data0_r, + ctrl1_s, data1_r, + ) + } + + next(state: ()) { + let tok = join(); + + let tok = send(tok, ctrl0_s, SBCtrl { length: Length:0xA1 }); + let tok = send(tok, ctrl0_s, SBCtrl { length: Length:0xA2 }); + let tok = send(tok, ctrl0_s, SBCtrl { length: Length:0xA3 }); + let tok = send(tok, ctrl0_s, SBCtrl { length: Length:0xA4 }); + let tok = send(tok, ctrl0_s, SBCtrl { length: Length:0xA5 }); + let tok = send(tok, ctrl0_s, SBCtrl { length: Length:0xA6 }); + let tok = send(tok, ctrl0_s, SBCtrl { length: Length:0xA7 }); + let tok = send(tok, ctrl0_s, SBCtrl { length: Length:0xA8 }); + + let (tok, data) = recv(tok, data0_r); + assert_eq(data, SBOutput { data: Data:0, length: Length:0xA1, error: false }); + let (tok, data) = recv(tok, data0_r); + assert_eq(data, SBOutput { data: Data:1, length: Length:0xA2, error: false }); + let (tok, data) = recv(tok, data0_r); + assert_eq(data, SBOutput { data: Data:2, length: Length:0xA3, error: false }); + let (tok, data) = recv(tok, data0_r); + assert_eq(data, SBOutput { data: Data:3, length: Length:0xA4, error: false }); + let (tok, data) = recv(tok, data0_r); + assert_eq(data, SBOutput { data: Data:4, length: Length:0xA5, error: false }); + let (tok, data) = recv(tok, data0_r); + assert_eq(data, SBOutput { data: Data:5, length: Length:0xA6, error: false }); + let (tok, data) = recv(tok, data0_r); + assert_eq(data, SBOutput { data: Data:6, length: Length:0xA7, error: false }); + let (tok, data) = recv(tok, data0_r); + assert_eq(data, SBOutput { data: Data:7, length: Length:0xA8, error: false }); + + let tok = send(tok, sel_req_s, u1:1); + let (tok, _) = recv(tok, sel_resp_r); + + let tok = send(tok, ctrl1_s, SBCtrl { length: Length:0xB1 }); + let tok = send(tok, ctrl1_s, SBCtrl { length: Length:0xB2 }); + let tok = send(tok, ctrl1_s, SBCtrl { length: Length:0xB3 }); + let tok = send(tok, ctrl1_s, SBCtrl { length: Length:0xB4 }); + let tok = send(tok, ctrl1_s, SBCtrl { length: Length:0xB5 }); + let tok = send(tok, ctrl1_s, SBCtrl { length: Length:0xB6 }); + let tok = send(tok, ctrl1_s, SBCtrl { length: Length:0xB7 }); + let tok = send(tok, ctrl1_s, SBCtrl { length: Length:0xB8 }); + + let (tok, data) = recv(tok, data1_r); + assert_eq(data, SBOutput { data: Data:8, length: Length:0xB1, error: false}); + let (tok, data) = recv(tok, data1_r); + assert_eq(data, SBOutput { data: Data:9, length: Length:0xB2, error: false}); + let (tok, data) = recv(tok, data1_r); + assert_eq(data, SBOutput { data: Data:10, length: Length:0xB3, error: false}); + let (tok, data) = recv(tok, data1_r); + assert_eq(data, SBOutput { data: Data:11, length: Length:0xB4, error: false}); + let (tok, data) = recv(tok, data1_r); + assert_eq(data, SBOutput { data: Data:12, length: Length:0xB5, error: false}); + let (tok, data) = recv(tok, data1_r); + assert_eq(data, SBOutput { data: Data:13, length: Length:0xB6, error: false}); + let (tok, data) = recv(tok, data1_r); + assert_eq(data, SBOutput { data: Data:14, length: Length:0xB7, error: false}); + let (tok, data) = recv(tok, data1_r); + assert_eq(data, SBOutput { data: Data:15, length: Length:0xB8, error: false}); + + send(tok, terminator, true); + } +} diff --git a/xls/modules/zstd/rle_lookup_dec.x b/xls/modules/zstd/rle_lookup_dec.x new file mode 100644 index 0000000000..b965d119c2 --- /dev/null +++ b/xls/modules/zstd/rle_lookup_dec.x @@ -0,0 +1,190 @@ +// 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; +import xls.modules.zstd.common; +import xls.modules.zstd.refilling_shift_buffer; +import xls.modules.zstd.fse_table_creator; + +pub proc RleLookupDecoder< + AXI_DATA_W: u32, + FSE_RAM_DATA_W: u32, FSE_RAM_ADDR_W: u32, FSE_RAM_NUM_PARTITIONS: u32, + SB_LENGTH_W: u32 = {refilling_shift_buffer::length_width(AXI_DATA_W)}, +> { + type Req = common::LookupDecoderReq; + type Resp = common::LookupDecoderResp; + + type Status = common::LookupDecoderStatus; + + type FseRamWrReq = ram::WriteReq; + type FseRamWrResp = ram::WriteResp; + + type SBOutput = refilling_shift_buffer::RefillingShiftBufferOutput; + type SBCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + + req_r: chan in; + resp_s: chan out; + + fse_wr_req_s: chan out; + fse_wr_resp_r: chan in; + + buffer_ctrl_s: chan out; + buffer_data_r: chan in; + + init {} + + config( + req_r: chan in, + resp_s: chan out, + + fse_wr_req_s: chan out, + fse_wr_resp_r: chan in, + + buffer_ctrl_s: chan out, + buffer_data_r: chan in, + ) { + ( + req_r, resp_s, + fse_wr_req_s, fse_wr_resp_r, + buffer_ctrl_s, buffer_data_r, + ) + } + + next(state: ()) { + let tok = join(); + // receive request + let (tok, _) = recv(tok, req_r); + // ask shift buffer for one byte + let tok = send(tok, buffer_ctrl_s, SBCtrl { + length: uN[SB_LENGTH_W]:8 + }); + // receive byte + let (tok, byte) = recv(tok, buffer_data_r); + // write byte to first location in memory + + let fse_wr_req = FseRamWrReq { + addr: uN[FSE_RAM_ADDR_W]:0, + data: fse_table_creator::fse_record_to_bits(common::FseTableRecord { + symbol: byte.data as u8, + num_of_bits: u8:0, + base: u16:0, + }), + mask: all_ones!(), + }; + trace_fmt!("RLE RAM REQUEST: {:#x}", fse_wr_req); + + let tok = send(tok, fse_wr_req_s, fse_wr_req); + // receive write response + let (tok, _) = recv(tok, fse_wr_resp_r); + // send response + let tok = send(tok, resp_s, Resp { + status: if byte.error { Status::ERROR } else { Status::OK }, + accuracy_log: common::FseAccuracyLog:0, + }); + } +} + + +const TEST_AXI_DATA_W = u32:64; +const TEST_SB_LENGTH_W = refilling_shift_buffer::length_width(TEST_AXI_DATA_W); + +const TEST_FSE_RAM_DATA_W = u32:32; +const TEST_FSE_RAM_SIZE = u32:1 << common::FSE_MAX_ACCURACY_LOG; +const TEST_FSE_RAM_ADDR_W = std::clog2(TEST_FSE_RAM_SIZE); +const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_W; +const TEST_FSE_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_FSE_RAM_WORD_PARTITION_SIZE, TEST_FSE_RAM_DATA_W); + +#[test_proc] +proc RleLookupDecoderTest { + type Req = common::LookupDecoderReq; + type Resp = common::LookupDecoderResp; + type Status = common::LookupDecoderStatus; + + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; + type FseRamWrReq = ram::WriteReq; + type FseRamWrResp = ram::WriteResp; + + type SBOutput = refilling_shift_buffer::RefillingShiftBufferOutput; + type SBCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + + terminator: chan out; + + req_s: chan out; + resp_r: chan in; + + fse_wr_req_r: chan in; + fse_wr_resp_s: chan out; + + buffer_ctrl_r: chan in; + buffer_data_s: chan out; + + init {} + + config(terminator: chan out) { + + let (req_s, req_r) = chan("req"); + let (resp_s, resp_r) = chan("resp"); + let (fse_wr_req_s, fse_wr_req_r) = chan("fse_wr_req"); + let (fse_wr_resp_s, fse_wr_resp_r) = chan("fse_wr_resp"); + let (buffer_ctrl_s, buffer_ctrl_r) = chan("buffer_ctrl"); + let (buffer_data_s, buffer_data_r) = chan("buffer_data"); + + spawn RleLookupDecoder< + TEST_AXI_DATA_W, + TEST_FSE_RAM_DATA_W, TEST_FSE_RAM_ADDR_W, TEST_FSE_RAM_NUM_PARTITIONS, + >( + req_r, resp_s, + fse_wr_req_s, fse_wr_resp_r, + buffer_ctrl_s, buffer_data_r, + ); + + ( + terminator, + req_s, resp_r, + fse_wr_req_r, fse_wr_resp_s, + buffer_ctrl_r, buffer_data_s, + ) + } + + next(_: ()) { + let tok = join(); + + let tok = send(tok, req_s, Req {}); + let (tok, buf_req) = recv(tok, buffer_ctrl_r); + assert_eq(buf_req, SBCtrl { + length: uN[TEST_SB_LENGTH_W]:8 + }); + let tok = send(tok, buffer_data_s, SBOutput { + length: uN[TEST_SB_LENGTH_W]:8, + data: uN[TEST_AXI_DATA_W]:0xC5, + error: false, + }); + let (tok, ram_req) = recv(tok, fse_wr_req_r); + assert_eq(ram_req, FseRamWrReq { + addr: uN[TEST_FSE_RAM_ADDR_W]:0, + data: u32:0xC5, + mask: uN[TEST_FSE_RAM_NUM_PARTITIONS]:0x1, + }); + let tok = send(tok, fse_wr_resp_s, FseRamWrResp {}); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: Status::OK, + accuracy_log: common::FseAccuracyLog:0, + }); + send(tok, terminator, true); + } +} diff --git a/xls/modules/zstd/sequence_dec.x b/xls/modules/zstd/sequence_dec.x index a33911ef3b..b14f088c07 100644 --- a/xls/modules/zstd/sequence_dec.x +++ b/xls/modules/zstd/sequence_dec.x @@ -35,6 +35,7 @@ type SequenceExecutorMessageType = common::SequenceExecutorMessageType; type BlockSyncData = common::BlockSyncData; type CommandConstructorData = common::CommandConstructorData; +type CompressionMode = common::CompressionMode; enum SequenceDecoderStatus: u3 { OK = 0, @@ -69,23 +70,23 @@ struct SequenceDecoderState { } struct FseLookupCtrlReq { - ll: bool, - ml: bool, - of: bool, + ll_mode: CompressionMode, + ml_mode: CompressionMode, + of_mode: CompressionMode, } type AccuracyLog = common::FseAccuracyLog; struct FseLookupCtrlResp { - ll_accuracy_log: AccuracyLog, - ml_accuracy_log: AccuracyLog, - of_accuracy_log: AccuracyLog, + ll_accuracy_log: u7, + ml_accuracy_log: u7, + of_accuracy_log: u7, } struct FseLookupCtrlState { - decode: bool[3], - decode_valid: bool, - resp: FseLookupCtrlResp, + mode: CompressionMode[3], + mode_valid: bool, cnt: u2, + accuracy_logs: u7[3], } pub proc FseLookupCtrl { @@ -125,18 +126,26 @@ pub proc FseLookupCtrl { } next(state: State) { + const PREDEFINED_ACURACY_LOG = u7[3]:[u7:6, u7:5, u7:6]; + let tok0 = join(); - if !state.decode_valid { + if !state.mode_valid { let (tok1_0, req) = recv(tok0, req_r); State { - decode: bool[3]:[req.ll, req.of, req.ml], - decode_valid: true, + mode: CompressionMode[3]:[req.ll_mode, req.of_mode, req.ml_mode], + mode_valid: true, cnt: u2:0, ..zero!() } } else { - let do_set = state.decode[state.cnt]; + let is_rle = (state.mode[state.cnt] == CompressionMode::RLE); + let is_compressed = (state.mode[state.cnt] == CompressionMode::COMPRESSED); + let is_predefined = (state.mode[state.cnt] == CompressionMode::PREDEFINED); + let is_repeated = (state.mode[state.cnt] == CompressionMode::REPEAT); + + let do_set = is_rle || is_compressed; + match(state.cnt) { u2:0 => trace_fmt!("Handling LL"), u2:1 => trace_fmt!("Handling OF"), @@ -156,7 +165,7 @@ pub proc FseLookupCtrl { } else {}; // trace_fmt!("Received response from demux"); - let tok3 = send_if(tok2, fld_req_s, do_set, FseLookupDecoderReq {}); + let tok3 = send_if(tok2, fld_req_s, do_set, FseLookupDecoderReq { is_rle }); if do_set { trace_fmt!("[SequenceDecoderCtrl/FseLookupCtrl]: Sent FseLookupDecoder req"); } else {}; @@ -166,20 +175,28 @@ pub proc FseLookupCtrl { trace_fmt!("[SequenceDecoderCtrl/FseLookupCtrl]: Received FseLookupDecoder resp {:#x}", fld_resp); } else {}; - let resp = match(state.cnt) { - u2:0 => FseLookupCtrlResp { ll_accuracy_log: fld_resp.accuracy_log, ..state.resp}, - u2:1 => FseLookupCtrlResp { of_accuracy_log: fld_resp.accuracy_log, ..state.resp}, - u2:2 => FseLookupCtrlResp { ml_accuracy_log: fld_resp.accuracy_log, ..state.resp}, - _ => fail!("impossible_cnt", zero!()), + let accuracy_log = if is_predefined { + PREDEFINED_ACURACY_LOG[state.cnt] + } else if is_repeated { + state.accuracy_logs[state.cnt] + } else if is_rle || is_compressed { + fld_resp.accuracy_log as u7 + } else { + fail!("impossible_case", u7:0) }; - // trace_fmt!("Received response from from FseLookupDecoder {:#x}", fld_resp); + let accuracy_logs = update(state.accuracy_logs, state.cnt, accuracy_log); + trace_fmt!("[SequenceDecoderCtrl/FseLookupCtrl]: accuracy_log: {:#x}, accuracy_logs: {:#x}", accuracy_log, accuracy_logs); if state.cnt >= u2:2 { - let tok5 = send(tok4, resp_s, resp); - zero!() + let tok5 = send(tok4, resp_s, FseLookupCtrlResp { + ll_accuracy_log: accuracy_logs[0], + of_accuracy_log: accuracy_logs[1], + ml_accuracy_log: accuracy_logs[2], + }); + State { accuracy_logs, ..zero!() } } else { - State { cnt: state.cnt + u2:1, resp, ..state} + State { accuracy_logs, cnt: state.cnt + u2:1, ..state} } } } @@ -496,12 +513,6 @@ pub proc SequenceDecoderCtrl< let (tok_recv_scd, conf_resp) = recv(tok_send_scd, scd_resp_r); trace_fmt!("[SequenceDecoderCtrl]: Received decoded Sequence header: {:#x}", conf_resp); - let zero_sequences = (conf_resp.header.sequence_count == u17:0); - if !zero_sequences { - assert!(conf_resp.header.literals_mode != CompressionMode::RLE, "unsupported_fse_table_mode"); - assert!(conf_resp.header.match_mode != CompressionMode::RLE, "unsupported_fse_table_mode"); - assert!(conf_resp.header.offset_mode != CompressionMode::RLE, "unsupported_fse_table_mode"); - } else {}; // Start RefillingShiftBuffer for decoding lookups let tok_dec_lookup = send(tok_recv_scd, fld_rsb_start_req_s, RefillingShiftBufferStart { @@ -510,11 +521,12 @@ pub proc SequenceDecoderCtrl< // Request decoding lookups let flc_req = FseLookupCtrlReq { - ll: (conf_resp.header.literals_mode == CompressionMode::COMPRESSED), - ml: (conf_resp.header.match_mode == CompressionMode::COMPRESSED), - of: (conf_resp.header.offset_mode == CompressionMode::COMPRESSED), + ll_mode: conf_resp.header.literals_mode, + ml_mode: conf_resp.header.match_mode, + of_mode: conf_resp.header.offset_mode, }; + let zero_sequences = (conf_resp.header.sequence_count == u17:0); let tok_send_ctrl = send_if(tok_recv_scd, flc_req_s, !zero_sequences, flc_req); if !zero_sequences { trace_fmt!("[SequenceDecoderCtrl]: Sent FseLookupCtrl request: {:#x}", flc_req); @@ -529,21 +541,24 @@ pub proc SequenceDecoderCtrl< // Set proper LL lookup through demux let ll_demux_sel = (conf_resp.header.literals_mode != CompressionMode::PREDEFINED); - let tok_ll_demux = send_if(tok_recv_scd, ll_demux_req_s, !zero_sequences, ll_demux_sel); + let ll_demux_do_send = !zero_sequences && (conf_resp.header.literals_mode != CompressionMode::REPEAT); + let tok_ll_demux = send_if(tok_recv_scd, ll_demux_req_s, ll_demux_do_send, ll_demux_sel); // Receive response from LL lookup demux - let (tok_ll_demux, _) = recv_if(tok_ll_demux, ll_demux_resp_r, !zero_sequences, ()); + let (tok_ll_demux, _) = recv_if(tok_ll_demux, ll_demux_resp_r, ll_demux_do_send, ()); // Set proper ML lookup through demux let ml_demux_sel = (conf_resp.header.match_mode != CompressionMode::PREDEFINED); - let tok_ml_demux = send_if(tok_recv_scd, ml_demux_req_s, !zero_sequences, ml_demux_sel); + let ml_demux_do_send = !zero_sequences && (conf_resp.header.match_mode != CompressionMode::REPEAT); + let tok_ml_demux = send_if(tok_recv_scd, ml_demux_req_s, ml_demux_do_send, ml_demux_sel); // Receive response from ML lookup demux - let (tok_ml_demux, _) = recv_if(tok_ml_demux, ml_demux_resp_r, !zero_sequences, ()); + let (tok_ml_demux, _) = recv_if(tok_ml_demux, ml_demux_resp_r, ml_demux_do_send, ()); // Set proper OF lookup through demux let of_demux_sel = (conf_resp.header.offset_mode != CompressionMode::PREDEFINED); - let tok_of_demux = send_if(tok_recv_scd, of_demux_req_s, !zero_sequences, of_demux_sel); + let of_demux_do_send = !zero_sequences && (conf_resp.header.offset_mode != CompressionMode::REPEAT); + let tok_of_demux = send_if(tok_recv_scd, of_demux_req_s, of_demux_do_send, of_demux_sel); // Receive response from OF lookup demux - let (tok_of_demux, _) = recv_if(tok_of_demux, of_demux_resp_r, !zero_sequences, ()); + let (tok_of_demux, _) = recv_if(tok_of_demux, of_demux_resp_r, of_demux_do_send, ()); let tok_demux = join(tok_ll_demux, tok_ml_demux, tok_of_demux); @@ -557,9 +572,9 @@ pub proc SequenceDecoderCtrl< sync: req.sync, sequences_count: conf_resp.header.sequence_count as u24, literals_count: req.literals_count, - ll_acc_log: if (conf_resp.header.literals_mode == CompressionMode::PREDEFINED) { u7:6 } else { flc_resp.ll_accuracy_log as u7 }, - of_acc_log: if (conf_resp.header.offset_mode == CompressionMode::PREDEFINED) { u7:5 } else { flc_resp.of_accuracy_log as u7 }, - ml_acc_log: if (conf_resp.header.match_mode == CompressionMode::PREDEFINED) { u7:6 } else { flc_resp.ml_accuracy_log as u7 }, + ll_acc_log: flc_resp.ll_accuracy_log as u7, + of_acc_log: flc_resp.of_accuracy_log as u7, + ml_acc_log: flc_resp.ml_accuracy_log as u7, }; let tok_fse_dec = send(tok_demux, fd_ctrl_s, fd_ctrl); @@ -1194,10 +1209,10 @@ const TEST_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_TMP2_RAM_WORD_PART // - sequences section as it appears in memory // - expected output size // - expected output -const SEQ_DEC_TESTCASES: (u32, u64[32], u32, SequenceExecutorPacket[64])[3] = [ - // Test case 0 - // raw literals with sequences with 3 predefined tables - // ./decodecorpus -pdata2.out -odata2.in -s35304 --block-type=2 --content-size --literal-type=0 --max-block-size-log=7 +const SEQ_DEC_TESTCASES: (u32, u64[32], u32, SequenceExecutorPacket[64])[4] = [ +// // Test case 0 +// // raw literals with sequences with 3 predefined tables +// // ./decodecorpus -pdata2.out -odata2.in -s35304 --block-type=2 --content-size --literal-type=0 --max-block-size-log=7 ( u32:17, u64[32]:[ @@ -1594,7 +1609,131 @@ const SEQ_DEC_TESTCASES: (u32, u64[32], u32, SequenceExecutorPacket[64])[3] = [ zero!(), ... ] ), - // Test case 3 (WARNING: long test running time) + // Test case 3 + // LL - compressed, OF - compressed, ML - RLE + ( + u32:0x17, + u64[32]:[ + u64:0x0, u64:0x0, + u64:0x39fb5432a90a409, + u64:0x6b2940007b74a10, + u64:0xaca57e409b057, + u64:0x0, ... + ], + u32:18, + SequenceExecutorPacket[64]:[ + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0002, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x0004, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0000, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x0007, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0001, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x0009, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0005, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x0009, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0000, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x000b, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0011, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x0012, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0004, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x0023, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0002, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x000a, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: u64:0x0002, + content: u64:0x0, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: u64:0x0003, + content: u64:0x0034, + last: true, + }, + zero!(), ... + ] + ), + // Test case N (WARNING: long test running time) // 3 custom lookup tables with accuracy log 9, 8 and 9 // decodecorpus -pdata.out -odata.in -s58745 --block-type=2 --content-size --literal-type=0 --max-block-size-log=7 // ( From 4d863525a55ac58cfe39762f414ed2422e22587f Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 22 Jan 2025 21:59:26 +0100 Subject: [PATCH 59/85] Add test frame for repeated and rle tables Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 1 + xls/modules/zstd/comp_frame_fse_repeated.x | 21 +++++++++++++++++++++ xls/modules/zstd/zstd_dec_test.x | 1 + 3 files changed, 23 insertions(+) create mode 100644 xls/modules/zstd/comp_frame_fse_repeated.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 4fc18e5743..37307f09de 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1280,6 +1280,7 @@ xls_dslx_test( "comp_frame.x", "comp_frame_huffman.x", "comp_frame_fse_comp.x", + "comp_frame_fse_repeated.x", ], tags = ["manual"], deps = zstd_dec_deps, diff --git a/xls/modules/zstd/comp_frame_fse_repeated.x b/xls/modules/zstd/comp_frame_fse_repeated.x new file mode 100644 index 0000000000..37de3b7ddf --- /dev/null +++ b/xls/modules/zstd/comp_frame_fse_repeated.x @@ -0,0 +1,21 @@ +pub struct DataArray{ + data: uN[BITS_PER_WORD][LENGTH], + length: u32, + array_length: u32 +} +pub const FRAMES:DataArray< + u32:64, + u32:12 +>[1] = [DataArray<64, 12>{ + length: u32:92, + array_length: u32:12, + data: uN[64][12]:[uN[64]:0x003e2484fd2fb528, uN[64]:0x02d6790000840000, uN[64]:0xff86117f06110168, uN[64]:0x0000440452dd7eff, uN[64]:0x04b6010674016531, uN[64]:0x001c00b32100001c, uN[64]:0x0100001c00e70100, uN[64]:0x00bd0100001c000d, uN[64]:0x001c003a0100001c, uN[64]:0x0100001c007f0100, uN[64]:0x00690100001d006b, uN[64]:0x993d99b6] +}]; +pub const DECOMPRESSED_FRAMES:DataArray< + u32:64, + u32:8 +>[1] = [DataArray<64, 8>{ + length: u32:62, + array_length: u32:8, + data: uN[64][8]:[uN[64]:0xd6d6d6d6d6d6d6d6, uN[64]:0xd6d6d6d6d6d6d6d6, uN[64]:0xd6d6d6d6d6d6d6d6, uN[64]:0xd6d6d6d6d6d6d6d6, uN[64]:0xd6d6d6d6d6d6d6d6, uN[64]:0xd6d6d6d6d6d6d6d6, uN[64]:0xd6d6656565656565, uN[64]:0xb3b3b3b3d6d6] +}]; diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x index 92aac6a0f6..52306111ce 100644 --- a/xls/modules/zstd/zstd_dec_test.x +++ b/xls/modules/zstd/zstd_dec_test.x @@ -31,6 +31,7 @@ import xls.modules.zstd.ram_mux; // import xls.modules.zstd.zstd_frame_testcases as comp_frame; // import xls.modules.zstd.comp_frame_huffman as comp_frame; // import xls.modules.zstd.comp_frame_fse_comp as comp_frame; +// import xls.modules.zstd.comp_frame_fse_repeated as comp_frame; import xls.modules.zstd.comp_frame; const TEST_WINDOW_LOG_MAX = u32:30; From 9a733de90c529aa14e121eb6d8ff033ccaf830fa Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Wed, 22 Jan 2025 17:11:42 +0100 Subject: [PATCH 60/85] modules/zstd: Add RamMerge proc Internal-tag: [#71874] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 7 ++++ xls/modules/zstd/ram_merge.x | 78 ++++++++++++++++++++++++++++++++++++ xls/modules/zstd/zstd_dec.x | 45 +++++++++++++++++---- 3 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 xls/modules/zstd/ram_merge.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 37307f09de..b3b3f968b7 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1242,6 +1242,12 @@ xls_dslx_test( tags = ["manual"], ) +xls_dslx_library( + name = "ram_merge_dslx", + srcs = ["ram_merge.x"], + deps = ["//xls/examples:ram_dslx"], +) + zstd_dec_deps = [ ":axi_csr_accessor_dslx", ":block_header_dec_dslx", @@ -1257,6 +1263,7 @@ zstd_dec_deps = [ ":huffman_literals_dec_dslx", ":literals_buffer_dslx", ":parallel_rams_dslx", + ":ram_merge_dslx", "//xls/examples:ram_dslx", "//xls/modules/zstd/memory:mem_reader_dslx", "//xls/modules/zstd/memory:mem_writer_dslx", diff --git a/xls/modules/zstd/ram_merge.x b/xls/modules/zstd/ram_merge.x new file mode 100644 index 0000000000..c4e306dfa7 --- /dev/null +++ b/xls/modules/zstd/ram_merge.x @@ -0,0 +1,78 @@ +// Copyright 2025 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.examples.ram; + +pub proc RamMerge< + RAM_ADDR_W: u32, + RAM_DATA_W: u32, + RAM_NUM_PARTITIONS: u32 +> { + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp; + + init {} + + read_side_rd_req_r: chan in; + read_side_rd_resp_s: chan out; + + write_side_wr_req_r: chan in; + write_side_wr_resp_s: chan out; + + merge_side_rd_req_s: chan out; + merge_side_rd_resp_r: chan in; + merge_side_wr_req_s: chan out; + merge_side_wr_resp_r: chan in; + + config( + read_side_rd_req_r: chan in, + read_side_rd_resp_s: chan out, + + write_side_wr_req_r: chan in, + write_side_wr_resp_s: chan out, + + merge_side_rd_req_s: chan out, + merge_side_rd_resp_r: chan in, + merge_side_wr_req_s: chan out, + merge_side_wr_resp_r: chan in, + ) { + ( + read_side_rd_req_r, read_side_rd_resp_s, + + write_side_wr_req_r, write_side_wr_resp_s, + + merge_side_rd_req_s, merge_side_rd_resp_r, + merge_side_wr_req_s, merge_side_wr_resp_r + ) + } + + next (state: ()) { + let tok = join(); + + // Passthrough Requests + let (tok_rd, rd_req, rd_req_valid) = recv_non_blocking(tok, read_side_rd_req_r, zero!()); + let (tok_rd, rd_resp, rd_resp_valid) = recv_non_blocking(tok_rd, merge_side_rd_resp_r, zero!()); + let tok_rd = send_if(tok_rd, merge_side_rd_req_s, rd_req_valid, rd_req); + let tok_rd = send_if(tok_rd, read_side_rd_resp_s, rd_resp_valid, rd_resp); + + let (tok_wr, wr_req, wr_req_valid) = recv_non_blocking(tok, write_side_wr_req_r, zero!()); + let (tok_wr, wr_resp, wr_resp_valid) = recv_non_blocking(tok_wr, merge_side_wr_resp_r, zero!()); + let tok_wr = send_if(tok_wr, merge_side_wr_req_s, wr_req_valid, wr_req); + let tok_wr = send_if(tok_wr, write_side_wr_resp_s, wr_resp_valid, wr_resp); + + let tok_joined = join(tok_rd, tok_wr); + } +} diff --git a/xls/modules/zstd/zstd_dec.x b/xls/modules/zstd/zstd_dec.x index 31ab2d643f..77ddea55f6 100644 --- a/xls/modules/zstd/zstd_dec.x +++ b/xls/modules/zstd/zstd_dec.x @@ -35,6 +35,7 @@ import xls.modules.zstd.sequence_executor; import xls.modules.zstd.huffman_literals_dec; import xls.modules.zstd.literals_buffer; import xls.modules.zstd.parallel_rams; +import xls.modules.zstd.ram_merge; type BlockSize = common::BlockSize; type BlockType = common::BlockType; @@ -1286,6 +1287,11 @@ pub proc ZstdDecoder< let (cmd_output_s, cmd_output_r) = chan("cmd_output"); + let (huffman_lit_weights_read_side_rd_req_s, huffman_lit_weights_read_side_rd_req_r) = chan("huffman_lit_weights_read_side_rd_req"); + let (huffman_lit_weights_read_side_rd_resp_s, huffman_lit_weights_read_side_rd_resp_r) = chan("huffman_lit_weights_read_side_rd_resp"); + let (huffman_lit_weights_write_side_wr_req_s, huffman_lit_weights_write_side_wr_req_r) = chan("huffman_lit_weights_write_side_wr_req"); + let (huffman_lit_weights_write_side_wr_resp_s, huffman_lit_weights_write_side_wr_resp_r) = chan("huffman_lit_weights_write_side_wr_resp"); + spawn comp_block_dec::CompressBlockDecoder< AXI_DATA_W, AXI_ADDR_W, AXI_ID_W, AXI_DEST_W, // FSE lookup table RAMs @@ -1333,12 +1339,22 @@ pub proc ZstdDecoder< litbuf_wr_req_s[4], litbuf_wr_req_s[5], litbuf_wr_req_s[6], litbuf_wr_req_s[7], litbuf_wr_resp_r[0], litbuf_wr_resp_r[1], litbuf_wr_resp_r[2], litbuf_wr_resp_r[3], litbuf_wr_resp_r[4], litbuf_wr_resp_r[5], litbuf_wr_resp_r[6], litbuf_wr_resp_r[7], - huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_resp_r, - huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_resp_r, + huffman_lit_weights_read_side_rd_req_s, huffman_lit_weights_read_side_rd_resp_r, + huffman_lit_weights_write_side_wr_req_s, huffman_lit_weights_write_side_wr_resp_r, huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_resp_r, huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_resp_r, ); + spawn ram_merge::RamMerge( + // Read side + huffman_lit_weights_read_side_rd_req_r, huffman_lit_weights_read_side_rd_resp_s, + // Write side + huffman_lit_weights_write_side_wr_req_r, huffman_lit_weights_write_side_wr_resp_s, + // Merge side + huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_resp_r, + huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_resp_r, + ); + // Collecting Packets let (seq_exec_input_s, seq_exec_input_r) = chan("demux_output"); @@ -1465,6 +1481,8 @@ const INST_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions( const HUFFMAN_WEIGHTS_RAM_ADDR_W: u32 = huffman_literals_dec::WEIGHTS_ADDR_WIDTH; const HUFFMAN_WEIGHTS_RAM_DATA_W: u32 = huffman_literals_dec::WEIGHTS_DATA_WIDTH; const HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::WEIGHTS_NUM_PARTITIONS; +const HUFFMAN_WEIGHTS_RAM_SIZE: u32 = huffman_literals_dec::RAM_SIZE; +const HUFFMAN_WEIGHTS_RAM_PARTITION_WORD_SIZE: u32 = huffman_literals_dec::WEIGHTS_PARTITION_WORD_SIZE; // Huffman prescan memory parameters const HUFFMAN_PRESCAN_RAM_ADDR_W: u32 = huffman_literals_dec::PRESCAN_ADDR_WIDTH; const HUFFMAN_PRESCAN_RAM_DATA_W: u32 = huffman_literals_dec::PRESCAN_DATA_WIDTH; @@ -1480,6 +1498,8 @@ const LITERALS_BUFFER_RAM_DATA_W: u32 = literals_buffer::RAM_DATA_WIDTH; const LITERALS_BUFFER_RAM_NUM_PARTITIONS: u32 = literals_buffer::RAM_NUM_PARTITIONS; const LITERALS_BUFFER_RAM_WORD_PARTITION_SIZE: u32 = LITERALS_BUFFER_RAM_DATA_W; +const INST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const INST_RAM_INITIALIZED = true; proc ZstdDecoderInternalInst { type State = ZstdDecoderInternalState; @@ -1617,6 +1637,8 @@ proc ZstdDecoderInst { type HuffmanPrescanWriteReq = ram::WriteReq; type HuffmanPrescanWriteResp = ram::WriteResp; + const CHANNEL_DEPTH = u32:1; + init { } config( @@ -1667,12 +1689,6 @@ proc ZstdDecoderInst { litbuf_wr_req_s: chan[u32:8] out, litbuf_wr_resp_r: chan[u32:8] in, - // Huffman weights memory - huffman_lit_weights_mem_rd_req_s: chan out, - huffman_lit_weights_mem_rd_resp_r: chan in, - huffman_lit_weights_mem_wr_req_s: chan out, - huffman_lit_weights_mem_wr_resp_r: chan in, - // Huffman prescan memory huffman_lit_prescan_mem_rd_req_s: chan out, huffman_lit_prescan_mem_rd_resp_r: chan in, @@ -1721,6 +1737,19 @@ proc ZstdDecoderInst { notify_s: chan<()> out, reset_s: chan<()> out, ) { + // FIXME: Remove inline Huffman Weights memory once HuffmanLiteralsDecoder's memory ports are able to be rewritten + let (huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_req_r) = chan("huffman_lit_weights_mem_rd_req"); + let (huffman_lit_weights_mem_rd_resp_s, huffman_lit_weights_mem_rd_resp_r) = chan("huffman_lit_weights_mem_rd_resp"); + let (huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_req_r) = chan("huffman_lit_weights_mem_wr_req"); + let (huffman_lit_weights_mem_wr_resp_s, huffman_lit_weights_mem_wr_resp_r) = chan("huffman_lit_weights_mem_wr_resp"); + spawn ram::RamModel< + HUFFMAN_WEIGHTS_RAM_DATA_W, HUFFMAN_WEIGHTS_RAM_SIZE, HUFFMAN_WEIGHTS_RAM_PARTITION_WORD_SIZE, + INST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, INST_RAM_INITIALIZED + >( + huffman_lit_weights_mem_rd_req_r, huffman_lit_weights_mem_rd_resp_s, + huffman_lit_weights_mem_wr_req_r, huffman_lit_weights_mem_wr_resp_s, + ); + spawn ZstdDecoder< INST_AXI_DATA_W, INST_AXI_ADDR_W, INST_AXI_ID_W, INST_AXI_DEST_W, INST_REGS_N, INST_WINDOW_LOG_MAX, From a7f5b4e47c7645acc484667e1787e44f7ab36f77 Mon Sep 17 00:00:00 2001 From: Maciej Torhan Date: Fri, 17 Jan 2025 13:33:37 +0100 Subject: [PATCH 61/85] modules/zstd: Implement HuffmanFseWeightsDecoder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Krzysztof Obłonczek Signed-off-by: Maciej Torhan --- xls/modules/zstd/BUILD | 4 + xls/modules/zstd/common.x | 2 +- xls/modules/zstd/huffman_literals_dec.x | 281 +++- xls/modules/zstd/huffman_weights_dec.x | 1431 +++++++++++++++++++-- xls/modules/zstd/refilling_shift_buffer.x | 36 +- 5 files changed, 1621 insertions(+), 133 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index b3b3f968b7..77e7024911 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -3051,6 +3051,10 @@ xls_dslx_library( ], deps = [ "//xls/modules/zstd:huffman_prescan_dslx", + "//xls/modules/zstd:refilling_shift_buffer_dslx", + "//xls/modules/zstd:fse_lookup_dec_dslx", + "//xls/modules/zstd:fse_table_creator_dslx", + "//xls/modules/zstd:math_dslx", "//xls/modules/zstd/memory:mem_reader_dslx", "//xls/modules/zstd/memory:axi_ram_dslx", "//xls/examples:ram_dslx", diff --git a/xls/modules/zstd/common.x b/xls/modules/zstd/common.x index 8c1a48b143..377a5aa2c4 100644 --- a/xls/modules/zstd/common.x +++ b/xls/modules/zstd/common.x @@ -176,7 +176,7 @@ pub struct FseTableCreatorCtrl { negative_proba_count: FseSymbolCount } -pub fn highest_set_bit(num: uN[N]) -> u16 { std::flog2(num) } +pub fn highest_set_bit(num: uN[N]) -> uN[N] { std::flog2(num) } // SequenceDecoder diff --git a/xls/modules/zstd/huffman_literals_dec.x b/xls/modules/zstd/huffman_literals_dec.x index 55589b9992..b3b650f2ae 100644 --- a/xls/modules/zstd/huffman_literals_dec.x +++ b/xls/modules/zstd/huffman_literals_dec.x @@ -49,6 +49,9 @@ pub const PRESCAN_NUM_PARTITIONS: u32 = u32:1; pub proc HuffmanLiteralsDecoder< AXI_DATA_W: u32, AXI_ADDR_W: u32, AXI_ID_W: u32, AXI_DEST_W: u32, + WEIGHTS_DPD_RAM_ADDR_W: u32, WEIGHTS_DPD_RAM_DATA_W: u32, WEIGHTS_DPD_RAM_NUM_PARTITIONS: u32, + WEIGHTS_TMP_RAM_ADDR_W: u32, WEIGHTS_TMP_RAM_DATA_W: u32, WEIGHTS_TMP_RAM_NUM_PARTITIONS: u32, + WEIGHTS_FSE_RAM_ADDR_W: u32, WEIGHTS_FSE_RAM_DATA_W: u32, WEIGHTS_FSE_RAM_NUM_PARTITIONS: u32, WEIGHTS_RAM_ADDR_WIDTH: u32 = {WEIGHTS_ADDR_WIDTH}, WEIGHTS_RAM_DATA_WIDTH: u32 = {WEIGHTS_DATA_WIDTH}, WEIGHTS_RAM_NUM_PARTITIONS: u32 = {WEIGHTS_NUM_PARTITIONS}, @@ -71,6 +74,22 @@ pub proc HuffmanLiteralsDecoder< type PrescanRamWrReq = ram::WriteReq; type PrescanRamWrResp = ram::WriteResp; + // Weights FSE RAMs + type WeightsDpdRamRdReq = ram::ReadReq; + type WeightsDpdRamRdResp = ram::ReadResp; + type WeightsDpdRamWrReq = ram::WriteReq; + type WeightsDpdRamWrResp = ram::WriteResp; + + type WeightsTmpRamRdReq = ram::ReadReq; + type WeightsTmpRamRdResp = ram::ReadResp; + type WeightsTmpRamWrReq = ram::WriteReq; + type WeightsTmpRamWrResp = ram::WriteResp; + + type WeightsFseRamRdReq = ram::ReadReq; + type WeightsFseRamRdResp = ram::ReadResp; + type WeightsFseRamWrReq = ram::WriteReq; + type WeightsFseRamWrResp = ram::WriteResp; + type WeightsDecReq = weights_dec::HuffmanWeightsDecoderReq; type WeightsDecResp = weights_dec::HuffmanWeightsDecoderResp; @@ -98,8 +117,10 @@ pub proc HuffmanLiteralsDecoder< weights_raw_dec_axi_ar_s: chan out, weights_raw_dec_axi_r_r: chan in, // AXI interface - FSE Huffman tree description decoder - weights_fse_dec_axi_ar_s: chan out, - weights_fse_dec_axi_r_r: chan in, + weights_fse_lookup_dec_axi_ar_s: chan out, + weights_fse_lookup_dec_axi_r_r: chan in, + weights_fse_decoder_dec_axi_ar_s: chan out, + weights_fse_decoder_dec_axi_r_r: chan in, // weight memory weights_ram_rd_req_s: chan out, weights_ram_rd_resp_r: chan in, @@ -110,6 +131,19 @@ pub proc HuffmanLiteralsDecoder< prescan_ram_rd_resp_r: chan in, prescan_ram_wr_req_s: chan out, prescan_ram_wr_resp_r: chan in, + // Weights FSE RAMs + weights_dpd_rd_req_s: chan out, + weights_dpd_rd_resp_r: chan in, + weights_dpd_wr_req_s: chan out, + weights_dpd_wr_resp_r: chan in, + weights_tmp_rd_req_s: chan out, + weights_tmp_rd_resp_r: chan in, + weights_tmp_wr_req_s: chan out, + weights_tmp_wr_resp_r: chan in, + weights_fse_rd_req_s: chan out, + weights_fse_rd_resp_r: chan in, + weights_fse_wr_req_s: chan out, + weights_fse_wr_resp_r: chan in, ) { let (prescan_start_s, prescan_start_r) = chan("prescan_start"); let (code_builder_start_s, code_builder_start_r) = chan("code_buider"); @@ -130,8 +164,10 @@ pub proc HuffmanLiteralsDecoder< let (weights_header_dec_mem_rd_resp_s, weights_header_dec_mem_rd_resp_r) = chan("weights_dec_mem_rd_resp"); let (weights_raw_dec_mem_rd_req_s, weights_raw_dec_mem_rd_req_r) = chan("weights_dec_mem_rd_req"); let (weights_raw_dec_mem_rd_resp_s, weights_raw_dec_mem_rd_resp_r) = chan("weights_dec_mem_rd_resp"); - let (weights_fse_dec_mem_rd_req_s, weights_fse_dec_mem_rd_req_r) = chan("weights_dec_mem_rd_req"); - let (weights_fse_dec_mem_rd_resp_s, weights_fse_dec_mem_rd_resp_r) = chan("weights_dec_mem_rd_resp"); + let (weights_fse_lookup_dec_mem_rd_req_s, weights_fse_lookup_dec_mem_rd_req_r) = chan("weights_lookup_dec_mem_rd_req"); + let (weights_fse_lookup_dec_mem_rd_resp_s, weights_fse_lookup_dec_mem_rd_resp_r) = chan("weights_lookup_dec_mem_rd_resp"); + let (weights_fse_decoder_dec_mem_rd_req_s, weights_fse_decoder_dec_mem_rd_req_r) = chan("weights_decoder_dec_mem_rd_req"); + let (weights_fse_decoder_dec_mem_rd_resp_s, weights_fse_decoder_dec_mem_rd_resp_r) = chan("weights_decoder_dec_mem_rd_resp"); // code builder loopback let (weights_pow_sum_loopback_s, weights_pow_sum_loopback_r) = chan("weights_pow_sum_loopback"); @@ -165,21 +201,31 @@ pub proc HuffmanLiteralsDecoder< ); spawn mem_reader::MemReader( - weights_fse_dec_mem_rd_req_r, weights_fse_dec_mem_rd_resp_s, - weights_fse_dec_axi_ar_s, weights_fse_dec_axi_r_r + weights_fse_lookup_dec_mem_rd_req_r, weights_fse_lookup_dec_mem_rd_resp_s, + weights_fse_lookup_dec_axi_ar_s, weights_fse_lookup_dec_axi_r_r + ); + + spawn mem_reader::MemReader( + weights_fse_decoder_dec_mem_rd_req_r, weights_fse_decoder_dec_mem_rd_resp_s, + weights_fse_decoder_dec_axi_ar_s, weights_fse_decoder_dec_axi_r_r ); spawn weights_dec::HuffmanWeightsDecoder< - AXI_ADDR_W, AXI_DATA_W, - WEIGHTS_RAM_ADDR_WIDTH, - WEIGHTS_RAM_DATA_WIDTH, - WEIGHTS_RAM_NUM_PARTITIONS, + AXI_ADDR_W, AXI_DATA_W, AXI_ID_W, + WEIGHTS_RAM_ADDR_WIDTH, WEIGHTS_RAM_DATA_WIDTH, WEIGHTS_RAM_NUM_PARTITIONS, + WEIGHTS_DPD_RAM_ADDR_W, WEIGHTS_DPD_RAM_DATA_W, WEIGHTS_DPD_RAM_NUM_PARTITIONS, + WEIGHTS_TMP_RAM_ADDR_W, WEIGHTS_TMP_RAM_DATA_W, WEIGHTS_TMP_RAM_NUM_PARTITIONS, + WEIGHTS_FSE_RAM_ADDR_W, WEIGHTS_FSE_RAM_DATA_W, WEIGHTS_FSE_RAM_NUM_PARTITIONS, >( weights_dec_req_r, weights_dec_resp_s, weights_header_dec_mem_rd_req_s, weights_header_dec_mem_rd_resp_r, weights_raw_dec_mem_rd_req_s, weights_raw_dec_mem_rd_resp_r, - weights_fse_dec_mem_rd_req_s, weights_fse_dec_mem_rd_resp_r, + weights_fse_lookup_dec_mem_rd_req_s, weights_fse_lookup_dec_mem_rd_resp_r, + weights_fse_decoder_dec_mem_rd_req_s, weights_fse_decoder_dec_mem_rd_resp_r, weights_ram_wr_req_s, weights_ram_wr_resp_r, + weights_dpd_rd_req_s, weights_dpd_rd_resp_r, weights_dpd_wr_req_s, weights_dpd_wr_resp_r, + weights_tmp_rd_req_s, weights_tmp_rd_resp_r, weights_tmp_wr_req_s, weights_tmp_wr_resp_r, + weights_fse_rd_req_s, weights_fse_rd_resp_r, weights_fse_wr_req_s, weights_fse_wr_resp_r, ); spawn prescan::WeightPreScan( @@ -244,6 +290,30 @@ pub const INST_PRESCAN_RAM_ADDR_WIDTH = PRESCAN_ADDR_WIDTH; pub const INST_PRESCAN_RAM_DATA_WIDTH = PRESCAN_DATA_WIDTH; pub const INST_PRESCAN_RAM_NUM_PARTITIONS = PRESCAN_NUM_PARTITIONS; +const INST_WEIGHTS_DPD_RAM_DATA_W = u32:16; +const INST_WEIGHTS_DPD_RAM_SIZE = u32:256; +const INST_WEIGHTS_DPD_RAM_ADDR_W = std::clog2(INST_WEIGHTS_DPD_RAM_SIZE); +const INST_WEIGHTS_DPD_RAM_WORD_PARTITION_SIZE = INST_WEIGHTS_DPD_RAM_DATA_W; +const INST_WEIGHTS_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( + INST_WEIGHTS_DPD_RAM_WORD_PARTITION_SIZE, INST_WEIGHTS_DPD_RAM_DATA_W +); + +const INST_WEIGHTS_FSE_RAM_DATA_W = u32:32; +const INST_WEIGHTS_FSE_RAM_SIZE = u32:256; +const INST_WEIGHTS_FSE_RAM_ADDR_W = std::clog2(INST_WEIGHTS_FSE_RAM_SIZE); +const INST_WEIGHTS_FSE_RAM_WORD_PARTITION_SIZE = INST_WEIGHTS_FSE_RAM_DATA_W / u32:3; +const INST_WEIGHTS_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( + INST_WEIGHTS_FSE_RAM_WORD_PARTITION_SIZE, INST_WEIGHTS_FSE_RAM_DATA_W +); + +const INST_WEIGHTS_TMP_RAM_DATA_W = u32:16; +const INST_WEIGHTS_TMP_RAM_SIZE = u32:256; +const INST_WEIGHTS_TMP_RAM_ADDR_W = std::clog2(INST_WEIGHTS_TMP_RAM_SIZE); +const INST_WEIGHTS_TMP_RAM_WORD_PARTITION_SIZE = INST_WEIGHTS_TMP_RAM_DATA_W; +const INST_WEIGHTS_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( + INST_WEIGHTS_TMP_RAM_WORD_PARTITION_SIZE, INST_WEIGHTS_TMP_RAM_DATA_W +); + proc HuffmanLiteralsDecoderInst { type Ctrl = HuffmanLiteralsDecoderReq; type Resp = HuffmanLiteralsDecoderResp; @@ -259,6 +329,21 @@ proc HuffmanLiteralsDecoderInst { type PrescanRamWrReq = ram::WriteReq; type PrescanRamWrResp = ram::WriteResp; + type WeightsDpdRamRdReq = ram::ReadReq; + type WeightsDpdRamRdResp = ram::ReadResp; + type WeightsDpdRamWrReq = ram::WriteReq; + type WeightsDpdRamWrResp = ram::WriteResp; + + type WeightsTmpRamRdReq = ram::ReadReq; + type WeightsTmpRamRdResp = ram::ReadResp; + type WeightsTmpRamWrReq = ram::WriteReq; + type WeightsTmpRamWrResp = ram::WriteResp; + + type WeightsFseRamRdReq = ram::ReadReq; + type WeightsFseRamRdResp = ram::ReadResp; + type WeightsFseRamWrReq = ram::WriteReq; + type WeightsFseRamWrResp = ram::WriteResp; + config ( ctrl_r: chan in, resp_s: chan out, @@ -271,8 +356,10 @@ proc HuffmanLiteralsDecoderInst { weights_header_dec_axi_r_r: chan in, weights_raw_dec_axi_ar_s: chan out, weights_raw_dec_axi_r_r: chan in, - weights_fse_dec_axi_ar_s: chan out, - weights_fse_dec_axi_r_r: chan in, + weights_fse_lookup_dec_axi_ar_s: chan out, + weights_fse_lookup_dec_axi_r_r: chan in, + weights_fse_decoder_dec_axi_ar_s: chan out, + weights_fse_decoder_dec_axi_r_r: chan in, weights_ram_rd_req_s: chan out, weights_ram_rd_resp_r: chan in, weights_ram_wr_req_s: chan out, @@ -281,9 +368,24 @@ proc HuffmanLiteralsDecoderInst { prescan_ram_rd_resp_r: chan in, prescan_ram_wr_req_s: chan out, prescan_ram_wr_resp_r: chan in, + weights_dpd_rd_req_s: chan out, + weights_dpd_rd_resp_r: chan in, + weights_dpd_wr_req_s: chan out, + weights_dpd_wr_resp_r: chan in, + weights_tmp_rd_req_s: chan out, + weights_tmp_rd_resp_r: chan in, + weights_tmp_wr_req_s: chan out, + weights_tmp_wr_resp_r: chan in, + weights_fse_rd_req_s: chan out, + weights_fse_rd_resp_r: chan in, + weights_fse_wr_req_s: chan out, + weights_fse_wr_resp_r: chan in, ) { spawn HuffmanLiteralsDecoder< INST_AXI_DATA_W, INST_AXI_ADDR_W, INST_AXI_ID_W, INST_AXI_DEST_W, + INST_WEIGHTS_DPD_RAM_ADDR_W, INST_WEIGHTS_DPD_RAM_DATA_W, INST_WEIGHTS_DPD_RAM_NUM_PARTITIONS, + INST_WEIGHTS_TMP_RAM_ADDR_W, INST_WEIGHTS_TMP_RAM_DATA_W, INST_WEIGHTS_TMP_RAM_NUM_PARTITIONS, + INST_WEIGHTS_FSE_RAM_ADDR_W, INST_WEIGHTS_FSE_RAM_DATA_W, INST_WEIGHTS_FSE_RAM_NUM_PARTITIONS, INST_WEIGHTS_RAM_ADDR_WIDTH, INST_WEIGHTS_RAM_DATA_WIDTH, INST_WEIGHTS_RAM_NUM_PARTITIONS, INST_PRESCAN_RAM_ADDR_WIDTH, INST_PRESCAN_RAM_DATA_WIDTH, INST_PRESCAN_RAM_NUM_PARTITIONS >( @@ -293,11 +395,18 @@ proc HuffmanLiteralsDecoderInst { jump_table_axi_ar_s, jump_table_axi_r_r, weights_header_dec_axi_ar_s, weights_header_dec_axi_r_r, weights_raw_dec_axi_ar_s, weights_raw_dec_axi_r_r, - weights_fse_dec_axi_ar_s, weights_fse_dec_axi_r_r, + weights_fse_lookup_dec_axi_ar_s, weights_fse_lookup_dec_axi_r_r, + weights_fse_decoder_dec_axi_ar_s, weights_fse_decoder_dec_axi_r_r, weights_ram_rd_req_s, weights_ram_rd_resp_r, weights_ram_wr_req_s, weights_ram_wr_resp_r, prescan_ram_rd_req_s, prescan_ram_rd_resp_r, prescan_ram_wr_req_s, prescan_ram_wr_resp_r, + weights_dpd_rd_req_s, weights_dpd_rd_resp_r, + weights_dpd_wr_req_s, weights_dpd_wr_resp_r, + weights_tmp_rd_req_s, weights_tmp_rd_resp_r, + weights_tmp_wr_req_s, weights_tmp_wr_resp_r, + weights_fse_rd_req_s, weights_fse_rd_resp_r, + weights_fse_wr_req_s, weights_fse_wr_resp_r, ); } @@ -337,6 +446,30 @@ pub const TEST_PRESCAN_RAM_NUM_PARTITIONS = PRESCAN_NUM_PARTITIONS; pub const TEST_PRESCAN_RAM_SIZE = prescan::RAM_SIZE; pub const TEST_PRESCAN_WORD_PARTITION_SIZE = prescan::WeightPreScanMetaDataSize(); +const TEST_WEIGHTS_DPD_RAM_DATA_W = u32:16; +const TEST_WEIGHTS_DPD_RAM_SIZE = u32:256; +const TEST_WEIGHTS_DPD_RAM_ADDR_W = std::clog2(TEST_WEIGHTS_DPD_RAM_SIZE); +const TEST_WEIGHTS_DPD_RAM_WORD_PARTITION_SIZE = TEST_WEIGHTS_DPD_RAM_DATA_W; +const TEST_WEIGHTS_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_WEIGHTS_DPD_RAM_WORD_PARTITION_SIZE, TEST_WEIGHTS_DPD_RAM_DATA_W +); + +const TEST_WEIGHTS_FSE_RAM_DATA_W = u32:32; +const TEST_WEIGHTS_FSE_RAM_SIZE = u32:256; +const TEST_WEIGHTS_FSE_RAM_ADDR_W = std::clog2(TEST_WEIGHTS_FSE_RAM_SIZE); +const TEST_WEIGHTS_FSE_RAM_WORD_PARTITION_SIZE = TEST_WEIGHTS_FSE_RAM_DATA_W / u32:3; +const TEST_WEIGHTS_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_WEIGHTS_FSE_RAM_WORD_PARTITION_SIZE, TEST_WEIGHTS_FSE_RAM_DATA_W +); + +const TEST_WEIGHTS_TMP_RAM_DATA_W = u32:16; +const TEST_WEIGHTS_TMP_RAM_SIZE = u32:256; +const TEST_WEIGHTS_TMP_RAM_ADDR_W = std::clog2(TEST_WEIGHTS_TMP_RAM_SIZE); +const TEST_WEIGHTS_TMP_RAM_WORD_PARTITION_SIZE = TEST_WEIGHTS_TMP_RAM_DATA_W; +const TEST_WEIGHTS_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_WEIGHTS_TMP_RAM_WORD_PARTITION_SIZE, TEST_WEIGHTS_TMP_RAM_DATA_W +); + type TestCtrl = HuffmanLiteralsDecoderReq; type TestResp = HuffmanLiteralsDecoderResp; type TestAxiR = axi::AxiR; @@ -363,6 +496,21 @@ type TestAxiRamData = uN[TEST_AXI_RAM_MODEL_DATA_WIDTH]; type TestAxiRamAddr = uN[TEST_AXI_RAM_MODEL_ADDR_WIDTH]; type TestAxiRamMask = uN[TEST_AXI_RAM_MODEL_NUM_PARTITIONS]; +type TestWeightsDpdRamRdReq = ram::ReadReq; +type TestWeightsDpdRamRdResp = ram::ReadResp; +type TestWeightsDpdRamWrReq = ram::WriteReq; +type TestWeightsDpdRamWrResp = ram::WriteResp; + +type TestWeightsTmpRamRdReq = ram::ReadReq; +type TestWeightsTmpRamRdResp = ram::ReadResp; +type TestWeightsTmpRamWrReq = ram::WriteReq; +type TestWeightsTmpRamWrResp = ram::WriteResp; + +type TestWeightsFseRamRdReq = ram::ReadReq; +type TestWeightsFseRamRdResp = ram::ReadResp; +type TestWeightsFseRamWrReq = ram::WriteReq; +type TestWeightsFseRamWrResp = ram::WriteResp; + // Data for test case // Source: Example from RFC 8878, 4.2.2. Huffman-Coded Streams // https://datatracker.ietf.org/doc/html/rfc8878#huffman_coded_streams @@ -569,8 +717,10 @@ proc HuffmanLiteralsDecoder_test { ram_wr_resp_huffman_weights_header_r : chan in; ram_wr_req_huffman_weights_raw_s : chan out; ram_wr_resp_huffman_weights_raw_r : chan in; - ram_wr_req_huffman_weights_fse_s : chan out; - ram_wr_resp_huffman_weights_fse_r : chan in; + ram_wr_req_huffman_weights_fse_lookup_s : chan out; + ram_wr_resp_huffman_weights_fse_lookup_r : chan in; + ram_wr_req_huffman_weights_fse_decoder_s : chan out; + ram_wr_resp_huffman_weights_fse_decoder_r : chan in; config (terminator: chan out) { let (ctrl_s, ctrl_r) = chan("ctrl"); @@ -584,8 +734,10 @@ proc HuffmanLiteralsDecoder_test { let (weights_header_dec_axi_r_s, weights_header_dec_axi_r_r) = chan("weights_header_dec_axi_r"); let (weights_raw_dec_axi_ar_s, weights_raw_dec_axi_ar_r) = chan("weights_raw_dec_axi_ar"); let (weights_raw_dec_axi_r_s, weights_raw_dec_axi_r_r) = chan("weights_raw_dec_axi_r"); - let (weights_fse_dec_axi_ar_s, weights_fse_dec_axi_ar_r) = chan("weights_fse_dec_axi_ar"); - let (weights_fse_dec_axi_r_s, weights_fse_dec_axi_r_r) = chan("weights_fse_dec_axi_r"); + let (weights_fse_lookup_dec_axi_ar_s, weights_fse_lookup_dec_axi_ar_r) = chan("weights_fse_lookup_dec_axi_ar"); + let (weights_fse_lookup_dec_axi_r_s, weights_fse_lookup_dec_axi_r_r) = chan("weights_fse_lookup_dec_axi_r"); + let (weights_fse_decoder_dec_axi_ar_s, weights_fse_decoder_dec_axi_ar_r) = chan("weights_fse_decoder_dec_axi_ar"); + let (weights_fse_decoder_dec_axi_r_s, weights_fse_decoder_dec_axi_r_r) = chan("weights_fse_decoder_dec_axi_r"); // weights internal memory let (weights_ram_rd_req_s, weights_ram_rd_req_r) = chan("weights_ram_rd_req"); @@ -599,21 +751,62 @@ proc HuffmanLiteralsDecoder_test { let (prescan_ram_rd_req_s, prescan_ram_rd_req_r) = chan("prescan_ram_rd_req"); let (prescan_ram_rd_resp_s, prescan_ram_rd_resp_r) = chan("prescan_ram_rd_resp"); + // Weights FSE RAMs + let (weights_dpd_rd_req_s, weights_dpd_rd_req_r) = chan("weights_dpd_rd_req"); + let (weights_dpd_rd_resp_s, weights_dpd_rd_resp_r) = chan("weights_dpd_rd_resp"); + let (weights_dpd_wr_req_s, weights_dpd_wr_req_r) = chan("weights_dpd_wr_req"); + let (weights_dpd_wr_resp_s, weights_dpd_wr_resp_r) = chan("weights_dpd_wr_resp"); + + spawn ram::RamModel< + TEST_WEIGHTS_DPD_RAM_DATA_W, + TEST_WEIGHTS_DPD_RAM_SIZE, + TEST_WEIGHTS_DPD_RAM_WORD_PARTITION_SIZE + >(weights_dpd_rd_req_r, weights_dpd_rd_resp_s, weights_dpd_wr_req_r, weights_dpd_wr_resp_s); + + let (weights_tmp_rd_req_s, weights_tmp_rd_req_r) = chan("weights_tmp_rd_req"); + let (weights_tmp_rd_resp_s, weights_tmp_rd_resp_r) = chan("weights_tmp_rd_resp"); + let (weights_tmp_wr_req_s, weights_tmp_wr_req_r) = chan("weights_tmp_wr_req"); + let (weights_tmp_wr_resp_s, weights_tmp_wr_resp_r) = chan("weights_tmp_wr_resp"); + + spawn ram::RamModel< + TEST_WEIGHTS_TMP_RAM_DATA_W, + TEST_WEIGHTS_TMP_RAM_SIZE, + TEST_WEIGHTS_TMP_RAM_WORD_PARTITION_SIZE + >(weights_tmp_rd_req_r, weights_tmp_rd_resp_s, weights_tmp_wr_req_r, weights_tmp_wr_resp_s); + + let (weights_fse_rd_req_s, weights_fse_rd_req_r) = chan("weights_tmp_rd_req"); + let (weights_fse_rd_resp_s, weights_fse_rd_resp_r) = chan("weights_tmp_rd_resp"); + let (weights_fse_wr_req_s, weights_fse_wr_req_r) = chan("weights_tmp_wr_req"); + let (weights_fse_wr_resp_s, weights_fse_wr_resp_r) = chan("weights_tmp_wr_resp"); + + spawn ram::RamModel< + TEST_WEIGHTS_FSE_RAM_DATA_W, + TEST_WEIGHTS_FSE_RAM_SIZE, + TEST_WEIGHTS_FSE_RAM_WORD_PARTITION_SIZE + >(weights_fse_rd_req_r, weights_fse_rd_resp_s, weights_fse_wr_req_r, weights_fse_wr_resp_s); + spawn HuffmanLiteralsDecoder< TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_DEST_W, + TEST_WEIGHTS_DPD_RAM_ADDR_W, TEST_WEIGHTS_DPD_RAM_DATA_W, TEST_WEIGHTS_DPD_RAM_NUM_PARTITIONS, + TEST_WEIGHTS_TMP_RAM_ADDR_W, TEST_WEIGHTS_TMP_RAM_DATA_W, TEST_WEIGHTS_TMP_RAM_NUM_PARTITIONS, + TEST_WEIGHTS_FSE_RAM_ADDR_W, TEST_WEIGHTS_FSE_RAM_DATA_W, TEST_WEIGHTS_FSE_RAM_NUM_PARTITIONS, TEST_WEIGHTS_RAM_ADDR_WIDTH, TEST_WEIGHTS_RAM_DATA_WIDTH, TEST_WEIGHTS_RAM_NUM_PARTITIONS, - TEST_PRESCAN_RAM_ADDR_WIDTH, TEST_PRESCAN_RAM_DATA_WIDTH, TEST_PRESCAN_RAM_NUM_PARTITIONS + TEST_PRESCAN_RAM_ADDR_WIDTH, TEST_PRESCAN_RAM_DATA_WIDTH, TEST_PRESCAN_RAM_NUM_PARTITIONS, >( ctrl_r, resp_s, decoded_literals_s, axi_ar_s, axi_r_r, jump_table_axi_ar_s, jump_table_axi_r_r, weights_header_dec_axi_ar_s, weights_header_dec_axi_r_r, weights_raw_dec_axi_ar_s, weights_raw_dec_axi_r_r, - weights_fse_dec_axi_ar_s, weights_fse_dec_axi_r_r, + weights_fse_lookup_dec_axi_ar_s, weights_fse_lookup_dec_axi_r_r, + weights_fse_decoder_dec_axi_ar_s, weights_fse_decoder_dec_axi_r_r, weights_ram_rd_req_s, weights_ram_rd_resp_r, weights_ram_wr_req_s, weights_ram_wr_resp_r, prescan_ram_rd_req_s, prescan_ram_rd_resp_r, prescan_ram_wr_req_s, prescan_ram_wr_resp_r, + weights_dpd_rd_req_s, weights_dpd_rd_resp_r, weights_dpd_wr_req_s, weights_dpd_wr_resp_r, + weights_tmp_rd_req_s, weights_tmp_rd_resp_r, weights_tmp_wr_req_s, weights_tmp_wr_resp_r, + weights_fse_rd_req_s, weights_fse_rd_resp_r, weights_fse_wr_req_s, weights_fse_wr_resp_r, ); // Mock RAM for HuffmanLiteralsDecoder MemReader @@ -721,10 +914,35 @@ proc HuffmanLiteralsDecoder_test { ); // Mock RAM for HuffmanWeights fse decoder MemReader - let (ram_rd_req_huffman_weights_fse_s, ram_rd_req_huffman_weights_fse_r) = chan("ram_rd_req_huffman_weights_fse"); - let (ram_rd_resp_huffman_weights_fse_s, ram_rd_resp_huffman_weights_fse_r) = chan("ram_rd_resp_huffman_weights_fse"); - let (ram_wr_req_huffman_weights_fse_s, ram_wr_req_huffman_weights_fse_r) = chan("ram_wr_req_huffman_weights_fse"); - let (ram_wr_resp_huffman_weights_fse_s, ram_wr_resp_huffman_weights_fse_r) = chan("ram_wr_resp_huffman_weights_fse"); + let (ram_rd_req_huffman_weights_fse_lookup_s, ram_rd_req_huffman_weights_fse_lookup_r) = chan("ram_rd_req_huffman_weights_fse_lookup"); + let (ram_rd_resp_huffman_weights_fse_lookup_s, ram_rd_resp_huffman_weights_fse_lookup_r) = chan("ram_rd_resp_huffman_weights_fse_lookup"); + let (ram_wr_req_huffman_weights_fse_lookup_s, ram_wr_req_huffman_weights_fse_lookup_r) = chan("ram_wr_req_huffman_weights_fse_lookup"); + let (ram_wr_resp_huffman_weights_fse_lookup_s, ram_wr_resp_huffman_weights_fse_lookup_r) = chan("ram_wr_resp_huffman_weights_fse_lookup"); + + spawn ram::RamModel< + TEST_AXI_RAM_MODEL_DATA_WIDTH, + TEST_AXI_RAM_MODEL_SIZE, + TEST_AXI_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_AXI_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_AXI_RAM_MODEL_INITIALIZED, + TEST_AXI_RAM_MODEL_ASSERT_VALID_READ, + TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + ram_rd_req_huffman_weights_fse_lookup_r, ram_rd_resp_huffman_weights_fse_lookup_s, ram_wr_req_huffman_weights_fse_lookup_r, ram_wr_resp_huffman_weights_fse_lookup_s + ); + + spawn axi_ram::AxiRamReader< + TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, + TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + weights_fse_lookup_dec_axi_ar_r, weights_fse_lookup_dec_axi_r_s, + ram_rd_req_huffman_weights_fse_lookup_s, ram_rd_resp_huffman_weights_fse_lookup_r + ); + + let (ram_rd_req_huffman_weights_fse_decoder_s, ram_rd_req_huffman_weights_fse_decoder_r) = chan("ram_rd_req_huffman_weights_fse"); + let (ram_rd_resp_huffman_weights_fse_decoder_s, ram_rd_resp_huffman_weights_fse_decoder_r) = chan("ram_rd_resp_huffman_weights_fse"); + let (ram_wr_req_huffman_weights_fse_decoder_s, ram_wr_req_huffman_weights_fse_decoder_r) = chan("ram_wr_req_huffman_weights_fse"); + let (ram_wr_resp_huffman_weights_fse_decoder_s, ram_wr_resp_huffman_weights_fse_decoder_r) = chan("ram_wr_resp_huffman_weights_fse"); spawn ram::RamModel< TEST_AXI_RAM_MODEL_DATA_WIDTH, @@ -735,15 +953,15 @@ proc HuffmanLiteralsDecoder_test { TEST_AXI_RAM_MODEL_ASSERT_VALID_READ, TEST_AXI_RAM_MODEL_ADDR_WIDTH > ( - ram_rd_req_huffman_weights_fse_r, ram_rd_resp_huffman_weights_fse_s, ram_wr_req_huffman_weights_fse_r, ram_wr_resp_huffman_weights_fse_s + ram_rd_req_huffman_weights_fse_decoder_r, ram_rd_resp_huffman_weights_fse_decoder_s, ram_wr_req_huffman_weights_fse_decoder_r, ram_wr_resp_huffman_weights_fse_decoder_s ); spawn axi_ram::AxiRamReader< TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH > ( - weights_fse_dec_axi_ar_r, weights_fse_dec_axi_r_s, - ram_rd_req_huffman_weights_fse_s, ram_rd_resp_huffman_weights_fse_r + weights_fse_decoder_dec_axi_ar_r, weights_fse_decoder_dec_axi_r_s, + ram_rd_req_huffman_weights_fse_decoder_s, ram_rd_resp_huffman_weights_fse_decoder_r ); spawn ram::RamModel< @@ -767,7 +985,8 @@ proc HuffmanLiteralsDecoder_test { ram_wr_req_jump_table_s, ram_wr_resp_jump_table_r, ram_wr_req_huffman_weights_header_s, ram_wr_resp_huffman_weights_header_r, ram_wr_req_huffman_weights_raw_s, ram_wr_resp_huffman_weights_raw_r, - ram_wr_req_huffman_weights_fse_s, ram_wr_resp_huffman_weights_fse_r + ram_wr_req_huffman_weights_fse_lookup_s, ram_wr_resp_huffman_weights_fse_lookup_r, + ram_wr_req_huffman_weights_fse_decoder_s, ram_wr_resp_huffman_weights_fse_decoder_r, ) } @@ -788,8 +1007,10 @@ proc HuffmanLiteralsDecoder_test { let (tok, _) = recv(tok, ram_wr_resp_huffman_weights_header_r); let tok = send(tok, ram_wr_req_huffman_weights_raw_s, mem_req); let (tok, _) = recv(tok, ram_wr_resp_huffman_weights_raw_r); - let tok = send(tok, ram_wr_req_huffman_weights_fse_s, mem_req); - let (tok, _) = recv(tok, ram_wr_resp_huffman_weights_fse_r); + let tok = send(tok, ram_wr_req_huffman_weights_fse_lookup_s, mem_req); + let (tok, _) = recv(tok, ram_wr_resp_huffman_weights_fse_lookup_r); + let tok = send(tok, ram_wr_req_huffman_weights_fse_decoder_s, mem_req); + let (tok, _) = recv(tok, ram_wr_resp_huffman_weights_fse_decoder_r); tok }(tok); trace_fmt!("Filling system memory mock done"); diff --git a/xls/modules/zstd/huffman_weights_dec.x b/xls/modules/zstd/huffman_weights_dec.x index e44b0d11d0..bf71a615dc 100644 --- a/xls/modules/zstd/huffman_weights_dec.x +++ b/xls/modules/zstd/huffman_weights_dec.x @@ -14,12 +14,20 @@ import std; -import xls.examples.ram as ram; -import xls.modules.zstd.huffman_prescan as huffman_prescan; -import xls.modules.zstd.memory.axi as axi; -import xls.modules.zstd.memory.axi_ram as axi_ram; -import xls.modules.zstd.memory.mem_reader as mem_reader; -import xls.modules.zstd.ram_mux as ram_mux; +import xls.examples.ram; +import xls.modules.zstd.common ; +import xls.modules.zstd.huffman_prescan; +import xls.modules.zstd.memory.axi; +import xls.modules.zstd.memory.axi_ram; +import xls.modules.zstd.memory.mem_reader; +import xls.modules.zstd.ram_mux; +import xls.modules.zstd.refilling_shift_buffer; +import xls.modules.zstd.fse_lookup_dec; +import xls.modules.zstd.fse_table_creator; +import xls.modules.zstd.math; + +const HUFFMAN_FSE_MAX_ACCURACY_LOG = u32:9; +const HUFFMAN_FSE_ACCURACY_W = std::clog2(HUFFMAN_FSE_MAX_ACCURACY_LOG + u32:1); struct HuffmanRawWeightsDecoderReq { addr: uN[AXI_ADDR_W], @@ -323,8 +331,522 @@ proc HuffmanRawWeightsDecoder< } } + +enum HuffmanFseDecoderStatus: u1 { + OK = 0, + ERROR = 1, +} + +struct HuffmanFseDecoderCtrl { + acc_log: uN[HUFFMAN_FSE_ACCURACY_W ], + length: u8, +} + +struct HuffmanFseDecoderFinish { + status: HuffmanFseDecoderStatus, +} + +type HuffmanFseTableRecord = common::FseTableRecord; + +struct CommandConstructorData {} + +enum HuffmanFseDecoderFSM : u4 { + RECV_CTRL = 0, + PADDING = 1, + INIT_EVEN_STATE = 2, + INIT_ODD_STATE = 3, + SEND_RAM_EVEN_RD_REQ = 4, + RECV_RAM_EVEN_RD_RESP = 5, + SEND_RAM_ODD_RD_REQ = 6, + RECV_RAM_ODD_RD_RESP = 7, + UPDATE_EVEN_STATE = 8, + UPDATE_ODD_STATE = 9, + DECODE_LAST_WEIGHT = 10, + SEND_WEIGHT = 11, + SEND_WEIGHT_DONE = 12, + FILL_ZEROS = 13, + SEND_FINISH = 14, +} +struct HuffmanFseDecoderState { + fsm: HuffmanFseDecoderFSM, + ctrl: HuffmanFseDecoderCtrl, // decode request + even: u8, + odd: u8, + even_table_record: HuffmanFseTableRecord, // FSE lookup record for even_state + odd_table_record: HuffmanFseTableRecord, // FSE lookup record for odd_state + even_table_record_valid: bool, + odd_table_record_valid: bool, + even_state: u16, // analogous to state1 in educational ZSTD decoder + odd_state: u16, // analogous to state1 in educational ZSTD decoder + // https://github.com/facebook/zstd/blob/fe34776c207f3f879f386ed4158a38d927ff6d10/doc/educational_decoder/zstd_decompress.c#L2069 + read_bits_needed: u7, // how many bits to request from the ShiftBuffer next + sent_buf_ctrl: bool, // have we sent request to ShiftBuffer in this FSM state already? + shift_buffer_error: bool, // sticky flag, asserted if ShiftBuffer returns error in data + // payload, cleared when going to initial state + padding: u4, // how much padding have we consumed (used for checking stream validity) + current_iteration: u8, // which iteration of the FSE-encoded-weights decoding loop are we in: + // https://github.com/facebook/zstd/blob/fe34776c207f3f879f386ed4158a38d927ff6d10/doc/educational_decoder/zstd_decompress.c#L2081 + stream_len: u8, // how long is the FSE-encoded-weights stream, in bytes + stream_empty: bool, // did we ask for more bits than available in the stream (i.e. caused stream underflow)? + // analogous to 'offset < 0' check from educational ZSTD decoder: + // https://github.com/facebook/zstd/blob/fe34776c207f3f879f386ed4158a38d927ff6d10/doc/educational_decoder/zstd_decompress.c#L2089 + last_weight: u1, // whether the last weight is odd (last_weight == 1) or even (last_weight == 0) - + // - analogue to whether we should end up here: + // https://github.com/facebook/zstd/blob/fe34776c207f3f879f386ed4158a38d927ff6d10/doc/educational_decoder/zstd_decompress.c#L2091 + // ...or here: + // https://github.com/facebook/zstd/blob/fe34776c207f3f879f386ed4158a38d927ff6d10/doc/educational_decoder/zstd_decompress.c#L2100 + weights_pow_of_two_sum: u32, // sum of 2^weight for all weights, needed to calculate last weight + last_weight_decoded: bool, // have we decoded last weight? +} + +pub proc HuffmanFseDecoder< + RAM_DATA_W: u32, RAM_ADDR_W: u32, RAM_NUM_PARTITIONS:u32, + WEIGHTS_RAM_DATA_W: u32, WEIGHTS_RAM_ADDR_W: u32, WEIGHTS_RAM_NUM_PARTITIONS: u32, + AXI_DATA_W: u32, + REFILLING_SB_DATA_W: u32 = {AXI_DATA_W}, + REFILLING_SB_LENGTH_W: u32 = {refilling_shift_buffer::length_width(REFILLING_SB_DATA_W)}, +> { + type Ctrl = HuffmanFseDecoderCtrl; + type Finish = HuffmanFseDecoderFinish; + type Status = HuffmanFseDecoderStatus; + type State = HuffmanFseDecoderState; + type FSM = HuffmanFseDecoderFSM; + + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; + + type RefillingSBCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + type RefillingSBOutput = refilling_shift_buffer::RefillingShiftBufferOutput; + + type WeightsRamWrReq = ram::WriteReq; + type WeightsRamWrResp = ram::WriteResp; + + // Control + ctrl_r: chan in; + finish_s: chan out; + + // Shift buffer + rsb_ctrl_s: chan out; + rsb_data_r: chan in; + + // FSE table RAMs + table_rd_req_s: chan out; + table_rd_resp_r: chan in; + + // Weights RAMs + weights_wr_req_s: chan out; + weights_wr_resp_r: chan in; + + config ( + ctrl_r: chan in, + finish_s: chan out, + rsb_ctrl_s: chan out, + rsb_data_r: chan in, + table_rd_req_s: chan out, + table_rd_resp_r: chan in, + weights_wr_req_s: chan out, + weights_wr_resp_r: chan in, + ) { + ( + ctrl_r, finish_s, + rsb_ctrl_s, rsb_data_r, + table_rd_req_s, table_rd_resp_r, + weights_wr_req_s, weights_wr_resp_r, + ) + } + + init { zero!() } + + next (state: HuffmanFseDecoderState) { + type RamAddr = uN[RAM_ADDR_W]; + const RAM_MASK_ALL = std::unsigned_max_value(); + + let tok = join(); + + // receive ctrl + let (_, ctrl, ctrl_valid) = recv_if_non_blocking(tok, ctrl_r, state.fsm == FSM::RECV_CTRL, zero!()); + if ctrl_valid { + trace_fmt!("ctrl: {:#x}", ctrl); + } else {}; + let state = if ctrl_valid { + HuffmanFseDecoderState { + ctrl: ctrl, + stream_len: ctrl.length * u8:8, + ..state + } + } else { state }; + + // receive ram read response + let do_recv_table_rd_resp = state.fsm == FSM::RECV_RAM_EVEN_RD_RESP || state.fsm == FSM::RECV_RAM_ODD_RD_RESP; + let (_, table_rd_resp, table_rd_resp_valid) = recv_if_non_blocking(tok, table_rd_resp_r, do_recv_table_rd_resp, zero!()); + + let table_record = fse_table_creator::bits_to_fse_record(table_rd_resp.data); + + if table_rd_resp_valid { + trace_fmt!("table_record: {:#x}", table_record); + } else {}; + + // request records + let do_send_ram_rd_req = state.fsm == FSM::SEND_RAM_EVEN_RD_REQ || state.fsm == FSM::SEND_RAM_ODD_RD_REQ; + let ram_rd_req_addr = match (state.fsm) { + FSM::SEND_RAM_EVEN_RD_REQ => state.even_state as RamAddr, + FSM::SEND_RAM_ODD_RD_REQ => state.odd_state as RamAddr, + _ => RamAddr:0, + }; + + let table_req = FseRamRdReq { addr: ram_rd_req_addr, mask: RAM_MASK_ALL }; + + send_if(tok, table_rd_req_s, do_send_ram_rd_req, table_req); + + if do_send_ram_rd_req { + trace_fmt!("table_req: {:#x}", table_req); + } else {}; + + // read bits + let do_read_bits = ( + state.fsm == FSM::PADDING || + state.fsm == FSM::INIT_EVEN_STATE || + state.fsm == FSM::INIT_ODD_STATE || + state.fsm == FSM::UPDATE_EVEN_STATE || + state.fsm == FSM::UPDATE_ODD_STATE + ); + let do_send_buf_ctrl = do_read_bits && !state.sent_buf_ctrl && state.stream_len > u8:0; + + let read_length = if state.read_bits_needed as u8 > state.stream_len { + state.stream_len as u7 + } else { + state.read_bits_needed + }; + + let state = if state.read_bits_needed > u7:0 { + HuffmanFseDecoderState { + stream_empty: state.read_bits_needed as u8 > state.stream_len, + ..state + } + } else { state }; + + if do_send_buf_ctrl { + trace_fmt!("[FseDecoder] Asking for {:#x} data", read_length); + } else {}; + + send_if(tok, rsb_ctrl_s, do_send_buf_ctrl, RefillingSBCtrl { + length: read_length, + }); + + let state = if do_send_buf_ctrl { + HuffmanFseDecoderState { sent_buf_ctrl: do_send_buf_ctrl, ..state } + } else { state }; + + let recv_sb_output = (do_read_bits && state.sent_buf_ctrl); + let (_, buf_data, buf_data_valid) = recv_if_non_blocking(tok, rsb_data_r, recv_sb_output, zero!()); + if buf_data_valid && buf_data.length as u32 > u32:0{ + trace_fmt!("[FseDecoder] Received data {:#x} in state {}", buf_data, state.fsm); + } else { }; + + let state = if do_read_bits & buf_data_valid { + HuffmanFseDecoderState { + sent_buf_ctrl: false, + shift_buffer_error: state.shift_buffer_error | buf_data.error, + stream_len: state.stream_len - buf_data.length as u8, + ..state + } + } else { state }; + + // decode last weight + let max_bits = common::highest_set_bit(state.weights_pow_of_two_sum) + u32:1; + let next_power = u32:1 << max_bits; + let left_over = (next_power - state.weights_pow_of_two_sum); + let last_weight = (common::highest_set_bit(left_over) + u32:1) as u8; + + // write weight + const WEIGHTS_RAM_BYTES = WEIGHTS_RAM_DATA_W as u8 / u8:8; + let iter_mod4_inv = u8:3 - (state.current_iteration & u8:0x3); + let weight = (((state.even as u8) << u32:4) & u8:0xF0) | ((state.odd as u8) & u8:0x0F); + let weights_wr_req = WeightsRamWrReq { + addr: (state.current_iteration / WEIGHTS_RAM_BYTES) as uN[WEIGHTS_RAM_ADDR_W], + data: (weight as uN[WEIGHTS_RAM_DATA_W] << (u8:8 * iter_mod4_inv)), + // mask appropriate byte in 32-bit word with 4-bit slices + mask: uN[WEIGHTS_RAM_NUM_PARTITIONS]:0x3 << (u8:2 * iter_mod4_inv), + }; + let tok = send_if(tok, weights_wr_req_s, state.fsm == FSM::SEND_WEIGHT, weights_wr_req); + if (state.fsm == FSM::SEND_WEIGHT) { + trace_fmt!("Sent weight to RAM: {:#x}", weights_wr_req); + } else {}; + + let (tok, _, weights_wr_resp_valid) = recv_if_non_blocking( + tok, weights_wr_resp_r, state.fsm == FSM::SEND_WEIGHT_DONE, zero!() + ); + + // send finish + send_if(tok, finish_s, state.fsm == FSM::SEND_FINISH, Finish { + status: if state.shift_buffer_error { Status::ERROR } else { Status::OK } + }); + + // update state + match (state.fsm) { + FSM::RECV_CTRL => { + if (ctrl_valid) { + trace_fmt!("[FseDecoder] Moving to PADDING"); + State { + fsm: FSM::PADDING, + ctrl: ctrl, + read_bits_needed: u7:1, + ..state + } + } else { state } + }, + FSM::PADDING => { + if (buf_data_valid) { + let padding = state.padding + u4:1; + assert!(padding <= u4:8, "invalid_padding"); + + let padding_available = (buf_data.data as u1 == u1:0); + if padding_available { + State { + fsm: FSM::PADDING, + read_bits_needed: u7:1, + padding, ..state + } + } else { + trace_fmt!("[FseDecoder] Moving to INIT_LOOKUP_STATE"); + trace_fmt!("padding is: {:#x}", padding); + State { + fsm: FSM::INIT_EVEN_STATE, + read_bits_needed: state.ctrl.acc_log as u7, + ..state + } + } + } else { state } + }, + FSM::INIT_EVEN_STATE => { + if (buf_data_valid) { + trace_fmt!("[FseDecoder] Moving to INIT_ODD_STATE"); + State { + fsm: FSM::INIT_ODD_STATE, + even_state: buf_data.data as u16, + read_bits_needed: state.ctrl.acc_log as u7, + ..state + } + } else { state } + }, + FSM::INIT_ODD_STATE => { + if (buf_data_valid) { + trace_fmt!("[FseDecoder] Moving to SEND_RAM_EVEN_RD_REQ"); + State { + fsm: FSM::SEND_RAM_EVEN_RD_REQ, + odd_state: buf_data.data as u16, + read_bits_needed: u7:0, + ..state + } + } else { state } + }, + FSM::SEND_RAM_EVEN_RD_REQ => { + trace_fmt!("[FseDecoder] Moving to RECV_RAM_EVEN_RD_RESP"); + trace_fmt!("State even: {:#x}", state.even_state); + State { + fsm: FSM::RECV_RAM_EVEN_RD_RESP, + even_table_record_valid: false, + ..state + } + }, + FSM::RECV_RAM_EVEN_RD_RESP => { + // save fse records in state + let state = if table_rd_resp_valid { + State { even_table_record: table_record, even_table_record_valid: true, ..state } + } else { state }; + + if state.even_table_record_valid { + let symbol = state.even_table_record.symbol; + let pow = if symbol != u8:0 { + u32:1 << (symbol - u8:1) + } else { + u32:0 + }; + if state.stream_empty { + trace_fmt!("[FseDecoder] Moving to DECODE_LAST_WEIGHT"); + State { + fsm: FSM::DECODE_LAST_WEIGHT, + even: symbol, + weights_pow_of_two_sum: state.weights_pow_of_two_sum + pow, + ..state + } + } else { + trace_fmt!("[FseDecoder] Moving to SEND_RAM_ODD_RD_REQ"); + State { + fsm: FSM::SEND_RAM_ODD_RD_REQ, + even: symbol, + weights_pow_of_two_sum: state.weights_pow_of_two_sum + pow, + ..state + } + } + } else { state } + }, + FSM::SEND_RAM_ODD_RD_REQ => { + trace_fmt!("[FseDecoder] Moving to RECV_RAM_ODD_RD_RESP"); + trace_fmt!("State odd: {:#x}", state.odd_state); + State { + fsm: FSM::RECV_RAM_ODD_RD_RESP, + odd_table_record_valid: false, + ..state + } + }, + FSM::RECV_RAM_ODD_RD_RESP => { + // save fse records in state + let state = if table_rd_resp_valid { + State { odd_table_record: table_record, odd_table_record_valid: true, ..state } + } else { state }; + + if state.odd_table_record_valid { + let symbol = state.odd_table_record.symbol; + let pow = if symbol != u8:0 { + u32:1 << (symbol - u8:1) + } else { + u32:0 + }; + if state.stream_empty { + trace_fmt!("[FseDecoder] Moving to SEND_WEIGHT"); + State { + fsm: FSM::DECODE_LAST_WEIGHT, + odd: symbol, + weights_pow_of_two_sum: state.weights_pow_of_two_sum + pow, + ..state + } + } else { + trace_fmt!("[FseDecoder] Moving to UPDATE_EVEN_STATE"); + State { + fsm: FSM::UPDATE_EVEN_STATE, + odd: state.odd_table_record.symbol, + weights_pow_of_two_sum: state.weights_pow_of_two_sum + pow, + read_bits_needed: state.even_table_record.num_of_bits as u7, + ..state + } + } + } else { state } + }, + FSM::UPDATE_EVEN_STATE => { + if state.stream_empty { + trace_fmt!("[FseDecoder] Moving to SEND_WEIGHT"); + State { + fsm: FSM::SEND_WEIGHT, + even_state: state.even_table_record.base + buf_data.data as u16, + read_bits_needed: state.odd_table_record.num_of_bits as u7, + last_weight: u1:0, + ..state + } + } else if buf_data_valid || state.stream_len == u8:0 { + trace_fmt!("[FseDecoder] Moving to UPDATE_ODD_STATE"); + State { + fsm: FSM::UPDATE_ODD_STATE, + even_state: state.even_table_record.base + buf_data.data as u16, + read_bits_needed: state.odd_table_record.num_of_bits as u7, + ..state + } + } else { state } + }, + FSM::UPDATE_ODD_STATE => { + if state.stream_empty { + trace_fmt!("[FseDecoder] Moving to SEND_WEIGHT"); + State { + fsm: FSM::SEND_WEIGHT, + odd_state: state.odd_table_record.base + buf_data.data as u16, + read_bits_needed: u7:0, + last_weight: u1:1, + ..state + } + } else if buf_data_valid || state.stream_len == u8:0 { + trace_fmt!("[FseDecoder] Moving to SEND_WEIGHT"); + State { + fsm: FSM::SEND_WEIGHT, + odd_state: state.odd_table_record.base + buf_data.data as u16, + read_bits_needed: u7:0, + ..state + } + } else { state } + }, + FSM::DECODE_LAST_WEIGHT => { + trace_fmt!("[FseDecoder] Moving to SEND_WEIGHT"); + trace_fmt!("[FseDecoder] Last weight {:#x}, weights^2: {}, max_bits: {}, left_over: {}, iteration {}", last_weight, state.weights_pow_of_two_sum, max_bits, left_over, state.current_iteration); + if state.last_weight == u1:0 { + State { + fsm: FSM::SEND_WEIGHT, + even: last_weight, + odd: u8:0, + last_weight_decoded: true, + ..state + } + } else { + State { + fsm: FSM::SEND_WEIGHT, + // even weight should be kept unchanged + odd: last_weight, + last_weight_decoded: true, + ..state + } + } + }, + FSM::SEND_WEIGHT => { + trace_fmt!("[FseDecoder] Current iteration: {}, weights: {} {} {}", state.current_iteration, state.even_state, state.odd_state, weight); + State { + fsm: FSM::SEND_WEIGHT_DONE, + current_iteration: state.current_iteration + u8:1, + ..state + } + }, + FSM::SEND_WEIGHT_DONE => { + if weights_wr_resp_valid { + trace_fmt!("Weights write done"); + let fsm = if state.stream_empty { + if state.last_weight_decoded { + FSM::FILL_ZEROS + } else { + // get second-to-last weight + if state.last_weight == u1:1 { + FSM::SEND_RAM_EVEN_RD_REQ + } else { + FSM::SEND_RAM_ODD_RD_REQ + } + } + } else { + FSM::SEND_RAM_EVEN_RD_REQ + }; + State { + fsm: fsm, + ..state + } + } else { state } + }, + FSM::FILL_ZEROS => { + if state.current_iteration == u8:0x7F { + State { + fsm: FSM::SEND_FINISH, + ..state + } + } else { + State { + fsm: FSM::SEND_WEIGHT, + even: u8:0, + odd: u8:0, + ..state + } + } + }, + FSM::SEND_FINISH => { + trace_fmt!("[FseDecoder] Moving to RECV_CTRL"); + State { + fsm:FSM::RECV_CTRL, + ..zero!() + } + }, + _ => { + fail!("impossible_case", state) + }, + } + } +} + struct HuffmanFseWeightsDecoderReq { - addr: uN[AXI_ADDR_W] + addr: uN[AXI_ADDR_W], + length: u8, } enum HuffmanFseWeightsDecoderStatus: u1 { @@ -336,14 +858,17 @@ struct HuffmanFseWeightsDecoderResp { status: HuffmanFseWeightsDecoderStatus, } -struct HuffmanFseWeightsDecoderState { - placeholder: u1 -} +struct HuffmanFseWeightsDecoderState { } proc HuffmanFseWeightsDecoder< - AXI_ADDR_W: u32, AXI_DATA_W: u32, - WEIGHTS_RAM_ADDR_W: u32, WEIGHTS_RAM_DATA_W: u32, - WEIGHTS_RAM_NUM_PARTITIONS: u32 + AXI_ADDR_W: u32, AXI_DATA_W: u32, AXI_ID_W: u32, + WEIGHTS_RAM_ADDR_W: u32, WEIGHTS_RAM_DATA_W: u32, WEIGHTS_RAM_NUM_PARTITIONS: u32, + DPD_RAM_ADDR_W: u32, DPD_RAM_DATA_W: u32, DPD_RAM_NUM_PARTITIONS: u32, + TMP_RAM_ADDR_W: u32, TMP_RAM_DATA_W: u32, TMP_RAM_NUM_PARTITIONS: u32, + TMP2_RAM_ADDR_W: u32, TMP2_RAM_DATA_W: u32, TMP2_RAM_NUM_PARTITIONS: u32, + FSE_RAM_ADDR_W: u32, FSE_RAM_DATA_W: u32, FSE_RAM_NUM_PARTITIONS: u32, + REFILLING_SB_DATA_W: u32 = {AXI_DATA_W}, + REFILLING_SB_LENGTH_W: u32 = {refilling_shift_buffer::length_width(AXI_DATA_W)}, > { type Req = HuffmanFseWeightsDecoderReq; type Resp = HuffmanFseWeightsDecoderResp; @@ -356,16 +881,58 @@ proc HuffmanFseWeightsDecoder< type WeightsRamWrReq = ram::WriteReq; type WeightsRamWrResp = ram::WriteResp; + type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; + type FseLookupDecoderResp = fse_lookup_dec::FseLookupDecoderResp; + + type DpdRamRdReq = ram::ReadReq; + type DpdRamRdResp = ram::ReadResp; + type DpdRamWrReq = ram::WriteReq; + type DpdRamWrResp = ram::WriteResp; + + type TmpRamRdReq = ram::ReadReq; + type TmpRamRdResp = ram::ReadResp; + type TmpRamWrReq = ram::WriteReq; + type TmpRamWrResp = ram::WriteResp; + + type Tmp2RamRdReq = ram::ReadReq; + type Tmp2RamRdResp = ram::ReadResp; + type Tmp2RamWrReq = ram::WriteReq; + type Tmp2RamWrResp = ram::WriteResp; + + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; + type FseRamWrReq = ram::WriteReq; + type FseRamWrResp = ram::WriteResp; + + type RefillingShiftBufferStart = refilling_shift_buffer::RefillStart; + type RefillingShiftBufferError = refilling_shift_buffer::RefillingShiftBufferInput; + type RefillingShiftBufferOutput = refilling_shift_buffer::RefillingShiftBufferOutput; + type RefillingShiftBufferCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + + type MemAxiAr = axi::AxiAr; + type MemAxiR = axi::AxiR; + // Control req_r: chan in; resp_s: chan out; - // MemReader interface for fetching Huffman Tree Description - mem_rd_req_s: chan out; - mem_rd_resp_r: chan in; + // Refilling shift buffer for lookup decoder + fld_rsb_start_req_s: chan out; + fld_rsb_stop_flush_req_s: chan<()> out; + fld_rsb_flushing_done_r: chan<()> in; - weights_ram_wr_req_s: chan out; - weights_ram_wr_resp_r: chan in; + // Refilling shift buffer for FSE decoder + fd_rsb_start_req_s: chan out; + fd_rsb_stop_flush_req_s: chan<()> out; + fd_rsb_flushing_done_r: chan<()> in; + + // FSE Lookup Decoder + fld_req_s: chan out; + fld_resp_r: chan in; + + // Huffman FSE Decoder + fd_ctrl_s: chan out; + fd_finish_r: chan in; init { zero!() @@ -377,34 +944,159 @@ proc HuffmanFseWeightsDecoder< resp_s: chan out, // MemReader interface for fetching Huffman Tree Description - mem_rd_req_s: chan out, - mem_rd_resp_r: chan in, + lookup_mem_rd_req_s: chan out, + lookup_mem_rd_resp_r: chan in, + decoder_mem_rd_req_s: chan out, + decoder_mem_rd_resp_r: chan in, // RAM Write interface (goes to Huffman Weights Memory) weights_ram_wr_req_s: chan out, weights_ram_wr_resp_r: chan in, + + // FSE RAMs + dpd_rd_req_s: chan out, + dpd_rd_resp_r: chan in, + dpd_wr_req_s: chan out, + dpd_wr_resp_r: chan in, + + tmp_rd_req_s: chan out, + tmp_rd_resp_r: chan in, + tmp_wr_req_s: chan out, + tmp_wr_resp_r: chan in, + + tmp2_rd_req_s: chan out, + tmp2_rd_resp_r: chan in, + tmp2_wr_req_s: chan out, + tmp2_wr_resp_r: chan in, + + fse_rd_req_s: chan out, + fse_rd_resp_r: chan in, + fse_wr_req_s: chan out, + fse_wr_resp_r: chan in, ) { + const CHANNEL_DEPTH = u32:1; + + // FseLookupDecoder + let (fld_rsb_start_req_s, fld_rsb_start_req_r) = chan("fd_rsb_start_req"); + let (fld_rsb_stop_flush_req_s, fld_rsb_stop_flush_req_r) = chan<(), CHANNEL_DEPTH>("fd_rsb_stop_flush_req"); + let (fld_rsb_ctrl_s, fld_rsb_ctrl_r) = chan("fd_rsb_ctrl"); + let (fld_rsb_data_s, fld_rsb_data_r) = chan("fd_rsb_data"); + let (fld_rsb_flushing_done_s, fld_rsb_flushing_done_r) = chan<(), CHANNEL_DEPTH>("fd_rsb_flushing_done"); + + spawn refilling_shift_buffer::RefillingShiftBuffer ( + lookup_mem_rd_req_s, lookup_mem_rd_resp_r, + fld_rsb_start_req_r, fld_rsb_stop_flush_req_r, + fld_rsb_ctrl_r, fld_rsb_data_s, + fld_rsb_flushing_done_s, + ); + + let (fld_req_s, fld_req_r) = chan("fse_req"); + let (fld_resp_s, fld_resp_r) = chan("fse_resp"); + + spawn fse_lookup_dec::FseLookupDecoder< + AXI_DATA_W, + DPD_RAM_DATA_W, DPD_RAM_ADDR_W, DPD_RAM_NUM_PARTITIONS, + TMP_RAM_DATA_W, TMP_RAM_ADDR_W, TMP_RAM_NUM_PARTITIONS, + TMP2_RAM_DATA_W, TMP2_RAM_ADDR_W, TMP2_RAM_NUM_PARTITIONS, + FSE_RAM_DATA_W, FSE_RAM_ADDR_W, FSE_RAM_NUM_PARTITIONS, + >( + fld_req_r, fld_resp_s, + dpd_rd_req_s, dpd_rd_resp_r, dpd_wr_req_s, dpd_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s, tmp2_wr_resp_r, + fse_wr_req_s, fse_wr_resp_r, + fld_rsb_ctrl_s, fld_rsb_data_r, + ); + + // Huffman FSE Decoder + let (fd_rsb_start_req_s, fd_rsb_start_req_r) = chan("fd_rsb_start_req"); + let (fd_rsb_stop_flush_req_s, fd_rsb_stop_flush_req_r) = chan<(), CHANNEL_DEPTH>("fd_rsb_stop_flush_req"); + let (fd_rsb_ctrl_s, fd_rsb_ctrl_r) = chan("fd_rsb_ctrl"); + let (fd_rsb_data_s, fd_rsb_data_r) = chan("fd_rsb_data"); + let (fd_rsb_flushing_done_s, fd_rsb_flushing_done_r) = chan<(), CHANNEL_DEPTH>("fd_rsb_flushing_done"); + + spawn refilling_shift_buffer::RefillingShiftBuffer ( + decoder_mem_rd_req_s, decoder_mem_rd_resp_r, + fd_rsb_start_req_r, fd_rsb_stop_flush_req_r, + fd_rsb_ctrl_r, fd_rsb_data_s, + fd_rsb_flushing_done_s, + ); + + let (fd_ctrl_s, fd_ctrl_r) = chan("fd_ctrl"); + let (fd_finish_s, fd_finish_r) = chan("fd_finish"); + + spawn HuffmanFseDecoder< + FSE_RAM_DATA_W, FSE_RAM_ADDR_W, FSE_RAM_NUM_PARTITIONS, + WEIGHTS_RAM_DATA_W, WEIGHTS_RAM_ADDR_W, WEIGHTS_RAM_NUM_PARTITIONS, + AXI_DATA_W, + >( + fd_ctrl_r, fd_finish_s, + fd_rsb_ctrl_s, fd_rsb_data_r, + fse_rd_req_s, fse_rd_resp_r, + weights_ram_wr_req_s, weights_ram_wr_resp_r, + ); ( req_r, resp_s, - mem_rd_req_s, mem_rd_resp_r, - weights_ram_wr_req_s, weights_ram_wr_resp_r + fld_rsb_start_req_s, fld_rsb_stop_flush_req_s, fld_rsb_flushing_done_r, + fd_rsb_start_req_s, fd_rsb_stop_flush_req_s, fd_rsb_flushing_done_r, + fld_req_s, fld_resp_r, + fd_ctrl_s, fd_finish_r, ) } next (state: State) { let tok = join(); - let (tok, req) = recv_if(tok, req_r, false, zero!()); + // Receive decoding request + let (tok, req) = recv(tok, req_r); + trace_fmt!("[FSE] Received decoding request {:#x}", req); - // Fetch Data - let tok = send_if(tok, mem_rd_req_s, false, zero!()); - let (tok, mem_rd_resp) = recv_if(tok, mem_rd_resp_r, false, zero!()); + // Decode lookup + let fld_rsb_start_req = RefillingShiftBufferStart { + start_addr: req.addr + uN[AXI_ADDR_W]:1, // skip header byte + }; + let tok = send(tok, fld_rsb_start_req_s, fld_rsb_start_req); + trace_fmt!("[FSE] Sent refilling shift buffer start request {:#x}", fld_rsb_start_req); - // Send to RAM - let tok = send_if(tok, weights_ram_wr_req_s, false, zero!()); - let (tok, _) = recv_if(tok, weights_ram_wr_resp_r, false, zero!()); + let fld_req = FseLookupDecoderReq { + }; + let tok = send(tok, fld_req_s, fld_req); + trace_fmt!("[FSE] Sent FSE lookup decoding request {:#x}", fld_req); + + let (tok, fld_resp) = recv(tok, fld_resp_r); + trace_fmt!("[FSE] Received FSE lookup decoding response {:#x}", fld_resp); + let tok = send(tok, fld_rsb_stop_flush_req_s, ()); + trace_fmt!("[FSE] Sent refilling shift buffer stop flush request"); + + let (tok, _) = recv(tok, fld_rsb_flushing_done_r); + trace_fmt!("[FSE] Received refilling shift buffer flushing done"); + + // Decode weights + let fd_rsb_start_req = RefillingShiftBufferStart { + start_addr: req.addr + uN[AXI_ADDR_W]:1 + req.length as uN[AXI_ADDR_W] + }; + let tok = send(tok, fd_rsb_start_req_s, fd_rsb_start_req); + trace_fmt!("[FSE] Sent refilling shift buffer start request {:#x}", fd_rsb_start_req); + + let fd_ctrl = HuffmanFseDecoderCtrl { + length: req.length - u8:2, // FIXME: replace hard-coded value with FSE header size + acc_log: fld_resp.accuracy_log, + }; + let tok = send(tok, fd_ctrl_s, fd_ctrl); + trace_fmt!("[FSE] Sent FSE decoding request {:#x}", fd_ctrl); + + let (tok, fd_finish) = recv(tok, fd_finish_r); + trace_fmt!("[FSE] Received FSE decoding finish {:#x}", fd_finish); + + let tok = send(tok, fd_rsb_stop_flush_req_s, ()); + trace_fmt!("[FSE] Sent refilling shift buffer stop flush request"); + + let (tok, _) = recv(tok, fd_rsb_flushing_done_r); + trace_fmt!("[FSE] Received refilling shift buffer flushing done"); + + // Send decoding response let tok = send(tok, resp_s, zero!()); zero!() @@ -435,9 +1127,12 @@ pub struct HuffmanWeightsDecoderState { } pub proc HuffmanWeightsDecoder< - AXI_ADDR_W: u32, AXI_DATA_W: u32, - WEIGHTS_RAM_ADDR_W: u32, WEIGHTS_RAM_DATA_W: u32, - WEIGHTS_RAM_NUM_PARTITIONS: u32 + AXI_ADDR_W: u32, AXI_DATA_W: u32, AXI_ID_W: u32, + WEIGHTS_RAM_ADDR_W: u32, WEIGHTS_RAM_DATA_W: u32, WEIGHTS_RAM_NUM_PARTITIONS: u32, + DPD_RAM_ADDR_W: u32, DPD_RAM_DATA_W: u32, DPD_RAM_NUM_PARTITIONS: u32, + TMP_RAM_ADDR_W: u32, TMP_RAM_DATA_W: u32, TMP_RAM_NUM_PARTITIONS: u32, + TMP2_RAM_ADDR_W: u32, TMP2_RAM_DATA_W: u32, TMP2_RAM_NUM_PARTITIONS: u32, + FSE_RAM_ADDR_W: u32, FSE_RAM_DATA_W: u32, FSE_RAM_NUM_PARTITIONS: u32, > { type Req = HuffmanWeightsDecoderReq; type Resp = HuffmanWeightsDecoderResp; @@ -457,6 +1152,30 @@ pub proc HuffmanWeightsDecoder< type FseWeightsReq = HuffmanFseWeightsDecoderReq; type FseWeightsResp = HuffmanFseWeightsDecoderResp; + // FSE RAMs + type DpdRamRdReq = ram::ReadReq; + type DpdRamRdResp = ram::ReadResp; + type DpdRamWrReq = ram::WriteReq; + type DpdRamWrResp = ram::WriteResp; + + type TmpRamRdReq = ram::ReadReq; + type TmpRamRdResp = ram::ReadResp; + type TmpRamWrReq = ram::WriteReq; + type TmpRamWrResp = ram::WriteResp; + + type Tmp2RamRdReq = ram::ReadReq; + type Tmp2RamRdResp = ram::ReadResp; + type Tmp2RamWrReq = ram::WriteReq; + type Tmp2RamWrResp = ram::WriteResp; + + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; + type FseRamWrReq = ram::WriteReq; + type FseRamWrResp = ram::WriteResp; + + type MemAxiAr = axi::AxiAr; + type MemAxiR = axi::AxiR; + // Control req_r: chan in; resp_s: chan out; @@ -502,12 +1221,35 @@ pub proc HuffmanWeightsDecoder< raw_weights_mem_rd_resp_r: chan in, // MemReader interface for Fse Huffman Tree Description Decoder - fse_weights_mem_rd_req_s: chan out, - fse_weights_mem_rd_resp_r: chan in, + fse_lookup_weights_mem_rd_req_s: chan out, + fse_lookup_weights_mem_rd_resp_r: chan in, + fse_decoder_weights_mem_rd_req_s: chan out, + fse_decoder_weights_mem_rd_resp_r: chan in, // Muxed internal RAM Write interface (goes to Huffman Weights Memory) weights_ram_wr_req_s: chan out, weights_ram_wr_resp_r: chan in, + + // FSE RAMs + dpd_rd_req_s: chan out, + dpd_rd_resp_r: chan in, + dpd_wr_req_s: chan out, + dpd_wr_resp_r: chan in, + + tmp_rd_req_s: chan out, + tmp_rd_resp_r: chan in, + tmp_wr_req_s: chan out, + tmp_wr_resp_r: chan in, + + tmp2_rd_req_s: chan out, + tmp2_rd_resp_r: chan in, + tmp2_wr_req_s: chan out, + tmp2_wr_resp_r: chan in, + + fse_rd_req_s: chan out, + fse_rd_resp_r: chan in, + fse_wr_req_s: chan out, + fse_wr_resp_r: chan in, ) { // Decoded Weights select for RamMux let (decoded_weights_sel_s, decoded_weights_sel_r) = chan("decoded_weights_sel"); @@ -525,8 +1267,13 @@ pub proc HuffmanWeightsDecoder< let (raw_weights_ram_wr_resp_s, raw_weights_ram_wr_resp_r) = chan("raw_weights_ram_wr_resp"); // Internal RAM Write interface with decoded Fse Huffman Tree Description - let (fse_weights_ram_wr_req_s, fse_weights_ram_wr_req_r) = chan("fse_weights_ram_wr_req"); - let (fse_weights_ram_wr_resp_s, fse_weights_ram_wr_resp_r) = chan("fse_weights_ram_wr_resp"); + let (fse_weights_ram_wr_req_s, fse_weights_ram_wr_req_r) = chan("raw_weights_ram_wr_req"); + let (fse_weights_ram_wr_resp_s, fse_weights_ram_wr_resp_r) = chan("raw_weights_ram_wr_resp"); + + let (fse_lookup_weights_ram_wr_req_s, fse_lookup_weights_ram_wr_req_r) = chan("fse_weights_ram_wr_req"); + let (fse_lookup_weights_ram_wr_resp_s, fse_lookup_weights_ram_wr_resp_r) = chan("fse_weights_ram_wr_resp"); + let (fse_decoder_weights_ram_wr_req_s, fse_decoder_weights_ram_wr_req_r) = chan("fse_weights_ram_wr_req"); + let (fse_decoder_weights_ram_wr_resp_s, fse_decoder_weights_ram_wr_resp_r) = chan("fse_weights_ram_wr_resp"); let (raw_weights_ram_rd_req_s, raw_weights_ram_rd_req_r) = chan("raw_weights_ram_rd_req"); let (raw_weights_ram_rd_resp_s, raw_weights_ram_rd_resp_r) = chan("raw_weights_ram_rd_resp"); @@ -546,13 +1293,21 @@ pub proc HuffmanWeightsDecoder< ); spawn HuffmanFseWeightsDecoder< - AXI_ADDR_W, AXI_DATA_W, - WEIGHTS_RAM_ADDR_W, WEIGHTS_RAM_DATA_W, - WEIGHTS_RAM_NUM_PARTITIONS + AXI_ADDR_W, AXI_DATA_W, AXI_ID_W, + WEIGHTS_RAM_ADDR_W, WEIGHTS_RAM_DATA_W, WEIGHTS_RAM_NUM_PARTITIONS, + DPD_RAM_ADDR_W, DPD_RAM_DATA_W, DPD_RAM_NUM_PARTITIONS, + TMP_RAM_ADDR_W, TMP_RAM_DATA_W, TMP_RAM_NUM_PARTITIONS, + TMP2_RAM_ADDR_W, TMP2_RAM_DATA_W, TMP2_RAM_NUM_PARTITIONS, + FSE_RAM_ADDR_W, FSE_RAM_DATA_W, FSE_RAM_NUM_PARTITIONS, >( fse_weights_req_r, fse_weights_resp_s, - fse_weights_mem_rd_req_s, fse_weights_mem_rd_resp_r, - fse_weights_ram_wr_req_s, fse_weights_ram_wr_resp_r + fse_lookup_weights_mem_rd_req_s, fse_lookup_weights_mem_rd_resp_r, + fse_decoder_weights_mem_rd_req_s, fse_decoder_weights_mem_rd_resp_r, + fse_weights_ram_wr_req_s, fse_weights_ram_wr_resp_r, + dpd_rd_req_s, dpd_rd_resp_r, dpd_wr_req_s, dpd_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s, tmp2_wr_resp_r, + fse_rd_req_s, fse_rd_resp_r, fse_wr_req_s, fse_wr_resp_r, ); spawn ram_mux::RamMux( @@ -611,7 +1366,10 @@ pub proc HuffmanWeightsDecoder< if weights_type == WeightsType::FSE { trace_fmt!("Decoding FSE Huffman weights"); } else {}; - let fse_weights_req = zero!(); + let fse_weights_req = FseWeightsReq { + addr: req.addr, + length: header_byte, + }; let tok = send_if(tok, fse_weights_req_s, weights_type == WeightsType::FSE, fse_weights_req); let (tok, fse_weights_resp) = recv_if(tok, fse_weights_resp_r, weights_type == WeightsType::FSE, zero!()); @@ -691,6 +1449,30 @@ const INST_WEIGHTS_RAM_SIMULTANEOUS_RW_BEHAVIOR = ram::SimultaneousReadWriteBeha const INST_WEIGHTS_RAM_INITIALIZED = true; const INST_WEIGHTS_RAM_ASSERT_VALID_READ = true; +const INST_DPD_RAM_DATA_W = u32:16; +const INST_DPD_RAM_SIZE = u32:256; +const INST_DPD_RAM_ADDR_W = std::clog2(INST_DPD_RAM_SIZE); +const INST_DPD_RAM_WORD_PARTITION_SIZE = INST_DPD_RAM_DATA_W; +const INST_DPD_RAM_NUM_PARTITIONS = ram::num_partitions(INST_DPD_RAM_WORD_PARTITION_SIZE, INST_DPD_RAM_DATA_W); + +const INST_FSE_RAM_DATA_W = u32:32; +const INST_FSE_RAM_SIZE = u32:256; +const INST_FSE_RAM_ADDR_W = std::clog2(INST_FSE_RAM_SIZE); +const INST_FSE_RAM_WORD_PARTITION_SIZE = INST_FSE_RAM_DATA_W / u32:3; +const INST_FSE_RAM_NUM_PARTITIONS = ram::num_partitions(INST_FSE_RAM_WORD_PARTITION_SIZE, INST_FSE_RAM_DATA_W); + +const INST_TMP_RAM_DATA_W = u32:16; +const INST_TMP_RAM_SIZE = u32:256; +const INST_TMP_RAM_ADDR_W = std::clog2(INST_TMP_RAM_SIZE); +const INST_TMP_RAM_WORD_PARTITION_SIZE = INST_TMP_RAM_DATA_W; +const INST_TMP_RAM_NUM_PARTITIONS = ram::num_partitions(INST_TMP_RAM_WORD_PARTITION_SIZE, INST_TMP_RAM_DATA_W); + +const INST_TMP2_RAM_DATA_W = u32:8; +const INST_TMP2_RAM_SIZE = u32:512; +const INST_TMP2_RAM_ADDR_W = std::clog2(INST_TMP2_RAM_SIZE); +const INST_TMP2_RAM_WORD_PARTITION_SIZE = INST_TMP2_RAM_DATA_W; +const INST_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions(INST_TMP2_RAM_WORD_PARTITION_SIZE, INST_TMP2_RAM_DATA_W); + proc HuffmanWeightsDecoderInst { // Memory Reader + Input @@ -718,6 +1500,31 @@ proc HuffmanWeightsDecoderInst { type Status = HuffmanWeightsDecoderStatus; type State = HuffmanWeightsDecoderState; + // FSE RAMs + type DpdRamRdReq = ram::ReadReq; + type DpdRamRdResp = ram::ReadResp; + type DpdRamWrReq = ram::WriteReq; + type DpdRamWrResp = ram::WriteResp; + + type TmpRamRdReq = ram::ReadReq; + type TmpRamRdResp = ram::ReadResp; + type TmpRamWrReq = ram::WriteReq; + type TmpRamWrResp = ram::WriteResp; + + type Tmp2RamRdReq = ram::ReadReq; + type Tmp2RamRdResp = ram::ReadResp; + type Tmp2RamWrReq = ram::WriteReq; + type Tmp2RamWrResp = ram::WriteResp; + + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; + type FseRamWrReq = ram::WriteReq; + type FseRamWrResp = ram::WriteResp; + + type MemAxiAr = axi::AxiAr; + type MemAxiR = axi::AxiR; + + config ( req_r: chan in, resp_s: chan out, @@ -725,21 +1532,47 @@ proc HuffmanWeightsDecoderInst { header_mem_rd_resp_r: chan in, raw_weights_mem_rd_req_s: chan out, raw_weights_mem_rd_resp_r: chan in, - fse_weights_mem_rd_req_s: chan out, - fse_weights_mem_rd_resp_r: chan in, + fse_lookup_weights_mem_rd_req_s: chan out, + fse_lookup_weights_mem_rd_resp_r: chan in, + fse_decoder_weights_mem_rd_req_s: chan out, + fse_decoder_weights_mem_rd_resp_r: chan in, weights_ram_wr_req_s: chan out, weights_ram_wr_resp_r: chan in, + dpd_rd_req_s: chan out, + dpd_rd_resp_r: chan in, + dpd_wr_req_s: chan out, + dpd_wr_resp_r: chan in, + tmp_rd_req_s: chan out, + tmp_rd_resp_r: chan in, + tmp_wr_req_s: chan out, + tmp_wr_resp_r: chan in, + tmp2_rd_req_s: chan out, + tmp2_rd_resp_r: chan in, + tmp2_wr_req_s: chan out, + tmp2_wr_resp_r: chan in, + fse_rd_req_s: chan out, + fse_rd_resp_r: chan in, + fse_wr_req_s: chan out, + fse_wr_resp_r: chan in, ) { spawn HuffmanWeightsDecoder< - INST_AXI_ADDR_W, INST_AXI_DATA_W, - INST_WEIGHTS_RAM_ADDR_W, INST_WEIGHTS_RAM_DATA_W, - INST_WEIGHTS_RAM_NUM_PARTITIONS + INST_AXI_ADDR_W, INST_AXI_DATA_W, INST_AXI_ID_W, + INST_WEIGHTS_RAM_ADDR_W, INST_WEIGHTS_RAM_DATA_W, INST_WEIGHTS_RAM_NUM_PARTITIONS, + INST_DPD_RAM_ADDR_W, INST_DPD_RAM_DATA_W, INST_DPD_RAM_NUM_PARTITIONS, + INST_TMP_RAM_ADDR_W, INST_TMP_RAM_DATA_W, INST_TMP_RAM_NUM_PARTITIONS, + INST_TMP2_RAM_ADDR_W, INST_TMP2_RAM_DATA_W, INST_TMP2_RAM_NUM_PARTITIONS, + INST_FSE_RAM_ADDR_W, INST_FSE_RAM_DATA_W, INST_FSE_RAM_NUM_PARTITIONS, > ( req_r, resp_s, header_mem_rd_req_s, header_mem_rd_resp_r, raw_weights_mem_rd_req_s, raw_weights_mem_rd_resp_r, - fse_weights_mem_rd_req_s, fse_weights_mem_rd_resp_r, + fse_lookup_weights_mem_rd_req_s, fse_lookup_weights_mem_rd_resp_r, + fse_decoder_weights_mem_rd_req_s, fse_decoder_weights_mem_rd_resp_r, weights_ram_wr_req_s, weights_ram_wr_resp_r, + dpd_rd_req_s, dpd_rd_resp_r, dpd_wr_req_s, dpd_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s, tmp2_wr_resp_r, + fse_rd_req_s, fse_rd_resp_r, fse_wr_req_s, fse_wr_resp_r, ); } @@ -748,6 +1581,7 @@ proc HuffmanWeightsDecoderInst { next (state: ()) { } } +const TEST_RAM_N = u32:4; const TEST_AXI_ADDR_W = u32:32; const TEST_AXI_DATA_W = u32:64; @@ -772,10 +1606,36 @@ const TEST_WEIGHTS_RAM_SIMULTANEOUS_RW_BEHAVIOR = ram::SimultaneousReadWriteBeha const TEST_WEIGHTS_RAM_INITIALIZED = true; const TEST_WEIGHTS_RAM_ASSERT_VALID_READ = true; -const TEST_INPUT_ADDR = uN[TEST_AXI_ADDR_W]:0x40; +const TEST_DPD_RAM_DATA_W = u32:16; +const TEST_DPD_RAM_SIZE = u32:256; +const TEST_DPD_RAM_ADDR_W = std::clog2(TEST_DPD_RAM_SIZE); +const TEST_DPD_RAM_WORD_PARTITION_SIZE = TEST_DPD_RAM_DATA_W; +const TEST_DPD_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_DPD_RAM_WORD_PARTITION_SIZE, TEST_DPD_RAM_DATA_W); + +const TEST_FSE_RAM_DATA_W = u32:32; +const TEST_FSE_RAM_SIZE = u32:256; +const TEST_FSE_RAM_ADDR_W = std::clog2(TEST_FSE_RAM_SIZE); +const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_W / u32:3; +const TEST_FSE_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_FSE_RAM_WORD_PARTITION_SIZE, TEST_FSE_RAM_DATA_W); +const TEST_FSE_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; + +const TEST_TMP_RAM_DATA_W = u32:16; +const TEST_TMP_RAM_SIZE = u32:256; +const TEST_TMP_RAM_ADDR_W = std::clog2(TEST_TMP_RAM_SIZE); +const TEST_TMP_RAM_WORD_PARTITION_SIZE = TEST_TMP_RAM_DATA_W; +const TEST_TMP_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_TMP_RAM_WORD_PARTITION_SIZE, TEST_TMP_RAM_DATA_W); + +const TEST_TMP2_RAM_DATA_W = u32:8; +const TEST_TMP2_RAM_SIZE = u32:512; +const TEST_TMP2_RAM_ADDR_W = std::clog2(TEST_TMP2_RAM_SIZE); +const TEST_TMP2_RAM_WORD_PARTITION_SIZE = TEST_TMP2_RAM_DATA_W; +const TEST_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_TMP2_RAM_WORD_PARTITION_SIZE, TEST_TMP2_RAM_DATA_W); + +// RAW weights +const TEST_RAW_INPUT_ADDR = uN[TEST_AXI_ADDR_W]:0x40; // Weights sum is 1010, so the last one will be 14 -const TEST_DATA = u8[65]:[ +const TEST_RAW_DATA = u8[65]:[ // len x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF u8:248, u8:0xB__6, u8:0x8__5, u8:0x6__A, u8:0x9__C, u8:0x0__C, u8:0xA__9, u8:0x0__0, u8:0xD__0, // 0x0x u8:0x6__E, u8:0x3__9, u8:0x8__4, u8:0x7__C, u8:0xC__2, u8:0x4__2, u8:0xB__A, u8:0x4__E, // 0x1x @@ -787,7 +1647,263 @@ const TEST_DATA = u8[65]:[ u8:0x3__3, u8:0xA__8, u8:0xE__E, u8:0x4__B, u8:0x0__0, u8:0x0__0, u8:0x0__0, u8:0x0__0, // 0x7x ]; -const TEST_DATA_LAST_WEIGHT = u8:0xA; +const TEST_RAW_DATA_LAST_WEIGHT = u8:0xA; + +// FSE weights +const TEST_FSE_INPUT_ADDR = uN[TEST_AXI_ADDR_W]:0x200; + +// Testcase format - tuple of: +// - array of: header (first byte) + FSE bitstream +// - array of: expected weights RAM contents +const TESTCASES_FSE: (u8[32], u8[128])[7] = [ + ( + u8[32]:[ + u8:10, + u8:0xC0, u8:0x25, u8:0x1D, u8:0x49, u8:0x6E, u8:0xC2, u8:0xFF, u8:0xFF, + u8:0xEE, u8:0x06, u8:0, ... + ], + u8[128]:[ + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x01, u8:0x12, u8:0x34, u8:0x56, u8:0, ... + ], + ), + ( + u8[32]:[ + u8:15, + u8:0xC0, u8:0x25, u8:0x1D, u8:0x9B, u8:0x1E, u8:0xAD, u8:0xFE, u8:0xFF, + u8:0x7F, u8:0x67, u8:0xFE, u8:0xD3, u8:0xFF, u8:0xCE, u8:0x05, u8:0, ... + ], + u8[128]:[ + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x10, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x30, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x50, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x01, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x02, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x04, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x06, u8:0, ... + ], + ), + ( + u8[32]:[ + u8:23, + u8:0x90, u8:0x25, u8:0x49, u8:0x3A, u8:0xEB, u8:0x3B, u8:0xBD, u8:0x7E, + u8:0xD6, u8:0x5D, u8:0x3C, u8:0xB3, u8:0x66, u8:0x77, u8:0xA8, u8:0xBB, + u8:0x25, u8:0x76, u8:0xBA, u8:0xFF, u8:0x20, u8:0xA8, u8:0x01, u8:0, ... + ], + u8[128]:[ + u8:0x00, u8:0x10, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x02, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x30, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x04, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x50, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x06, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x70, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x08, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x01, u8:0x00, + u8:0x00, u8:0x90, u8:0, ... + ], + ), + ( + u8[32]:[ + u8:8, + u8:0xF0, u8:0x39, u8:0xFF, u8:0x23, u8:0x45, u8:0x55, u8:0xCF, u8:0x99, u8:0, ... + ], + u8[128]:[ + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x20, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x01, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x10, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x03, u8:0, ... + ], + ), + ( + u8[32]:[ + u8:24, + u8:0xB0, u8:0xA5, u8:0x92, u8:0x0E, u8:0x14, u8:0x3B, u8:0x7B, u8:0x58, + u8:0xED, u8:0xB0, u8:0x1D, u8:0x9C, u8:0x43, u8:0x82, u8:0xC5, u8:0x8E, + u8:0xD3, u8:0x38, u8:0x36, u8:0x87, u8:0x73, u8:0x08, u8:0x58, u8:0x02, u8:0, ... + ], + u8[128]:[ + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x20, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x10, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x10, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x07, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x10, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x05, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x10, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x02, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x01, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x01, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x80, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x01, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x60, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x01, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, u8:0x30, u8:0, ... + ], + ), + ( + u8[32]:[ + u8:9, + u8:0xE0, u8:0xE9, u8:0x40, u8:0x0D, u8:0x80, u8:0x0A, u8:0x10, u8:0x59, + u8:0x04, u8:0, ... + ], + u8[128]:[ + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x03, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x20, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x01, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, u8:0x10, u8:0, ... + ], + ), + ( + u8[32]:[ + u8:9, + u8:0xF0, u8:0x19, u8:0x03, u8:0x23, u8:0x7D, u8:0x9F, u8:0xD7, u8:0xB5, + u8:0x06, u8:0, ... + ], + u8[128]:[ + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x10, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x30, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x01, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x02, u8:0, ... + ], + ), +]; #[test_proc] proc HuffmanWeightsDecoder_test { @@ -817,13 +1933,37 @@ proc HuffmanWeightsDecoder_test { type Status = HuffmanWeightsDecoderStatus; type State = HuffmanWeightsDecoderState; + // FSE RAMs + type DpdRamRdReq = ram::ReadReq; + type DpdRamRdResp = ram::ReadResp; + type DpdRamWrReq = ram::WriteReq; + type DpdRamWrResp = ram::WriteResp; + + type TmpRamRdReq = ram::ReadReq; + type TmpRamRdResp = ram::ReadResp; + type TmpRamWrReq = ram::WriteReq; + type TmpRamWrResp = ram::WriteResp; + + type Tmp2RamRdReq = ram::ReadReq; + type Tmp2RamRdResp = ram::ReadResp; + type Tmp2RamWrReq = ram::WriteReq; + type Tmp2RamWrResp = ram::WriteResp; + + type FseRamRdReq = ram::ReadReq; + type FseRamRdResp = ram::ReadResp; + type FseRamWrReq = ram::WriteReq; + type FseRamWrResp = ram::WriteResp; + + type MemAxiAr = axi::AxiAr; + type MemAxiR = axi::AxiR; + terminator: chan out; req_s: chan out; resp_r: chan in; - input_ram_wr_req_s: chan[3] out; - input_ram_wr_resp_r: chan[3] in; + input_ram_wr_req_s: chan[TEST_RAM_N] out; + input_ram_wr_resp_r: chan[TEST_RAM_N] in; weights_ram_rd_req_s: chan out; weights_ram_rd_resp_r: chan in; @@ -832,12 +1972,12 @@ proc HuffmanWeightsDecoder_test { // Input Memory - let (input_ram_rd_req_s, input_ram_rd_req_r) = chan[3]("input_ram_rd_req"); - let (input_ram_rd_resp_s, input_ram_rd_resp_r) = chan[3]("input_ram_rd_resp"); - let (input_ram_wr_req_s, input_ram_wr_req_r) = chan[3]("input_ram_wr_req"); - let (input_ram_wr_resp_s, input_ram_wr_resp_r) = chan[3]("input_ram_wr_resp"); + let (input_ram_rd_req_s, input_ram_rd_req_r) = chan[TEST_RAM_N]("input_ram_rd_req"); + let (input_ram_rd_resp_s, input_ram_rd_resp_r) = chan[TEST_RAM_N]("input_ram_rd_resp"); + let (input_ram_wr_req_s, input_ram_wr_req_r) = chan[TEST_RAM_N]("input_ram_wr_req"); + let (input_ram_wr_resp_s, input_ram_wr_resp_r) = chan[TEST_RAM_N]("input_ram_wr_resp"); - unroll_for! (i, _) in range(u32:0, u32:3) { + unroll_for! (i, _) in range(u32:0, TEST_RAM_N) { spawn ram::RamModel< TEST_RAM_DATA_W, TEST_RAM_SIZE, TEST_RAM_PARTITION_SIZE, TEST_RAM_SIMULTANEOUS_RW_BEHAVIOR, TEST_RAM_INITIALIZED, @@ -850,10 +1990,10 @@ proc HuffmanWeightsDecoder_test { // Input Memory Axi Reader - let (axi_ar_s, axi_ar_r) = chan[3]("axi_ar"); - let (axi_r_s, axi_r_r) = chan[3]("axi_r"); + let (axi_ar_s, axi_ar_r) = chan[TEST_RAM_N]("axi_ar"); + let (axi_r_s, axi_r_r) = chan[TEST_RAM_N]("axi_r"); - unroll_for! (i, _) in range(u32:0, u32:3) { + unroll_for! (i, _) in range(u32:0, TEST_RAM_N) { spawn axi_ram::AxiRamReader< TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, @@ -866,10 +2006,10 @@ proc HuffmanWeightsDecoder_test { // Input Memory Reader - let (mem_rd_req_s, mem_rd_req_r) = chan[3]("mem_rd_req"); - let (mem_rd_resp_s, mem_rd_resp_r) = chan[3]("mem_rd_resp"); + let (mem_rd_req_s, mem_rd_req_r) = chan[TEST_RAM_N]("mem_rd_req"); + let (mem_rd_resp_s, mem_rd_resp_r) = chan[TEST_RAM_N]("mem_rd_resp"); - unroll_for! (i, _) in range(u32:0, u32:3) { + unroll_for! (i, _) in range(u32:0, TEST_RAM_N) { spawn mem_reader::MemReader< TEST_AXI_DATA_W, TEST_AXI_ADDR_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, >( @@ -899,16 +2039,69 @@ proc HuffmanWeightsDecoder_test { let (req_s, req_r) = chan("req"); let (resp_s, resp_r) = chan("resp"); + // FSE RAMs + let (dpd_rd_req_s, dpd_rd_req_r) = chan("dpd_rd_req"); + let (dpd_rd_resp_s, dpd_rd_resp_r) = chan("dpd_rd_resp"); + let (dpd_wr_req_s, dpd_wr_req_r) = chan("dpd_wr_req"); + let (dpd_wr_resp_s, dpd_wr_resp_r) = chan("dpd_wr_resp"); + + spawn ram::RamModel< + TEST_DPD_RAM_DATA_W, + TEST_DPD_RAM_SIZE, + TEST_DPD_RAM_WORD_PARTITION_SIZE + >(dpd_rd_req_r, dpd_rd_resp_s, dpd_wr_req_r, dpd_wr_resp_s); + + let (tmp_rd_req_s, tmp_rd_req_r) = chan("tmp_rd_req"); + let (tmp_rd_resp_s, tmp_rd_resp_r) = chan("tmp_rd_resp"); + let (tmp_wr_req_s, tmp_wr_req_r) = chan("tmp_wr_req"); + let (tmp_wr_resp_s, tmp_wr_resp_r) = chan("tmp_wr_resp"); + + spawn ram::RamModel< + TEST_TMP_RAM_DATA_W, + TEST_TMP_RAM_SIZE, + TEST_TMP_RAM_WORD_PARTITION_SIZE + >(tmp_rd_req_r, tmp_rd_resp_s, tmp_wr_req_r, tmp_wr_resp_s); + + let (tmp2_rd_req_s, tmp2_rd_req_r) = chan("tmp2_rd_req"); + let (tmp2_rd_resp_s, tmp2_rd_resp_r) = chan("tmp2_rd_resp"); + let (tmp2_wr_req_s, tmp2_wr_req_r) = chan("tmp2_wr_req"); + let (tmp2_wr_resp_s, tmp2_wr_resp_r) = chan("tmp2_wr_resp"); + + spawn ram::RamModel< + TEST_TMP2_RAM_DATA_W, + TEST_TMP2_RAM_SIZE, + TEST_TMP2_RAM_WORD_PARTITION_SIZE + >(tmp2_rd_req_r, tmp2_rd_resp_s, tmp2_wr_req_r, tmp2_wr_resp_s); + + let (fse_rd_req_s, fse_rd_req_r) = chan("fse_rd_req"); + let (fse_rd_resp_s, fse_rd_resp_r) = chan("fse_rd_resp"); + let (fse_wr_req_s, fse_wr_req_r) = chan("fse_wr_req"); + let (fse_wr_resp_s, fse_wr_resp_r) = chan("fse_wr_resp"); + + spawn ram::RamModel< + TEST_FSE_RAM_DATA_W, + TEST_FSE_RAM_SIZE, + TEST_FSE_RAM_WORD_PARTITION_SIZE + >(fse_rd_req_r, fse_rd_resp_s, fse_wr_req_r, fse_wr_resp_s); + spawn HuffmanWeightsDecoder< - TEST_AXI_ADDR_W, TEST_AXI_DATA_W, - TEST_WEIGHTS_RAM_ADDR_W, TEST_WEIGHTS_RAM_DATA_W, - TEST_WEIGHTS_RAM_NUM_PARTITIONS + TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_ID_W, + TEST_WEIGHTS_RAM_ADDR_W, TEST_WEIGHTS_RAM_DATA_W, TEST_WEIGHTS_RAM_NUM_PARTITIONS, + TEST_DPD_RAM_ADDR_W, TEST_DPD_RAM_DATA_W, TEST_DPD_RAM_NUM_PARTITIONS, + TEST_TMP_RAM_ADDR_W, TEST_TMP_RAM_DATA_W, TEST_TMP_RAM_NUM_PARTITIONS, + TEST_TMP2_RAM_ADDR_W, TEST_TMP2_RAM_DATA_W, TEST_TMP2_RAM_NUM_PARTITIONS, + TEST_FSE_RAM_ADDR_W, TEST_FSE_RAM_DATA_W, TEST_FSE_RAM_NUM_PARTITIONS, > ( req_r, resp_s, mem_rd_req_s[0], mem_rd_resp_r[0], mem_rd_req_s[1], mem_rd_resp_r[1], mem_rd_req_s[2], mem_rd_resp_r[2], + mem_rd_req_s[3], mem_rd_resp_r[3], weights_ram_wr_req_s, weights_ram_wr_resp_r, + dpd_rd_req_s, dpd_rd_resp_r, dpd_wr_req_s, dpd_wr_resp_r, + tmp_rd_req_s, tmp_rd_resp_r, tmp_wr_req_s, tmp_wr_resp_r, + tmp2_rd_req_s, tmp2_rd_resp_r, tmp2_wr_req_s, tmp2_wr_resp_r, + fse_rd_req_s, fse_rd_resp_r, fse_wr_req_s, fse_wr_resp_r, ); ( @@ -926,29 +2119,30 @@ proc HuffmanWeightsDecoder_test { let tok = join(); + // RAW weights + // Fill input RAM - for (i, tok) in range(u32:0, (array_size(TEST_DATA) + TEST_DATA_PER_RAM_WRITE - u32:1) / TEST_DATA_PER_RAM_WRITE) { + for (i, tok) in range(u32:0, (array_size(TEST_RAW_DATA) + TEST_DATA_PER_RAM_WRITE - u32:1) / TEST_DATA_PER_RAM_WRITE) { let ram_data = for (j, ram_data) in range(u32:0, TEST_DATA_PER_RAM_WRITE) { let data_idx = i * TEST_DATA_PER_RAM_WRITE + j; - if (data_idx < array_size(TEST_DATA)) { - ram_data | ((TEST_DATA[data_idx] as uN[TEST_RAM_DATA_W]) << (u32:8 * j)) + if (data_idx < array_size(TEST_RAW_DATA)) { + ram_data | ((TEST_RAW_DATA[data_idx] as uN[TEST_RAM_DATA_W]) << (u32:8 * j)) } else { ram_data } }(uN[TEST_RAM_DATA_W]:0); let input_ram_wr_req = InputBufferRamWrReq { - addr: (TEST_INPUT_ADDR / u32:8) + i as uN[TEST_RAM_ADDR_W], + addr: (TEST_RAW_INPUT_ADDR / u32:8) + i as uN[TEST_RAM_ADDR_W], data: ram_data, mask: !uN[TEST_RAM_NUM_PARTITIONS]:0, }; - let tok = send(tok, input_ram_wr_req_s[0], input_ram_wr_req); - let (tok, _) = recv(tok, input_ram_wr_resp_r[0]); - let tok = send(tok, input_ram_wr_req_s[1], input_ram_wr_req); - let (tok, _) = recv(tok, input_ram_wr_resp_r[1]); - let tok = send(tok, input_ram_wr_req_s[2], input_ram_wr_req); - let (tok, _) = recv(tok, input_ram_wr_resp_r[2]); + let tok = unroll_for! (i, tok) in range(u32:0, TEST_RAM_N) { + let tok = send(tok, input_ram_wr_req_s[i], input_ram_wr_req); + let (tok, _) = recv(tok, input_ram_wr_resp_r[i]); + tok + }(tok); trace_fmt!("[TEST] Sent RAM write request to input RAMs {:#x}", input_ram_wr_req); @@ -957,7 +2151,7 @@ proc HuffmanWeightsDecoder_test { // Send decoding request let req = Req { - addr: TEST_INPUT_ADDR, + addr: TEST_RAW_INPUT_ADDR, }; let tok = send(tok, req_s, req); trace_fmt!("[TEST] Sent request {:#x}", req); @@ -966,15 +2160,15 @@ proc HuffmanWeightsDecoder_test { let (tok, resp) = recv(tok, resp_r); trace_fmt!("[TEST] Received respose {:#x}", resp); assert_eq(HuffmanWeightsDecoderStatus::OKAY, resp.status); - assert_eq((((TEST_DATA[0] - u8:127 + u8:1) >> u32:1) + u8:1) as uN[TEST_AXI_ADDR_W], resp.tree_description_size); + assert_eq((((TEST_RAW_DATA[0] - u8:127) >> u32:1) + u8:2) as uN[TEST_AXI_ADDR_W], resp.tree_description_size); // Insert last weight in test data - let last_weight_idx = ((TEST_DATA[0] as u32 - u32:127) / u32:2) + u32:1; + let last_weight_idx = ((TEST_RAW_DATA[0] as u32 - u32:127) / u32:2) + u32:1; let last_weight_entry = ( - TEST_DATA[last_weight_idx] | - (TEST_DATA_LAST_WEIGHT << (u32:4 * (u32:1 - ((TEST_DATA[0] - u8:127) as u1 as u32)))) + TEST_RAW_DATA[last_weight_idx] | + (TEST_RAW_DATA_LAST_WEIGHT << (u32:4 * (u32:1 - ((TEST_RAW_DATA[0] - u8:127) as u1 as u32)))) ); - let test_data = update(TEST_DATA, last_weight_idx, last_weight_entry); + let test_data = update(TEST_RAW_DATA, last_weight_idx, last_weight_entry); // Check output RAM let tok = for (i, tok) in range(u32:0, u32:32) { @@ -995,13 +2189,82 @@ proc HuffmanWeightsDecoder_test { }; let tok = send(tok, weights_ram_rd_req_s, weights_ram_rd_req); let (tok, weights_ram_rd_resp) = recv(tok, weights_ram_rd_resp_r); - trace_fmt!("[TEST] Weights RAM content - addr: {:#x} data: {:#x}", i, weights_ram_rd_resp.data); + trace_fmt!("[TEST] Weights RAM content - addr: {:#x} data: expected {:#x}, got {:#x}", i, expected_value, weights_ram_rd_resp.data); assert_eq(expected_value, weights_ram_rd_resp.data); tok }(tok); + + // FSE-encoded weights + unroll_for! (i, tok) in range(u32:0, array_size(TESTCASES_FSE)) { + let (TEST_FSE_DATA, TEST_FSE_WEIGHTS) = TESTCASES_FSE[i]; + // Fill input RAM + for (i, tok) in range(u32:0, (array_size(TEST_FSE_DATA) + TEST_DATA_PER_RAM_WRITE - u32:1) / TEST_DATA_PER_RAM_WRITE) { + let ram_data = for (j, ram_data) in range(u32:0, TEST_DATA_PER_RAM_WRITE) { + let data_idx = i * TEST_DATA_PER_RAM_WRITE + j; + if (data_idx < array_size(TEST_FSE_DATA)) { + ram_data | ((TEST_FSE_DATA[data_idx] as uN[TEST_RAM_DATA_W]) << (u32:8 * j)) + } else { + ram_data + } + }(uN[TEST_RAM_DATA_W]:0); + + let input_ram_wr_req = InputBufferRamWrReq { + addr: (TEST_FSE_INPUT_ADDR / u32:8) + i as uN[TEST_RAM_ADDR_W], + data: ram_data, + mask: !uN[TEST_RAM_NUM_PARTITIONS]:0, + }; + + let tok = unroll_for! (i, tok) in range(u32:0, TEST_RAM_N) { + let tok = send(tok, input_ram_wr_req_s[i], input_ram_wr_req); + let (tok, _) = recv(tok, input_ram_wr_resp_r[i]); + tok + }(tok); + + trace_fmt!("[TEST] Sent RAM write request to input RAMs {:#x}", input_ram_wr_req); + + tok + }(tok); + + // Send decoding request + let req = Req { + addr: TEST_FSE_INPUT_ADDR, + }; + let tok = send(tok, req_s, req); + trace_fmt!("[TEST] Sent request {:#x}", req); + + // Receive response + let (tok, resp) = recv(tok, resp_r); + trace_fmt!("[TEST] Received respose {:#x}", resp); + assert_eq(HuffmanWeightsDecoderStatus::OKAY, resp.status); + assert_eq((TEST_FSE_DATA[0] + u8:1) as uN[TEST_AXI_ADDR_W], resp.tree_description_size); + + // Check output RAM + let tok = for (i, tok) in range(u32:0, u32:32) { + let expected_value = ( + TEST_FSE_WEIGHTS[4*i + u32:0] ++ + TEST_FSE_WEIGHTS[4*i + u32:1] ++ + TEST_FSE_WEIGHTS[4*i + u32:2] ++ + TEST_FSE_WEIGHTS[4*i + u32:3] + ); + + let weights_ram_rd_req = WeightsRamRdReq { + addr: i as uN[TEST_WEIGHTS_RAM_ADDR_W], + mask: !uN[TEST_WEIGHTS_RAM_NUM_PARTITIONS]:0, + }; + let tok = send(tok, weights_ram_rd_req_s, weights_ram_rd_req); + let (tok, weights_ram_rd_resp) = recv(tok, weights_ram_rd_resp_r); + trace_fmt!("[TEST] Weights RAM content - addr: {:#x} data: expected {:#x}, got {:#x}", i, expected_value, weights_ram_rd_resp.data); + + assert_eq(expected_value, weights_ram_rd_resp.data); + + tok + }(tok); + tok + }(tok); + send(tok, terminator, true); } } diff --git a/xls/modules/zstd/refilling_shift_buffer.x b/xls/modules/zstd/refilling_shift_buffer.x index 7e6c8bcac2..ced9665439 100644 --- a/xls/modules/zstd/refilling_shift_buffer.x +++ b/xls/modules/zstd/refilling_shift_buffer.x @@ -142,9 +142,9 @@ proc RefillingShiftBufferInternal< let flush_amount_bits = std::min(DATA_W as BufferSize, state.bits_to_flush); // send "flushing done" notification once we complete it send_if(tok, flushing_done_s, flushing && flushing_end, ()); - // if (flushing && flushing_end) { - // trace_fmt!("Sent done on the flushing done channel"); - // } else {}; + if (flushing && flushing_end) { + trace_fmt!("Sent done on the flushing done channel"); + } else {}; // snooping logic for the ShiftBuffer control channel // recv and immediately send out control packets heading for ShiftBuffer, @@ -161,9 +161,9 @@ proc RefillingShiftBufferInternal< }; let do_send_ctrl = (flushing && flush_amount_bits > BufferSize:0) || snoop_ctrl_valid; send_if(tok, snoop_ctrl_s, do_send_ctrl, ctrl_packet); - // if do_send_ctrl { - // trace_fmt!("Sent snooped/injected control packet: {:#x}", ctrl_packet); - // } else {}; + if do_send_ctrl { + trace_fmt!("Sent snooped/injected control packet: {:#x}", ctrl_packet); + } else {}; // snoop data output packet (for keeping track how many bits in ShiftBuffer are occupied) let (_, snoop_data, snoop_data_valid) = recv_non_blocking(tok, snoop_data_out_r, zero!()); @@ -184,14 +184,14 @@ proc RefillingShiftBufferInternal< length: REFILL_SIZE, }; send_if(tok, reader_req_s, do_refill_cycle, mem_req); - // if (do_refill_cycle) { - // trace_fmt!("[{:#x}] Sent request for data to memory: {:#x}", INSTANCE, mem_req); - // } else {}; + if (do_refill_cycle) { + trace_fmt!("[{:#x}] Sent request for data to memory: {:#x}", INSTANCE, mem_req); + } else {}; // receive data from memory let (_, reader_resp, reader_resp_valid) = recv_non_blocking(tok, reader_resp_r, zero!()); - // if reader_resp_valid { - // trace_fmt!("[{:#x}] Received data from memory: {:#x}", INSTANCE, reader_resp); - // } else {}; + if reader_resp_valid { + trace_fmt!("[{:#x}] Received data from memory: {:#x}", INSTANCE, reader_resp); + } else {}; // always send some data regardless of the reader_resp.status to allow for all requests // to complete (possibly with invalid data) since the response channel queue must be empty for // flushing to work correctly @@ -205,9 +205,9 @@ proc RefillingShiftBufferInternal< // ShiftBuffer fast enough, apart from that since part of the condition `do_buffer_refill` // is `buf_will_have_enough_space` it should not block send_if(tok, buffer_data_in_s, do_buffer_refill, data_packet); - // if (do_buffer_refill) { - // trace_fmt!("Sent data to the ShiftBuffer: {:#x}", data_packet); - // } else {}; + if (do_buffer_refill) { + trace_fmt!("Sent data to the ShiftBuffer: {:#x}", data_packet); + } else {}; // length of additional data that will be inserted into the ShiftBuffer *in the future* // once all pending memory requests are served @@ -272,9 +272,9 @@ proc RefillingShiftBufferInternal< length: snoop_data.length, error: axi_error && reads_error_bits, }); - // if forward_snooped_data { - // trace_fmt!("[{:#x}] Forwarded snooped data output packet: {:#x}", INSTANCE, snoop_data); - // } else {}; + if forward_snooped_data { + trace_fmt!("[{:#x}] Forwarded snooped data output packet: {:#x}", INSTANCE, snoop_data); + } else {}; // FSM let next_state = match (state.fsm) { From f50d268a43d81e533462e52c109810fb412906f5 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Fri, 24 Jan 2025 17:07:50 +0100 Subject: [PATCH 62/85] Count the length of the section with probability frequencies Signed-off-by: Robert Winkler --- xls/modules/zstd/fse_proba_freq_dec.x | 40 ++++++++++++++++++++++++-- xls/modules/zstd/huffman_weights_dec.x | 5 ++-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/xls/modules/zstd/fse_proba_freq_dec.x b/xls/modules/zstd/fse_proba_freq_dec.x index 5daf857aef..fa75571494 100644 --- a/xls/modules/zstd/fse_proba_freq_dec.x +++ b/xls/modules/zstd/fse_proba_freq_dec.x @@ -49,11 +49,20 @@ pub enum FseProbaFreqDecoderStatus: u1 { ERROR = 1, } +const MAX_CONSUMED_FSE_BITS = FSE_MAX_SYMBOLS * FSE_MAX_ACCURACY_LOG; +const CONSUMED_FSE_BITS_WIDTH = std::clog2(MAX_CONSUMED_FSE_BITS); +pub type ConsumedFseBits = uN[CONSUMED_FSE_BITS_WIDTH]; + +const MAX_CONSUMED_FSE_BYTES = MAX_CONSUMED_FSE_BITS / u32:8; +const CONSUMED_FSE_BYTES_WIDTH = std::clog2(MAX_CONSUMED_FSE_BYTES); +pub type ConsumedFseBytes = uN[CONSUMED_FSE_BYTES_WIDTH]; + pub struct FseProbaFreqDecoderReq {} pub struct FseProbaFreqDecoderResp { status: FseProbaFreqDecoderStatus, accuracy_log: AccuracyLog, symbol_count: SymbolCount, + consumed_bytes: ConsumedFseBytes, } enum Fsm : u4 { @@ -89,7 +98,9 @@ struct State { // using up more probability points than were available data_invalid: bool, // number of bits read from RefillingShiftBuffer modulo 8 - read_bits_mod8: u3 + read_bits_mod8: u3, + // number of bits requested from the ShiftBuffer + read_bits: ConsumedFseBits, } // Adapter for input data, converting the data to a shift buffer input type @@ -254,6 +265,8 @@ pub proc FseProbaFreqDecoder< }; let (tok1_1, out_data) = recv_if(tok0, buff_out_data_r, do_buff_data_recv, zero!()); let data_invalid = state.data_invalid || out_data.error; + + let read_bits = state.read_bits + out_data.length as ConsumedFseBits; let read_bits_mod8 = state.read_bits_mod8 + out_data.length as u3; let (tok1_2, written_symbol_count, written_symb_count_valid) = @@ -292,6 +305,7 @@ pub proc FseProbaFreqDecoder< remaining_proba, written_symbol_count, data_invalid, + read_bits, read_bits_mod8, ..state }, @@ -303,7 +317,12 @@ pub proc FseProbaFreqDecoder< (true, BufferCtrl { length: bit_width as Length }), (false, zero!()), (false, zero!()), - State { fsm: Fsm::RECV_SYMBOL, written_symbol_count, ..state }, + State { + fsm: Fsm::RECV_SYMBOL, + written_symbol_count, + read_bits, + read_bits_mod8, + ..state }, ) }, Fsm::RECV_SYMBOL => { @@ -351,6 +370,7 @@ pub proc FseProbaFreqDecoder< remaining_proba, remainder, data_invalid, + read_bits, read_bits_mod8, ..state }, @@ -372,6 +392,7 @@ pub proc FseProbaFreqDecoder< symbol_count, remaining_proba, remainder, + read_bits, read_bits_mod8, ..state }, @@ -393,6 +414,7 @@ pub proc FseProbaFreqDecoder< symbol_count, remaining_proba, remainder, + read_bits, read_bits_mod8, ..state } @@ -432,6 +454,7 @@ pub proc FseProbaFreqDecoder< remainder: zero!(), written_symbol_count, data_invalid, + read_bits, read_bits_mod8, ..state }, @@ -450,6 +473,7 @@ pub proc FseProbaFreqDecoder< zero_proba_count, next_recv_zero, data_invalid, + read_bits, read_bits_mod8, ..state }, @@ -477,6 +501,8 @@ pub proc FseProbaFreqDecoder< written_symbol_count, zero_proba_count, symbol_count, + read_bits, + read_bits_mod8, ..state }, ) @@ -491,6 +517,8 @@ pub proc FseProbaFreqDecoder< zero_proba_count: SymbolCount:0, written_symbol_count, symbol_count, + read_bits, + read_bits_mod8, ..state }, ) @@ -504,6 +532,8 @@ pub proc FseProbaFreqDecoder< zero_proba_count, symbol_count, written_symbol_count, + read_bits, + read_bits_mod8, ..state }, ) @@ -521,6 +551,8 @@ pub proc FseProbaFreqDecoder< (false, zero!()), State { fsm: Fsm::CONSUME_PADDING, + read_bits, + read_bits_mod8, ..state } ) @@ -543,6 +575,7 @@ pub proc FseProbaFreqDecoder< status: if state.data_invalid { Status::ERROR } else { Status::OK }, accuracy_log: state.accuracy_log, symbol_count: state.symbol_count, + consumed_bytes: checked_cast(read_bits >> 3), }), zero!() ) @@ -710,6 +743,7 @@ proc FseProbaFreqDecoderTest { status: FseProbaFreqDecoderStatus::OK, accuracy_log: AccuracyLog:8, symbol_count: SymbolCount:12, + consumed_bytes: ConsumedFseBytes:6, }); // check that the proc consumed the padding by sending request @@ -762,6 +796,7 @@ proc FseProbaFreqDecoderTest { status: FseProbaFreqDecoderStatus::OK, accuracy_log: AccuracyLog:9, symbol_count: SymbolCount:2, + consumed_bytes: ConsumedFseBytes:2, }); for ((i, exp_val), tok): ((u32, RamData), token) in enumerate(EXPECTED_RAM_CONTENTS) { @@ -798,6 +833,7 @@ proc FseProbaFreqDecoderTest { status: FseProbaFreqDecoderStatus::OK, accuracy_log: AccuracyLog:9, symbol_count: SymbolCount:2, + consumed_bytes: ConsumedFseBytes:2, }); for ((i, exp_val), tok): ((u32, RamData), token) in enumerate(EXPECTED_RAM_CONTENTS) { diff --git a/xls/modules/zstd/huffman_weights_dec.x b/xls/modules/zstd/huffman_weights_dec.x index bf71a615dc..6c7b3aed36 100644 --- a/xls/modules/zstd/huffman_weights_dec.x +++ b/xls/modules/zstd/huffman_weights_dec.x @@ -1059,8 +1059,7 @@ proc HuffmanFseWeightsDecoder< let tok = send(tok, fld_rsb_start_req_s, fld_rsb_start_req); trace_fmt!("[FSE] Sent refilling shift buffer start request {:#x}", fld_rsb_start_req); - let fld_req = FseLookupDecoderReq { - }; + let fld_req = FseLookupDecoderReq {}; let tok = send(tok, fld_req_s, fld_req); trace_fmt!("[FSE] Sent FSE lookup decoding request {:#x}", fld_req); @@ -1081,7 +1080,7 @@ proc HuffmanFseWeightsDecoder< trace_fmt!("[FSE] Sent refilling shift buffer start request {:#x}", fd_rsb_start_req); let fd_ctrl = HuffmanFseDecoderCtrl { - length: req.length - u8:2, // FIXME: replace hard-coded value with FSE header size + length: req.length - checked_cast(fld_resp.consumed_bytes), acc_log: fld_resp.accuracy_log, }; let tok = send(tok, fd_ctrl_s, fd_ctrl); From cce77a5b70186a94637c53252aaea1a453a30d29 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Fri, 24 Jan 2025 17:19:50 +0100 Subject: [PATCH 63/85] Adjust the design to the changed HuffmanWeightsDecoder Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 3 +- xls/modules/zstd/comp_block_dec.x | 719 ++++++++++++++-------- xls/modules/zstd/comp_frame_huffman_fse.x | 21 + xls/modules/zstd/comp_lookup_dec.x | 55 +- xls/modules/zstd/fse_lookup_dec.x | 23 +- xls/modules/zstd/huffman_literals_dec.x | 98 ++- xls/modules/zstd/huffman_prescan.x | 15 +- xls/modules/zstd/huffman_weights_dec.x | 403 ++++++------ xls/modules/zstd/literals_decoder.x | 413 ++++++++++++- xls/modules/zstd/zstd_dec.x | 189 ++++-- xls/modules/zstd/zstd_dec_test.x | 199 ++++-- 11 files changed, 1520 insertions(+), 618 deletions(-) create mode 100644 xls/modules/zstd/comp_frame_huffman_fse.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 77e7024911..747d0d1deb 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1286,6 +1286,7 @@ xls_dslx_test( "zstd_frame_testcases.x", "comp_frame.x", "comp_frame_huffman.x", + "comp_frame_huffman_fse.x", "comp_frame_fse_comp.x", "comp_frame_fse_repeated.x", ], @@ -3052,7 +3053,7 @@ xls_dslx_library( deps = [ "//xls/modules/zstd:huffman_prescan_dslx", "//xls/modules/zstd:refilling_shift_buffer_dslx", - "//xls/modules/zstd:fse_lookup_dec_dslx", + "//xls/modules/zstd:comp_lookup_dec_dslx", "//xls/modules/zstd:fse_table_creator_dslx", "//xls/modules/zstd:math_dslx", "//xls/modules/zstd/memory:mem_reader_dslx", diff --git a/xls/modules/zstd/comp_block_dec.x b/xls/modules/zstd/comp_block_dec.x index 276a54cd03..0cf51ff320 100644 --- a/xls/modules/zstd/comp_block_dec.x +++ b/xls/modules/zstd/comp_block_dec.x @@ -55,13 +55,18 @@ pub proc CompressBlockDecoder< // AXI parameters AXI_DATA_W: u32, AXI_ADDR_W: u32, AXI_ID_W: u32, AXI_DEST_W: u32, - // FSE lookup table RAMs - DPD_RAM_ADDR_W: u32, DPD_RAM_DATA_W: u32, DPD_RAM_NUM_PARTITIONS: u32, - TMP_RAM_ADDR_W: u32, TMP_RAM_DATA_W: u32, TMP_RAM_NUM_PARTITIONS: u32, - TMP2_RAM_ADDR_W: u32, TMP2_RAM_DATA_W: u32, TMP2_RAM_NUM_PARTITIONS: u32, - FSE_RAM_ADDR_W: u32, FSE_RAM_DATA_W: u32, FSE_RAM_NUM_PARTITIONS: u32, + // FSE lookup table RAMs for sequences + SEQ_DPD_RAM_ADDR_W: u32, SEQ_DPD_RAM_DATA_W: u32, SEQ_DPD_RAM_NUM_PARTITIONS: u32, + SEQ_TMP_RAM_ADDR_W: u32, SEQ_TMP_RAM_DATA_W: u32, SEQ_TMP_RAM_NUM_PARTITIONS: u32, + SEQ_TMP2_RAM_ADDR_W: u32, SEQ_TMP2_RAM_DATA_W: u32, SEQ_TMP2_RAM_NUM_PARTITIONS: u32, + SEQ_FSE_RAM_ADDR_W: u32, SEQ_FSE_RAM_DATA_W: u32, SEQ_FSE_RAM_NUM_PARTITIONS: u32, // for literals decoder + HUFFMAN_WEIGHTS_DPD_RAM_ADDR_W: u32, HUFFMAN_WEIGHTS_DPD_RAM_DATA_W: u32, HUFFMAN_WEIGHTS_DPD_RAM_NUM_PARTITIONS: u32, + HUFFMAN_WEIGHTS_TMP_RAM_ADDR_W: u32, HUFFMAN_WEIGHTS_TMP_RAM_DATA_W: u32, HUFFMAN_WEIGHTS_TMP_RAM_NUM_PARTITIONS: u32, + HUFFMAN_WEIGHTS_TMP2_RAM_ADDR_W: u32, HUFFMAN_WEIGHTS_TMP2_RAM_DATA_W: u32, HUFFMAN_WEIGHTS_TMP2_RAM_NUM_PARTITIONS: u32, + HUFFMAN_WEIGHTS_FSE_RAM_ADDR_W: u32, HUFFMAN_WEIGHTS_FSE_RAM_DATA_W: u32, HUFFMAN_WEIGHTS_FSE_RAM_NUM_PARTITIONS: u32, + HISTORY_BUFFER_SIZE_KB: u32 = {common::HISTORY_BUFFER_SIZE_KB}, // FSE proba @@ -72,16 +77,16 @@ pub proc CompressBlockDecoder< AXI_DATA_W_DIV8: u32 = {AXI_DATA_W / u32:8}, // Huffman weights memory parameters - HUFFMAN_WEIGHTS_RAM_ADDR_WIDTH: u32 = {huffman_literals_dec::WEIGHTS_ADDR_WIDTH}, - HUFFMAN_WEIGHTS_RAM_DATA_WIDTH: u32 = {huffman_literals_dec::WEIGHTS_DATA_WIDTH}, + HUFFMAN_WEIGHTS_RAM_ADDR_W: u32 = {huffman_literals_dec::WEIGHTS_ADDR_WIDTH}, + HUFFMAN_WEIGHTS_RAM_DATA_W: u32 = {huffman_literals_dec::WEIGHTS_DATA_WIDTH}, HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS: u32 = {huffman_literals_dec::WEIGHTS_NUM_PARTITIONS}, // Huffman prescan memory parameters - HUFFMAN_PRESCAN_RAM_ADDR_WIDTH: u32 = {huffman_literals_dec::PRESCAN_ADDR_WIDTH}, - HUFFMAN_PRESCAN_RAM_DATA_WIDTH: u32 = {huffman_literals_dec::PRESCAN_DATA_WIDTH}, + HUFFMAN_PRESCAN_RAM_ADDR_W: u32 = {huffman_literals_dec::PRESCAN_ADDR_WIDTH}, + HUFFMAN_PRESCAN_RAM_DATA_W: u32 = {huffman_literals_dec::PRESCAN_DATA_WIDTH}, HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS: u32 = {huffman_literals_dec::PRESCAN_NUM_PARTITIONS}, // Literals buffer memory parameters - LITERALS_BUFFER_RAM_ADDR_WIDTH: u32 = {parallel_rams::ram_addr_width(HISTORY_BUFFER_SIZE_KB)}, - LITERALS_BUFFER_RAM_DATA_WIDTH: u32 = {literals_buffer::RAM_DATA_WIDTH}, + LITERALS_BUFFER_RAM_ADDR_W: u32 = {parallel_rams::ram_addr_width(HISTORY_BUFFER_SIZE_KB)}, + LITERALS_BUFFER_RAM_DATA_W: u32 = {literals_buffer::RAM_DATA_WIDTH}, LITERALS_BUFFER_RAM_NUM_PARTITIONS: u32 = {literals_buffer::RAM_NUM_PARTITIONS}, > { type Req = CompressBlockDecoderReq; @@ -99,25 +104,45 @@ pub proc CompressBlockDecoder< type MemAxiW = axi::AxiW; type MemAxiB = axi::AxiB; - type DpdRamRdReq = ram::ReadReq; - type DpdRamRdResp = ram::ReadResp; - type DpdRamWrReq = ram::WriteReq; - type DpdRamWrResp = ram::WriteResp; - - type TmpRamRdReq = ram::ReadReq; - type TmpRamRdResp = ram::ReadResp; - type TmpRamWrReq = ram::WriteReq; - type TmpRamWrResp = ram::WriteResp; - - type Tmp2RamRdReq = ram::ReadReq; - type Tmp2RamRdResp = ram::ReadResp; - type Tmp2RamWrReq = ram::WriteReq; - type Tmp2RamWrResp = ram::WriteResp; - - type FseRamRdReq = ram::ReadReq; - type FseRamRdResp = ram::ReadResp; - type FseRamWrReq = ram::WriteReq; - type FseRamWrResp = ram::WriteResp; + type SeqDpdRamRdReq = ram::ReadReq; + type SeqDpdRamRdResp = ram::ReadResp; + type SeqDpdRamWrReq = ram::WriteReq; + type SeqDpdRamWrResp = ram::WriteResp; + + type SeqTmpRamRdReq = ram::ReadReq; + type SeqTmpRamRdResp = ram::ReadResp; + type SeqTmpRamWrReq = ram::WriteReq; + type SeqTmpRamWrResp = ram::WriteResp; + + type SeqTmp2RamRdReq = ram::ReadReq; + type SeqTmp2RamRdResp = ram::ReadResp; + type SeqTmp2RamWrReq = ram::WriteReq; + type SeqTmp2RamWrResp = ram::WriteResp; + + type SeqFseRamRdReq = ram::ReadReq; + type SeqFseRamRdResp = ram::ReadResp; + type SeqFseRamWrReq = ram::WriteReq; + type SeqFseRamWrResp = ram::WriteResp; + + type HuffmanWeightsDpdRamRdReq = ram::ReadReq; + type HuffmanWeightsDpdRamRdResp = ram::ReadResp; + type HuffmanWeightsDpdRamWrReq = ram::WriteReq; + type HuffmanWeightsDpdRamWrResp = ram::WriteResp; + + type HuffmanWeightsTmpRamRdReq = ram::ReadReq; + type HuffmanWeightsTmpRamRdResp = ram::ReadResp; + type HuffmanWeightsTmpRamWrReq = ram::WriteReq; + type HuffmanWeightsTmpRamWrResp = ram::WriteResp; + + type HuffmanWeightsTmp2RamRdReq = ram::ReadReq; + type HuffmanWeightsTmp2RamRdResp = ram::ReadResp; + type HuffmanWeightsTmp2RamWrReq = ram::WriteReq; + type HuffmanWeightsTmp2RamWrResp = ram::WriteResp; + + type HuffmanWeightsFseRamRdReq = ram::ReadReq; + type HuffmanWeightsFseRamRdResp = ram::ReadResp; + type HuffmanWeightsFseRamWrReq = ram::WriteReq; + type HuffmanWeightsFseRamWrResp = ram::WriteResp; type LiteralsHeaderDecoderResp = literals_block_header_dec::LiteralsHeaderDecoderResp; type LiteralsBlockType = literals_block_header_dec::LiteralsBlockType; @@ -126,18 +151,19 @@ pub proc CompressBlockDecoder< type LiteralsBufCtrl = common::LiteralsBufferCtrl; type CommandConstructorData = common::CommandConstructorData; - type HuffmanWeightsReadReq = ram::ReadReq; - type HuffmanWeightsReadResp = ram::ReadResp; - type HuffmanWeightsWriteReq = ram::WriteReq; + type HuffmanWeightsReadReq = ram::ReadReq; + type HuffmanWeightsReadResp = ram::ReadResp; + type HuffmanWeightsWriteReq = ram::WriteReq; type HuffmanWeightsWriteResp = ram::WriteResp; - type HuffmanPrescanReadReq = ram::ReadReq; - type HuffmanPrescanReadResp = ram::ReadResp; - type HuffmanPrescanWriteReq = ram::WriteReq; + + type HuffmanPrescanReadReq = ram::ReadReq; + type HuffmanPrescanReadResp = ram::ReadResp; + type HuffmanPrescanWriteReq = ram::WriteReq; type HuffmanPrescanWriteResp = ram::WriteResp; - type LitBufRamRdReq = ram::ReadReq; - type LitBufRamRdResp = ram::ReadResp; - type LitBufRamWrReq = ram::WriteReq; + type LitBufRamRdReq = ram::ReadReq; + type LitBufRamRdResp = ram::ReadResp; + type LitBufRamWrReq = ram::WriteReq; type LitBufRamWrResp = ram::WriteResp; type AxiAddrW = uN[AXI_ADDR_W]; @@ -176,50 +202,50 @@ pub proc CompressBlockDecoder< fd_axi_r_r: chan in, // RAMs for FSE decoder - dpd_rd_req_s: chan out, - dpd_rd_resp_r: chan in, - dpd_wr_req_s: chan out, - dpd_wr_resp_r: chan in, - - tmp_rd_req_s: chan out, - tmp_rd_resp_r: chan in, - tmp_wr_req_s: chan out, - tmp_wr_resp_r: chan in, - - tmp2_rd_req_s: chan out, - tmp2_rd_resp_r: chan in, - tmp2_wr_req_s: chan out, - tmp2_wr_resp_r: chan in, - - ll_def_fse_rd_req_s: chan out, - ll_def_fse_rd_resp_r: chan in, - ll_def_fse_wr_req_s: chan out, - ll_def_fse_wr_resp_r: chan in, - - ll_fse_rd_req_s: chan out, - ll_fse_rd_resp_r: chan in, - ll_fse_wr_req_s: chan out, - ll_fse_wr_resp_r: chan in, - - ml_def_fse_rd_req_s: chan out, - ml_def_fse_rd_resp_r: chan in, - ml_def_fse_wr_req_s: chan out, - ml_def_fse_wr_resp_r: chan in, - - ml_fse_rd_req_s: chan out, - ml_fse_rd_resp_r: chan in, - ml_fse_wr_req_s: chan out, - ml_fse_wr_resp_r: chan in, - - of_def_fse_rd_req_s: chan out, - of_def_fse_rd_resp_r: chan in, - of_def_fse_wr_req_s: chan out, - of_def_fse_wr_resp_r: chan in, - - of_fse_rd_req_s: chan out, - of_fse_rd_resp_r: chan in, - of_fse_wr_req_s: chan out, - of_fse_wr_resp_r: chan in, + dpd_rd_req_s: chan out, + dpd_rd_resp_r: chan in, + dpd_wr_req_s: chan out, + dpd_wr_resp_r: chan in, + + tmp_rd_req_s: chan out, + tmp_rd_resp_r: chan in, + tmp_wr_req_s: chan out, + tmp_wr_resp_r: chan in, + + tmp2_rd_req_s: chan out, + tmp2_rd_resp_r: chan in, + tmp2_wr_req_s: chan out, + tmp2_wr_resp_r: chan in, + + ll_def_fse_rd_req_s: chan out, + ll_def_fse_rd_resp_r: chan in, + ll_def_fse_wr_req_s: chan out, + ll_def_fse_wr_resp_r: chan in, + + ll_fse_rd_req_s: chan out, + ll_fse_rd_resp_r: chan in, + ll_fse_wr_req_s: chan out, + ll_fse_wr_resp_r: chan in, + + ml_def_fse_rd_req_s: chan out, + ml_def_fse_rd_resp_r: chan in, + ml_def_fse_wr_req_s: chan out, + ml_def_fse_wr_resp_r: chan in, + + ml_fse_rd_req_s: chan out, + ml_fse_rd_resp_r: chan in, + ml_fse_wr_req_s: chan out, + ml_fse_wr_resp_r: chan in, + + of_def_fse_rd_req_s: chan out, + of_def_fse_rd_resp_r: chan in, + of_def_fse_wr_req_s: chan out, + of_def_fse_wr_resp_r: chan in, + + of_fse_rd_req_s: chan out, + of_fse_rd_resp_r: chan in, + of_fse_wr_req_s: chan out, + of_fse_wr_resp_r: chan in, // Literals decoder channels @@ -248,8 +274,12 @@ pub proc CompressBlockDecoder< huffman_weights_raw_axi_r_r: chan in, // AXI Huffman Weights FSE Decoder (manager) - huffman_weights_fse_axi_ar_s: chan out, - huffman_weights_fse_axi_r_r: chan in, + huffman_weights_fse_lookup_dec_axi_ar_s: chan out, + huffman_weights_fse_lookup_dec_axi_r_r: chan in, + + // AXI Huffman Weights FSE Decoder (manager) + huffman_weights_fse_decoder_dec_axi_ar_s: chan out, + huffman_weights_fse_decoder_dec_axi_r_r: chan in, // Literals buffer internal memory rd_req_m0_s: chan out, @@ -297,6 +327,25 @@ pub proc CompressBlockDecoder< huffman_lit_prescan_mem_wr_req_s: chan out, huffman_lit_prescan_mem_wr_resp_r: chan in, + huffman_lit_weights_dpd_rd_req_s: chan out, + huffman_lit_weights_dpd_rd_resp_r: chan in, + huffman_lit_weights_dpd_wr_req_s: chan out, + huffman_lit_weights_dpd_wr_resp_r: chan in, + + huffman_lit_weights_tmp_rd_req_s: chan out, + huffman_lit_weights_tmp_rd_resp_r: chan in, + huffman_lit_weights_tmp_wr_req_s: chan out, + huffman_lit_weights_tmp_wr_resp_r: chan in, + + huffman_lit_weights_tmp2_rd_req_s: chan out, + huffman_lit_weights_tmp2_rd_resp_r: chan in, + huffman_lit_weights_tmp2_wr_req_s: chan out, + huffman_lit_weights_tmp2_wr_resp_r: chan in, + + huffman_lit_weights_fse_rd_req_s: chan out, + huffman_lit_weights_fse_rd_resp_r: chan in, + huffman_lit_weights_fse_wr_req_s: chan out, + huffman_lit_weights_fse_wr_resp_r: chan in, ) { // TODO: for consistency all MemReaders should be in toplevel ZSTD decoder // so we should move them up in the hierarchy from LiteralsDecoder @@ -312,7 +361,13 @@ pub proc CompressBlockDecoder< spawn literals_decoder::LiteralsDecoder< HISTORY_BUFFER_SIZE_KB, - AXI_DATA_W, AXI_ADDR_W, AXI_ID_W, AXI_DEST_W + AXI_DATA_W, AXI_ADDR_W, AXI_ID_W, AXI_DEST_W, + HUFFMAN_WEIGHTS_DPD_RAM_ADDR_W, HUFFMAN_WEIGHTS_DPD_RAM_DATA_W, HUFFMAN_WEIGHTS_DPD_RAM_NUM_PARTITIONS, + HUFFMAN_WEIGHTS_TMP_RAM_ADDR_W, HUFFMAN_WEIGHTS_TMP_RAM_DATA_W, HUFFMAN_WEIGHTS_TMP_RAM_NUM_PARTITIONS, + HUFFMAN_WEIGHTS_TMP2_RAM_ADDR_W, HUFFMAN_WEIGHTS_TMP2_RAM_DATA_W, HUFFMAN_WEIGHTS_TMP2_RAM_NUM_PARTITIONS, + HUFFMAN_WEIGHTS_FSE_RAM_ADDR_W, HUFFMAN_WEIGHTS_FSE_RAM_DATA_W, HUFFMAN_WEIGHTS_FSE_RAM_NUM_PARTITIONS, + HUFFMAN_WEIGHTS_RAM_ADDR_W, HUFFMAN_WEIGHTS_RAM_DATA_W, HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS, + HUFFMAN_PRESCAN_RAM_ADDR_W, HUFFMAN_PRESCAN_RAM_DATA_W, HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS, >( lit_header_axi_ar_s, lit_header_axi_r_r, raw_lit_axi_ar_s, raw_lit_axi_r_r, @@ -320,17 +375,30 @@ pub proc CompressBlockDecoder< huffman_jump_table_axi_ar_s, huffman_jump_table_axi_r_r, huffman_weights_header_axi_ar_s, huffman_weights_header_axi_r_r, huffman_weights_raw_axi_ar_s, huffman_weights_raw_axi_r_r, - huffman_weights_fse_axi_ar_s, huffman_weights_fse_axi_r_r, + huffman_weights_fse_lookup_dec_axi_ar_s, huffman_weights_fse_lookup_dec_axi_r_r, + huffman_weights_fse_decoder_dec_axi_ar_s, huffman_weights_fse_decoder_dec_axi_r_r, lit_ctrl_req_r, lit_ctrl_resp_s, lit_ctrl_header_s, lit_buf_ctrl_r, lit_buf_out_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, + 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, huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_resp_r, huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_resp_r, huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_resp_r, huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_resp_r, + huffman_lit_weights_dpd_rd_req_s, huffman_lit_weights_dpd_rd_resp_r, + huffman_lit_weights_dpd_wr_req_s, huffman_lit_weights_dpd_wr_resp_r, + huffman_lit_weights_tmp_rd_req_s, huffman_lit_weights_tmp_rd_resp_r, + huffman_lit_weights_tmp_wr_req_s, huffman_lit_weights_tmp_wr_resp_r, + huffman_lit_weights_tmp2_rd_req_s, huffman_lit_weights_tmp2_rd_resp_r, + huffman_lit_weights_tmp2_wr_req_s, huffman_lit_weights_tmp2_wr_resp_r, + huffman_lit_weights_fse_rd_req_s, huffman_lit_weights_fse_rd_resp_r, + huffman_lit_weights_fse_wr_req_s, huffman_lit_weights_fse_wr_resp_r, ); let (seq_dec_req_s, seq_dec_req_r) = chan("seq_dec_req"); @@ -339,10 +407,10 @@ pub proc CompressBlockDecoder< spawn sequence_dec::SequenceDecoder< AXI_ADDR_W, AXI_DATA_W, AXI_DEST_W, AXI_ID_W, - DPD_RAM_ADDR_W, DPD_RAM_DATA_W, DPD_RAM_NUM_PARTITIONS, - TMP_RAM_ADDR_W, TMP_RAM_DATA_W, TMP_RAM_NUM_PARTITIONS, - TMP2_RAM_ADDR_W, TMP2_RAM_DATA_W, TMP2_RAM_NUM_PARTITIONS, - FSE_RAM_ADDR_W, FSE_RAM_DATA_W, FSE_RAM_NUM_PARTITIONS, + SEQ_DPD_RAM_ADDR_W, SEQ_DPD_RAM_DATA_W, SEQ_DPD_RAM_NUM_PARTITIONS, + SEQ_TMP_RAM_ADDR_W, SEQ_TMP_RAM_DATA_W, SEQ_TMP_RAM_NUM_PARTITIONS, + SEQ_TMP2_RAM_ADDR_W, SEQ_TMP2_RAM_DATA_W, SEQ_TMP2_RAM_NUM_PARTITIONS, + SEQ_FSE_RAM_ADDR_W, SEQ_FSE_RAM_DATA_W, SEQ_FSE_RAM_NUM_PARTITIONS, >( scd_axi_ar_s, scd_axi_r_r, fld_axi_ar_s, fld_axi_r_r, @@ -427,12 +495,12 @@ pub proc CompressBlockDecoder< } } -const TEST_CASE_RAM_DATA_WIDTH = u32:64; +const TEST_CASE_RAM_DATA_W = u32:64; const TEST_CASE_RAM_SIZE = u32:256; -const TEST_CASE_RAM_ADDR_WIDTH = std::clog2(TEST_CASE_RAM_SIZE); -const TEST_CASE_RAM_WORD_PARTITION_SIZE = TEST_CASE_RAM_DATA_WIDTH / u32:8; +const TEST_CASE_RAM_ADDR_W = std::clog2(TEST_CASE_RAM_SIZE); +const TEST_CASE_RAM_WORD_PARTITION_SIZE = TEST_CASE_RAM_DATA_W / u32:8; const TEST_CASE_RAM_NUM_PARTITIONS = ram::num_partitions( - TEST_CASE_RAM_WORD_PARTITION_SIZE, TEST_CASE_RAM_DATA_WIDTH); + TEST_CASE_RAM_WORD_PARTITION_SIZE, TEST_CASE_RAM_DATA_W); const TEST_CASE_RAM_BASE_ADDR = u32:0; const TEST_AXI_DATA_W = u32:64; @@ -441,54 +509,85 @@ const TEST_AXI_ID_W = u32:4; const TEST_AXI_DEST_W = u32:4; const TEST_AXI_DATA_W_DIV8 = TEST_AXI_DATA_W / u32:8; -const TEST_DPD_RAM_DATA_W = u32:16; -const TEST_DPD_RAM_SIZE = u32:256; -const TEST_DPD_RAM_ADDR_W = std::clog2(TEST_DPD_RAM_SIZE); -const TEST_DPD_RAM_WORD_PARTITION_SIZE = TEST_DPD_RAM_DATA_W; -const TEST_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( - TEST_DPD_RAM_WORD_PARTITION_SIZE, TEST_DPD_RAM_DATA_W); - -const TEST_FSE_RAM_DATA_W = u32:32; -const TEST_FSE_RAM_SIZE = u32:1 << common::FSE_MAX_ACCURACY_LOG; -const TEST_FSE_RAM_ADDR_W = std::clog2(TEST_FSE_RAM_SIZE); -const TEST_FSE_RAM_WORD_PARTITION_SIZE = TEST_FSE_RAM_DATA_W; -const TEST_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( - TEST_FSE_RAM_WORD_PARTITION_SIZE, TEST_FSE_RAM_DATA_W); - -const TEST_TMP_RAM_DATA_W = u32:16; -const TEST_TMP_RAM_SIZE = u32:256; -const TEST_TMP_RAM_ADDR_W = std::clog2(TEST_TMP_RAM_SIZE); -const TEST_TMP_RAM_WORD_PARTITION_SIZE = TEST_TMP_RAM_DATA_W; -const TEST_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( - TEST_TMP_RAM_WORD_PARTITION_SIZE, TEST_TMP_RAM_DATA_W); - -const TEST_TMP2_RAM_DATA_W = u32:8; -const TEST_TMP2_RAM_SIZE = u32:512; -const TEST_TMP2_RAM_ADDR_W = std::clog2(TEST_TMP2_RAM_SIZE); -const TEST_TMP2_RAM_WORD_PARTITION_SIZE = TEST_TMP2_RAM_DATA_W; -const TEST_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_TMP2_RAM_WORD_PARTITION_SIZE, TEST_TMP2_RAM_DATA_W); +const TEST_SEQ_DPD_RAM_DATA_W = u32:16; +const TEST_SEQ_DPD_RAM_SIZE = u32:256; +const TEST_SEQ_DPD_RAM_ADDR_W = std::clog2(TEST_SEQ_DPD_RAM_SIZE); +const TEST_SEQ_DPD_RAM_WORD_PARTITION_SIZE = TEST_SEQ_DPD_RAM_DATA_W; +const TEST_SEQ_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_SEQ_DPD_RAM_WORD_PARTITION_SIZE, TEST_SEQ_DPD_RAM_DATA_W); + +const TEST_SEQ_FSE_RAM_DATA_W = u32:32; +const TEST_SEQ_FSE_RAM_SIZE = u32:1 << common::FSE_MAX_ACCURACY_LOG; +const TEST_SEQ_FSE_RAM_ADDR_W = std::clog2(TEST_SEQ_FSE_RAM_SIZE); +const TEST_SEQ_FSE_RAM_WORD_PARTITION_SIZE = TEST_SEQ_FSE_RAM_DATA_W; +const TEST_SEQ_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_SEQ_FSE_RAM_WORD_PARTITION_SIZE, TEST_SEQ_FSE_RAM_DATA_W); + +const TEST_SEQ_TMP_RAM_DATA_W = u32:16; +const TEST_SEQ_TMP_RAM_SIZE = u32:256; +const TEST_SEQ_TMP_RAM_ADDR_W = std::clog2(TEST_SEQ_TMP_RAM_SIZE); +const TEST_SEQ_TMP_RAM_WORD_PARTITION_SIZE = TEST_SEQ_TMP_RAM_DATA_W; +const TEST_SEQ_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_SEQ_TMP_RAM_WORD_PARTITION_SIZE, TEST_SEQ_TMP_RAM_DATA_W); + +const TEST_SEQ_TMP2_RAM_DATA_W = u32:8; +const TEST_SEQ_TMP2_RAM_SIZE = u32:512; +const TEST_SEQ_TMP2_RAM_ADDR_W = std::clog2(TEST_SEQ_TMP2_RAM_SIZE); +const TEST_SEQ_TMP2_RAM_WORD_PARTITION_SIZE = TEST_SEQ_TMP2_RAM_DATA_W; +const TEST_SEQ_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_SEQ_TMP2_RAM_WORD_PARTITION_SIZE, TEST_SEQ_TMP2_RAM_DATA_W); const TEST_RAM_SIM_RW_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; const TEST_RAM_INITIALIZED = true; const HISTORY_BUFFER_SIZE_KB = common::HISTORY_BUFFER_SIZE_KB; -const HUFFMAN_WEIGHTS_RAM_ADDR_WIDTH: u32 = huffman_literals_dec::WEIGHTS_ADDR_WIDTH; -const HUFFMAN_WEIGHTS_RAM_DATA_WIDTH: u32 = huffman_literals_dec::WEIGHTS_DATA_WIDTH; -const HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::WEIGHTS_NUM_PARTITIONS; -// Huffman prescan memory parameters -const HUFFMAN_PRESCAN_RAM_ADDR_WIDTH: u32 = huffman_literals_dec::PRESCAN_ADDR_WIDTH; -const HUFFMAN_PRESCAN_RAM_DATA_WIDTH: u32 = huffman_literals_dec::PRESCAN_DATA_WIDTH; -const HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::PRESCAN_NUM_PARTITIONS; -// Literals buffer memory parameters -const LITERALS_BUFFER_RAM_ADDR_WIDTH: u32 = parallel_rams::ram_addr_width(HISTORY_BUFFER_SIZE_KB); -const LITERALS_BUFFER_RAM_SIZE: u32 = parallel_rams::ram_size(HISTORY_BUFFER_SIZE_KB); -const LITERALS_BUFFER_RAM_DATA_WIDTH: u32 = literals_buffer::RAM_DATA_WIDTH; -const LITERALS_BUFFER_RAM_NUM_PARTITIONS: u32 = literals_buffer::RAM_NUM_PARTITIONS; -const LITERALS_BUFFER_RAM_WORD_PARTITION_SIZE: u32 = LITERALS_BUFFER_RAM_DATA_WIDTH; - -const AXI_CHAN_N = u32:10; - +const TEST_HUFFMAN_WEIGHTS_DPD_RAM_DATA_W = u32:16; +const TEST_HUFFMAN_WEIGHTS_DPD_RAM_SIZE = u32:256; +const TEST_HUFFMAN_WEIGHTS_DPD_RAM_ADDR_W = std::clog2(TEST_HUFFMAN_WEIGHTS_DPD_RAM_SIZE); +const TEST_HUFFMAN_WEIGHTS_DPD_RAM_WORD_PARTITION_SIZE = TEST_HUFFMAN_WEIGHTS_DPD_RAM_DATA_W; +const TEST_HUFFMAN_WEIGHTS_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_HUFFMAN_WEIGHTS_DPD_RAM_WORD_PARTITION_SIZE, TEST_HUFFMAN_WEIGHTS_DPD_RAM_DATA_W); + +const TEST_HUFFMAN_WEIGHTS_FSE_RAM_DATA_W = u32:32; +const TEST_HUFFMAN_WEIGHTS_FSE_RAM_SIZE = u32:256; +const TEST_HUFFMAN_WEIGHTS_FSE_RAM_ADDR_W = std::clog2(TEST_HUFFMAN_WEIGHTS_FSE_RAM_SIZE); +const TEST_HUFFMAN_WEIGHTS_FSE_RAM_WORD_PARTITION_SIZE = TEST_HUFFMAN_WEIGHTS_FSE_RAM_DATA_W / u32:3; +const TEST_HUFFMAN_WEIGHTS_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_HUFFMAN_WEIGHTS_FSE_RAM_WORD_PARTITION_SIZE, TEST_HUFFMAN_WEIGHTS_FSE_RAM_DATA_W); + +const TEST_HUFFMAN_WEIGHTS_TMP_RAM_DATA_W = u32:16; +const TEST_HUFFMAN_WEIGHTS_TMP_RAM_SIZE = u32:256; +const TEST_HUFFMAN_WEIGHTS_TMP_RAM_ADDR_W = std::clog2(TEST_HUFFMAN_WEIGHTS_TMP_RAM_SIZE); +const TEST_HUFFMAN_WEIGHTS_TMP_RAM_WORD_PARTITION_SIZE = TEST_HUFFMAN_WEIGHTS_TMP_RAM_DATA_W; +const TEST_HUFFMAN_WEIGHTS_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_HUFFMAN_WEIGHTS_TMP_RAM_WORD_PARTITION_SIZE, TEST_HUFFMAN_WEIGHTS_TMP_RAM_DATA_W); + +const TEST_HUFFMAN_WEIGHTS_TMP2_RAM_DATA_W = u32:8; +const TEST_HUFFMAN_WEIGHTS_TMP2_RAM_SIZE = u32:512; +const TEST_HUFFMAN_WEIGHTS_TMP2_RAM_ADDR_W = std::clog2(TEST_HUFFMAN_WEIGHTS_TMP2_RAM_SIZE); +const TEST_HUFFMAN_WEIGHTS_TMP2_RAM_WORD_PARTITION_SIZE = TEST_HUFFMAN_WEIGHTS_TMP2_RAM_DATA_W; +const TEST_HUFFMAN_WEIGHTS_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_HUFFMAN_WEIGHTS_TMP2_RAM_WORD_PARTITION_SIZE, TEST_HUFFMAN_WEIGHTS_TMP2_RAM_DATA_W); + +const TEST_HUFFMAN_WEIGHTS_RAM_DATA_W: u32 = huffman_literals_dec::WEIGHTS_DATA_WIDTH; +const TEST_HUFFMAN_WEIGHTS_RAM_SIZE = huffman_literals_dec::RAM_SIZE; +const TEST_HUFFMAN_WEIGHTS_RAM_ADDR_W: u32 = huffman_literals_dec::WEIGHTS_ADDR_WIDTH; +const TEST_HUFFMAN_WEIGHTS_RAM_WORD_PARTITION_SIZE = huffman_literals_dec::WEIGHTS_PARTITION_WORD_SIZE; +const TEST_HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::WEIGHTS_NUM_PARTITIONS; + +const TEST_HUFFMAN_PRESCAN_RAM_DATA_W: u32 = huffman_literals_dec::PRESCAN_DATA_WIDTH; +const TEST_HUFFMAN_PRESCAN_RAM_SIZE = huffman_literals_dec::RAM_SIZE; +const TEST_HUFFMAN_PRESCAN_RAM_ADDR_W: u32 = huffman_literals_dec::PRESCAN_ADDR_WIDTH; +const TEST_HUFFMAN_PRESCAN_RAM_WORD_PARTITION_SIZE = huffman_literals_dec::PRESCAN_PARTITION_WORD_SIZE; +const TEST_HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::PRESCAN_NUM_PARTITIONS; + +const TEST_LITERALS_BUFFER_RAM_ADDR_W: u32 = parallel_rams::ram_addr_width(HISTORY_BUFFER_SIZE_KB); +const TEST_LITERALS_BUFFER_RAM_SIZE: u32 = parallel_rams::ram_size(HISTORY_BUFFER_SIZE_KB); +const TEST_LITERALS_BUFFER_RAM_DATA_W: u32 = literals_buffer::RAM_DATA_WIDTH; +const TEST_LITERALS_BUFFER_RAM_NUM_PARTITIONS: u32 = literals_buffer::RAM_NUM_PARTITIONS; +const TEST_LITERALS_BUFFER_RAM_WORD_PARTITION_SIZE: u32 = TEST_LITERALS_BUFFER_RAM_DATA_W; + +const AXI_CHAN_N = u32:11; // testcase format: // - block length (without block header, essentially length of sequences + literals sections), @@ -1031,51 +1130,73 @@ proc CompressBlockDecoderTest { type MemAxiW = axi::AxiW; type MemAxiB = axi::AxiB; - type DpdRamRdReq = ram::ReadReq; - type DpdRamRdResp = ram::ReadResp; - type DpdRamWrReq = ram::WriteReq; - type DpdRamWrResp = ram::WriteResp; + type SeqDpdRamRdReq = ram::ReadReq; + type SeqDpdRamRdResp = ram::ReadResp; + type SeqDpdRamWrReq = ram::WriteReq; + type SeqDpdRamWrResp = ram::WriteResp; - type TmpRamRdReq = ram::ReadReq; - type TmpRamRdResp = ram::ReadResp; - type TmpRamWrReq = ram::WriteReq; - type TmpRamWrResp = ram::WriteResp; + type SeqTmpRamRdReq = ram::ReadReq; + type SeqTmpRamRdResp = ram::ReadResp; + type SeqTmpRamWrReq = ram::WriteReq; + type SeqTmpRamWrResp = ram::WriteResp; - type Tmp2RamRdReq = ram::ReadReq; - type Tmp2RamRdResp = ram::ReadResp; - type Tmp2RamWrReq = ram::WriteReq; - type Tmp2RamWrResp = ram::WriteResp; + type SeqTmp2RamRdReq = ram::ReadReq; + type SeqTmp2RamRdResp = ram::ReadResp; + type SeqTmp2RamWrReq = ram::WriteReq; + type SeqTmp2RamWrResp = ram::WriteResp; - type FseRamRdReq = ram::ReadReq; - type FseRamRdResp = ram::ReadResp; - type FseRamWrReq = ram::WriteReq; - type FseRamWrResp = ram::WriteResp; + type SeqFseRamRdReq = ram::ReadReq; + type SeqFseRamRdResp = ram::ReadResp; + type SeqFseRamWrReq = ram::WriteReq; + type SeqFseRamWrResp = ram::WriteResp; type LiteralsHeaderDecoderResp = literals_block_header_dec::LiteralsHeaderDecoderResp; type LiteralsBlockType = literals_block_header_dec::LiteralsBlockType; type LiteralsDecReq = literals_decoder::LiteralsDecoderCtrlReq; type LiteralsDecResp = literals_decoder::LiteralsDecoderCtrlResp; type LiteralsBufCtrl = common::LiteralsBufferCtrl; + type SequenceExecutorPacket = common::SequenceExecutorPacket; type CommandConstructorData = common::CommandConstructorData; - type HuffmanWeightsReadReq = ram::ReadReq; - type HuffmanWeightsReadResp = ram::ReadResp; - type HuffmanWeightsWriteReq = ram::WriteReq; + type HuffmanWeightsReadReq = ram::ReadReq; + type HuffmanWeightsReadResp = ram::ReadResp; + type HuffmanWeightsWriteReq = ram::WriteReq; type HuffmanWeightsWriteResp = ram::WriteResp; - type HuffmanPrescanReadReq = ram::ReadReq; - type HuffmanPrescanReadResp = ram::ReadResp; - type HuffmanPrescanWriteReq = ram::WriteReq; + + type HuffmanPrescanReadReq = ram::ReadReq; + type HuffmanPrescanReadResp = ram::ReadResp; + type HuffmanPrescanWriteReq = ram::WriteReq; type HuffmanPrescanWriteResp = ram::WriteResp; - type LitBufRamRdReq = ram::ReadReq; - type LitBufRamRdResp = ram::ReadResp; - type LitBufRamWrReq = ram::WriteReq; + type HuffmanWeightsDpdRamRdReq = ram::ReadReq; + type HuffmanWeightsDpdRamRdResp = ram::ReadResp; + type HuffmanWeightsDpdRamWrReq = ram::WriteReq; + type HuffmanWeightsDpdRamWrResp = ram::WriteResp; + + type HuffmanWeightsTmpRamRdReq = ram::ReadReq; + type HuffmanWeightsTmpRamRdResp = ram::ReadResp; + type HuffmanWeightsTmpRamWrReq = ram::WriteReq; + type HuffmanWeightsTmpRamWrResp = ram::WriteResp; + + type HuffmanWeightsTmp2RamRdReq = ram::ReadReq; + type HuffmanWeightsTmp2RamRdResp = ram::ReadResp; + type HuffmanWeightsTmp2RamWrReq = ram::WriteReq; + type HuffmanWeightsTmp2RamWrResp = ram::WriteResp; + + type HuffmanWeightsFseRamRdReq = ram::ReadReq; + type HuffmanWeightsFseRamRdResp = ram::ReadResp; + type HuffmanWeightsFseRamWrReq = ram::WriteReq; + type HuffmanWeightsFseRamWrResp = ram::WriteResp; + + type LitBufRamRdReq = ram::ReadReq; + type LitBufRamRdResp = ram::ReadResp; + type LitBufRamWrReq = ram::WriteReq; type LitBufRamWrResp = ram::WriteResp; - type TestcaseRamRdReq = ram::ReadReq; - type TestcaseRamRdResp = ram::ReadResp; - type TestcaseRamWrReq = ram::WriteReq; + type TestcaseRamRdReq = ram::ReadReq; + type TestcaseRamRdResp = ram::ReadResp; + type TestcaseRamWrReq = ram::WriteReq; type TestcaseRamWrResp = ram::WriteResp; terminator: chan out; @@ -1086,22 +1207,22 @@ proc CompressBlockDecoderTest { axi_ram_wr_resp_r: chan[AXI_CHAN_N] in; ll_sel_test_s: chan out; - ll_def_test_rd_req_s: chan out; - ll_def_test_rd_resp_r: chan in; - ll_def_test_wr_req_s: chan out; - ll_def_test_wr_resp_r: chan in; + ll_def_test_rd_req_s: chan out; + ll_def_test_rd_resp_r: chan in; + ll_def_test_wr_req_s: chan out; + ll_def_test_wr_resp_r: chan in; ml_sel_test_s: chan out; - ml_def_test_rd_req_s: chan out; - ml_def_test_rd_resp_r: chan in; - ml_def_test_wr_req_s: chan out; - ml_def_test_wr_resp_r: chan in; + ml_def_test_rd_req_s: chan out; + ml_def_test_rd_resp_r: chan in; + ml_def_test_wr_req_s: chan out; + ml_def_test_wr_resp_r: chan in; of_sel_test_s: chan out; - of_def_test_rd_req_s: chan out; - of_def_test_rd_resp_r: chan in; - of_def_test_wr_req_s: chan out; - of_def_test_wr_resp_r: chan in; + of_def_test_rd_req_s: chan out; + of_def_test_rd_resp_r: chan in; + of_def_test_wr_req_s: chan out; + of_def_test_wr_resp_r: chan in; init {} config(terminator: chan out) { @@ -1112,16 +1233,84 @@ proc CompressBlockDecoderTest { let (cmd_constr_out_s, cmd_constr_out_r) = chan("cmd_constr_out"); // Huffman weights memory - let (huffman_lit_weights_mem_rd_req_s, _huffman_lit_weights_mem_rd_req_r) = chan("huffman_lit_weights_mem_rd_req"); - let (_huffman_lit_weights_mem_rd_resp_s, huffman_lit_weights_mem_rd_resp_r) = chan("huffman_lit_weights_mem_rd_resp"); - let (huffman_lit_weights_mem_wr_req_s, _huffman_lit_weights_mem_wr_req_r) = chan("huffman_lit_weights_mem_wr_req"); - let (_huffman_lit_weights_mem_wr_resp_s, huffman_lit_weights_mem_wr_resp_r) = chan("huffman_lit_weights_mem_wr_resp"); + let (huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_req_r) = chan("huffman_lit_weights_mem_rd_req"); + let (huffman_lit_weights_mem_rd_resp_s, huffman_lit_weights_mem_rd_resp_r) = chan("huffman_lit_weights_mem_rd_resp"); + let (huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_req_r) = chan("huffman_lit_weights_mem_wr_req"); + let (huffman_lit_weights_mem_wr_resp_s, huffman_lit_weights_mem_wr_resp_r) = chan("huffman_lit_weights_mem_wr_resp"); // Huffman prescan memory - let (huffman_lit_prescan_mem_rd_req_s, _huffman_lit_prescan_mem_rd_req_r) = chan("huffman_lit_prescan_mem_rd_req"); - let (_huffman_lit_prescan_mem_rd_resp_s, huffman_lit_prescan_mem_rd_resp_r) = chan("huffman_lit_prescan_mem_rd_resp"); - let (huffman_lit_prescan_mem_wr_req_s, _huffman_lit_prescan_mem_wr_req_r) = chan("huffman_lit_prescan_mem_wr_req"); - let (_huffman_lit_prescan_mem_wr_resp_s, huffman_lit_prescan_mem_wr_resp_r) = chan("huffman_lit_prescan_mem_wr_resp"); + let (huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_req_r) = chan("huffman_lit_prescan_mem_rd_req"); + let (huffman_lit_prescan_mem_rd_resp_s, huffman_lit_prescan_mem_rd_resp_r) = chan("huffman_lit_prescan_mem_rd_resp"); + let (huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_req_r) = chan("huffman_lit_prescan_mem_wr_req"); + let (huffman_lit_prescan_mem_wr_resp_s, huffman_lit_prescan_mem_wr_resp_r) = chan("huffman_lit_prescan_mem_wr_resp"); + + let (huffman_lit_weights_dpd_rd_req_s, huffman_lit_weights_dpd_rd_req_r) = chan("huffman_lit_weights_dpd_rd_req"); + let (huffman_lit_weights_dpd_rd_resp_s, huffman_lit_weights_dpd_rd_resp_r) = chan("huffman_lit_weights_dpd_rd_resp_r"); + let (huffman_lit_weights_dpd_wr_req_s, huffman_lit_weights_dpd_wr_req_r) = chan("huffman_lit_weights_dpd_wr_req"); + let (huffman_lit_weights_dpd_wr_resp_s, huffman_lit_weights_dpd_wr_resp_r) = chan("huffman_lit_weights_dpd_wr_resp"); + + let (huffman_lit_weights_tmp_rd_req_s, huffman_lit_weights_tmp_rd_req_r) = chan("huffman_lit_weights_tmp_rd_req"); + let (huffman_lit_weights_tmp_rd_resp_s, huffman_lit_weights_tmp_rd_resp_r) = chan("huffman_lit_weights_tmp_rd_resp"); + let (huffman_lit_weights_tmp_wr_req_s, huffman_lit_weights_tmp_wr_req_r) = chan("huffman_lit_weights_tmp_wr_req"); + let (huffman_lit_weights_tmp_wr_resp_s, huffman_lit_weights_tmp_wr_resp_r) = chan("huffman_lit_weights_tmp_wr_resp"); + + let (huffman_lit_weights_tmp2_rd_req_s, huffman_lit_weights_tmp2_rd_req_r) = chan("huffman_lit_weights_tmp2_rd_req"); + let (huffman_lit_weights_tmp2_rd_resp_s, huffman_lit_weights_tmp2_rd_resp_r) = chan("huffman_lit_weights_tmp2_rd_resp"); + let (huffman_lit_weights_tmp2_wr_req_s, huffman_lit_weights_tmp2_wr_req_r) = chan("huffman_lit_weights_tmp2_wr_req"); + let (huffman_lit_weights_tmp2_wr_resp_s, huffman_lit_weights_tmp2_wr_resp_r) = chan("huffman_lit_weights_tmp2_wr_resp"); + + let (huffman_lit_weights_fse_rd_req_s, huffman_lit_weights_fse_rd_req_r) = chan("huffman_lit_weights_fse_rd_req"); + let (huffman_lit_weights_fse_rd_resp_s, huffman_lit_weights_fse_rd_resp_r) = chan("huffman_lit_weights_fse_rd_resp_r"); + let (huffman_lit_weights_fse_wr_req_s, huffman_lit_weights_fse_wr_req_r) = chan("huffman_lit_weights_fse_wr_req"); + let (huffman_lit_weights_fse_wr_resp_s, huffman_lit_weights_fse_wr_resp_r) = chan("huffman_lit_weights_fse_wr_resp"); + + spawn ram::RamModel( + huffman_lit_weights_dpd_rd_req_r, huffman_lit_weights_dpd_rd_resp_s, + huffman_lit_weights_dpd_wr_req_r, huffman_lit_weights_dpd_wr_resp_s, + ); + + spawn ram::RamModel( + huffman_lit_weights_tmp_rd_req_r, huffman_lit_weights_tmp_rd_resp_s, + huffman_lit_weights_tmp_wr_req_r, huffman_lit_weights_tmp_wr_resp_s, + ); + + spawn ram::RamModel( + huffman_lit_weights_tmp2_rd_req_r, huffman_lit_weights_tmp2_rd_resp_s, + huffman_lit_weights_tmp2_wr_req_r, huffman_lit_weights_tmp2_wr_resp_s, + ); + + spawn ram::RamModel( + huffman_lit_weights_fse_rd_req_r, huffman_lit_weights_fse_rd_resp_s, + huffman_lit_weights_fse_wr_req_r, huffman_lit_weights_fse_wr_resp_s, + ); + + spawn ram::RamModel( + huffman_lit_prescan_mem_rd_req_r, huffman_lit_prescan_mem_rd_resp_s, + huffman_lit_prescan_mem_wr_req_r, huffman_lit_prescan_mem_wr_resp_s + ); + + spawn ram::RamModel( + huffman_lit_weights_mem_rd_req_r, huffman_lit_weights_mem_rd_resp_s, + huffman_lit_weights_mem_wr_req_r, huffman_lit_weights_mem_wr_resp_s + ); // AXI channels for various blocks let (axi_ram_rd_req_s, axi_ram_rd_req_r) = chan[AXI_CHAN_N]("axi_ram_rd_req"); @@ -1132,14 +1321,14 @@ proc CompressBlockDecoderTest { let (axi_ram_r_s, axi_ram_r_r) = chan[AXI_CHAN_N]("axi_ram_r"); unroll_for! (i, ()): (u32, ()) in range(u32:0, AXI_CHAN_N) { spawn ram::RamModel< - TEST_CASE_RAM_DATA_WIDTH, TEST_CASE_RAM_SIZE, TEST_CASE_RAM_WORD_PARTITION_SIZE, + TEST_CASE_RAM_DATA_W, TEST_CASE_RAM_SIZE, TEST_CASE_RAM_WORD_PARTITION_SIZE, TEST_RAM_SIM_RW_BEHAVIOR, TEST_RAM_INITIALIZED >( axi_ram_rd_req_r[i], axi_ram_rd_resp_s[i], axi_ram_wr_req_r[i], axi_ram_wr_resp_s[i] ); spawn axi_ram::AxiRamReader< TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, TEST_CASE_RAM_SIZE, - TEST_CASE_RAM_BASE_ADDR, TEST_CASE_RAM_DATA_WIDTH, TEST_CASE_RAM_ADDR_WIDTH + TEST_CASE_RAM_BASE_ADDR, TEST_CASE_RAM_DATA_W, TEST_CASE_RAM_ADDR_W >( axi_ram_ar_r[i], axi_ram_r_s[i], axi_ram_rd_req_s[i], axi_ram_rd_resp_r[i] ); @@ -1152,7 +1341,7 @@ proc CompressBlockDecoderTest { let (litbuf_wr_resp_s, litbuf_wr_resp_r) = chan[u32:8]("litbuf_wr_resp"); unroll_for! (i, ()): (u32, ()) in range(u32:0, u32:8) { spawn ram::RamModel< - LITERALS_BUFFER_RAM_DATA_WIDTH, LITERALS_BUFFER_RAM_SIZE, LITERALS_BUFFER_RAM_WORD_PARTITION_SIZE, + TEST_LITERALS_BUFFER_RAM_DATA_W, TEST_LITERALS_BUFFER_RAM_SIZE, TEST_LITERALS_BUFFER_RAM_WORD_PARTITION_SIZE, TEST_RAM_SIM_RW_BEHAVIOR, TEST_RAM_INITIALIZED >( litbuf_rd_req_r[i], litbuf_rd_resp_s[i], litbuf_wr_req_r[i], litbuf_wr_resp_s[i] @@ -1161,47 +1350,47 @@ proc CompressBlockDecoderTest { // RAMs for FSE decoder // DPD RAM - let (dpd_rd_req_s, dpd_rd_req_r) = chan("dpd_rd_req"); - let (dpd_rd_resp_s, dpd_rd_resp_r) = chan("dpd_rd_resp"); - let (dpd_wr_req_s, dpd_wr_req_r) = chan("dpd_wr_req"); - let (dpd_wr_resp_s, dpd_wr_resp_r) = chan("dpd_wr_resp"); - spawn ram::RamModel("dpd_rd_req"); + let (dpd_rd_resp_s, dpd_rd_resp_r) = chan("dpd_rd_resp"); + let (dpd_wr_req_s, dpd_wr_req_r) = chan("dpd_wr_req"); + let (dpd_wr_resp_s, dpd_wr_resp_r) = chan("dpd_wr_resp"); + spawn ram::RamModel( dpd_rd_req_r, dpd_rd_resp_s, dpd_wr_req_r, dpd_wr_resp_s, ); // TMP RAM - let (tmp_rd_req_s, tmp_rd_req_r) = chan("tmp_rd_req"); - let (tmp_rd_resp_s, tmp_rd_resp_r) = chan("tmp_rd_resp"); - let (tmp_wr_req_s, tmp_wr_req_r) = chan("tmp_wr_req"); - let (tmp_wr_resp_s, tmp_wr_resp_r) = chan("tmp_wr_resp"); + let (tmp_rd_req_s, tmp_rd_req_r) = chan("tmp_rd_req"); + let (tmp_rd_resp_s, tmp_rd_resp_r) = chan("tmp_rd_resp"); + let (tmp_wr_req_s, tmp_wr_req_r) = chan("tmp_wr_req"); + let (tmp_wr_resp_s, tmp_wr_resp_r) = chan("tmp_wr_resp"); spawn ram::RamModel< - TEST_TMP_RAM_DATA_W, TEST_TMP_RAM_SIZE, TEST_TMP_RAM_WORD_PARTITION_SIZE, + TEST_SEQ_TMP_RAM_DATA_W, TEST_SEQ_TMP_RAM_SIZE, TEST_SEQ_TMP_RAM_WORD_PARTITION_SIZE, TEST_RAM_SIM_RW_BEHAVIOR, TEST_RAM_INITIALIZED >( tmp_rd_req_r, tmp_rd_resp_s, tmp_wr_req_r, tmp_wr_resp_s, ); - let (tmp2_rd_req_s, tmp2_rd_req_r) = chan("tmp2_rd_req"); - let (tmp2_rd_resp_s, tmp2_rd_resp_r) = chan("tmp2_rd_resp"); - let (tmp2_wr_req_s, tmp2_wr_req_r) = chan("tmp2_wr_req"); - let (tmp2_wr_resp_s, tmp2_wr_resp_r) = chan("tmp2_wr_resp"); + let (tmp2_rd_req_s, tmp2_rd_req_r) = chan("tmp2_rd_req"); + let (tmp2_rd_resp_s, tmp2_rd_resp_r) = chan("tmp2_rd_resp"); + let (tmp2_wr_req_s, tmp2_wr_req_r) = chan("tmp2_wr_req"); + let (tmp2_wr_resp_s, tmp2_wr_resp_r) = chan("tmp2_wr_resp"); spawn ram::RamModel< - TEST_TMP2_RAM_DATA_W, TEST_TMP2_RAM_SIZE, TEST_TMP2_RAM_WORD_PARTITION_SIZE, + TEST_SEQ_TMP2_RAM_DATA_W, TEST_SEQ_TMP2_RAM_SIZE, TEST_SEQ_TMP2_RAM_WORD_PARTITION_SIZE, TEST_RAM_SIM_RW_BEHAVIOR, TEST_RAM_INITIALIZED >( tmp2_rd_req_r, tmp2_rd_resp_s, tmp2_wr_req_r, tmp2_wr_resp_s, ); // FSE RAMs - let (fse_rd_req_s, fse_rd_req_r) = chan[u32:6]("tmp_rd_req"); - let (fse_rd_resp_s, fse_rd_resp_r) = chan[u32:6]("tmp_rd_resp"); - let (fse_wr_req_s, fse_wr_req_r) = chan[u32:6]("tmp_wr_req"); - let (fse_wr_resp_s, fse_wr_resp_r) = chan[u32:6]("tmp_wr_resp"); + let (fse_rd_req_s, fse_rd_req_r) = chan[u32:6]("tmp_rd_req"); + let (fse_rd_resp_s, fse_rd_resp_r) = chan[u32:6]("tmp_rd_resp"); + let (fse_wr_req_s, fse_wr_req_r) = chan[u32:6]("tmp_wr_req"); + let (fse_wr_resp_s, fse_wr_resp_r) = chan[u32:6]("tmp_wr_resp"); unroll_for! (i, ()): (u32, ()) in range(u32:0, u32:6) { spawn ram::RamModel< - TEST_FSE_RAM_DATA_W, TEST_FSE_RAM_SIZE, TEST_FSE_RAM_WORD_PARTITION_SIZE, + TEST_SEQ_FSE_RAM_DATA_W, TEST_SEQ_FSE_RAM_SIZE, TEST_SEQ_FSE_RAM_WORD_PARTITION_SIZE, TEST_RAM_SIM_RW_BEHAVIOR, TEST_RAM_INITIALIZED >( fse_rd_req_r[i], fse_rd_resp_s[i], fse_wr_req_r[i], fse_wr_resp_s[i] @@ -1212,20 +1401,18 @@ proc CompressBlockDecoderTest { let (ll_sel_test_s, ll_sel_test_r) = chan("ll_sel_test"); - let (ll_def_test_rd_req_s, ll_def_test_rd_req_r) = chan("ll_def_test_rd_req"); - let (ll_def_test_rd_resp_s, ll_def_test_rd_resp_r) = chan("ll_def_test_rd_resp"); - let (ll_def_test_wr_req_s, ll_def_test_wr_req_r) = chan("ll_def_test_wr_req"); - let (ll_def_test_wr_resp_s, ll_def_test_wr_resp_r) = chan("ll_def_test_wr_resp"); + let (ll_def_test_rd_req_s, ll_def_test_rd_req_r) = chan("ll_def_test_rd_req"); + let (ll_def_test_rd_resp_s, ll_def_test_rd_resp_r) = chan("ll_def_test_rd_resp"); + let (ll_def_test_wr_req_s, ll_def_test_wr_req_r) = chan("ll_def_test_wr_req"); + let (ll_def_test_wr_resp_s, ll_def_test_wr_resp_r) = chan("ll_def_test_wr_resp"); - let (ll_def_fse_rd_req_s, ll_def_fse_rd_req_r) = chan("ll_def_fse_rd_req"); - let (ll_def_fse_rd_resp_s, ll_def_fse_rd_resp_r) = chan("ll_def_fse_rd_resp"); - let (ll_def_fse_wr_req_s, ll_def_fse_wr_req_r) = chan("ll_def_fse_wr_req"); - let (ll_def_fse_wr_resp_s, ll_def_fse_wr_resp_r) = chan("ll_def_fse_wr_resp"); + let (ll_def_fse_rd_req_s, ll_def_fse_rd_req_r) = chan("ll_def_fse_rd_req"); + let (ll_def_fse_rd_resp_s, ll_def_fse_rd_resp_r) = chan("ll_def_fse_rd_resp"); + let (ll_def_fse_wr_req_s, ll_def_fse_wr_req_r) = chan("ll_def_fse_wr_req"); + let (ll_def_fse_wr_resp_s, ll_def_fse_wr_resp_r) = chan("ll_def_fse_wr_resp"); spawn ram_mux::RamMux< - TEST_FSE_RAM_ADDR_W, - TEST_FSE_RAM_DATA_W, - TEST_FSE_RAM_NUM_PARTITIONS, + TEST_SEQ_FSE_RAM_ADDR_W, TEST_SEQ_FSE_RAM_DATA_W, TEST_SEQ_FSE_RAM_NUM_PARTITIONS, >( ll_sel_test_r, ll_def_test_rd_req_r, ll_def_test_rd_resp_s, ll_def_test_wr_req_r, ll_def_test_wr_resp_s, @@ -1237,20 +1424,18 @@ proc CompressBlockDecoderTest { let (ml_sel_test_s, ml_sel_test_r) = chan("ml_sel_test"); - let (ml_def_test_rd_req_s, ml_def_test_rd_req_r) = chan("ml_def_test_rd_req"); - let (ml_def_test_rd_resp_s, ml_def_test_rd_resp_r) = chan("ml_def_test_rd_resp"); - let (ml_def_test_wr_req_s, ml_def_test_wr_req_r) = chan("ml_def_test_wr_req"); - let (ml_def_test_wr_resp_s, ml_def_test_wr_resp_r) = chan("ml_def_test_wr_resp"); + let (ml_def_test_rd_req_s, ml_def_test_rd_req_r) = chan("ml_def_test_rd_req"); + let (ml_def_test_rd_resp_s, ml_def_test_rd_resp_r) = chan("ml_def_test_rd_resp"); + let (ml_def_test_wr_req_s, ml_def_test_wr_req_r) = chan("ml_def_test_wr_req"); + let (ml_def_test_wr_resp_s, ml_def_test_wr_resp_r) = chan("ml_def_test_wr_resp"); - let (ml_def_fse_rd_req_s, ml_def_fse_rd_req_r) = chan("ml_def_fse_rd_req"); - let (ml_def_fse_rd_resp_s, ml_def_fse_rd_resp_r) = chan("ml_def_fse_rd_resp"); - let (ml_def_fse_wr_req_s, ml_def_fse_wr_req_r) = chan("ml_def_fse_wr_req"); - let (ml_def_fse_wr_resp_s, ml_def_fse_wr_resp_r) = chan("ml_def_fse_wr_resp"); + let (ml_def_fse_rd_req_s, ml_def_fse_rd_req_r) = chan("ml_def_fse_rd_req"); + let (ml_def_fse_rd_resp_s, ml_def_fse_rd_resp_r) = chan("ml_def_fse_rd_resp"); + let (ml_def_fse_wr_req_s, ml_def_fse_wr_req_r) = chan("ml_def_fse_wr_req"); + let (ml_def_fse_wr_resp_s, ml_def_fse_wr_resp_r) = chan("ml_def_fse_wr_resp"); spawn ram_mux::RamMux< - TEST_FSE_RAM_ADDR_W, - TEST_FSE_RAM_DATA_W, - TEST_FSE_RAM_NUM_PARTITIONS, + TEST_SEQ_FSE_RAM_ADDR_W, TEST_SEQ_FSE_RAM_DATA_W, TEST_SEQ_FSE_RAM_NUM_PARTITIONS, >( ml_sel_test_r, ml_def_test_rd_req_r, ml_def_test_rd_resp_s, ml_def_test_wr_req_r, ml_def_test_wr_resp_s, @@ -1262,20 +1447,18 @@ proc CompressBlockDecoderTest { let (of_sel_test_s, of_sel_test_r) = chan("of_sel_test"); - let (of_def_test_rd_req_s, of_def_test_rd_req_r) = chan("of_def_test_rd_req"); - let (of_def_test_rd_resp_s, of_def_test_rd_resp_r) = chan("of_def_test_rd_resp"); - let (of_def_test_wr_req_s, of_def_test_wr_req_r) = chan("of_def_test_wr_req"); - let (of_def_test_wr_resp_s, of_def_test_wr_resp_r) = chan("of_def_test_wr_resp"); + let (of_def_test_rd_req_s, of_def_test_rd_req_r) = chan("of_def_test_rd_req"); + let (of_def_test_rd_resp_s, of_def_test_rd_resp_r) = chan("of_def_test_rd_resp"); + let (of_def_test_wr_req_s, of_def_test_wr_req_r) = chan("of_def_test_wr_req"); + let (of_def_test_wr_resp_s, of_def_test_wr_resp_r) = chan("of_def_test_wr_resp"); - let (of_def_fse_rd_req_s, of_def_fse_rd_req_r) = chan("of_def_fse_rd_req"); - let (of_def_fse_rd_resp_s, of_def_fse_rd_resp_r) = chan("of_def_fse_rd_resp"); - let (of_def_fse_wr_req_s, of_def_fse_wr_req_r) = chan("of_def_fse_wr_req"); - let (of_def_fse_wr_resp_s, of_def_fse_wr_resp_r) = chan("of_def_fse_wr_resp"); + let (of_def_fse_rd_req_s, of_def_fse_rd_req_r) = chan("of_def_fse_rd_req"); + let (of_def_fse_rd_resp_s, of_def_fse_rd_resp_r) = chan("of_def_fse_rd_resp"); + let (of_def_fse_wr_req_s, of_def_fse_wr_req_r) = chan("of_def_fse_wr_req"); + let (of_def_fse_wr_resp_s, of_def_fse_wr_resp_r) = chan("of_def_fse_wr_resp"); spawn ram_mux::RamMux< - TEST_FSE_RAM_ADDR_W, - TEST_FSE_RAM_DATA_W, - TEST_FSE_RAM_NUM_PARTITIONS, + TEST_SEQ_FSE_RAM_ADDR_W, TEST_SEQ_FSE_RAM_DATA_W, TEST_SEQ_FSE_RAM_NUM_PARTITIONS, >( of_sel_test_r, of_def_test_rd_req_r, of_def_test_rd_resp_s, of_def_test_wr_req_r, of_def_test_wr_resp_s, @@ -1285,11 +1468,16 @@ proc CompressBlockDecoderTest { spawn CompressBlockDecoder< TEST_AXI_DATA_W, TEST_AXI_ADDR_W, TEST_AXI_ID_W, TEST_AXI_DEST_W, - // FSE lookup table RAMs - TEST_DPD_RAM_ADDR_W, TEST_DPD_RAM_DATA_W, TEST_DPD_RAM_NUM_PARTITIONS, - TEST_TMP_RAM_ADDR_W, TEST_TMP_RAM_DATA_W, TEST_TMP_RAM_NUM_PARTITIONS, - TEST_TMP2_RAM_ADDR_W, TEST_TMP2_RAM_DATA_W, TEST_TMP2_RAM_NUM_PARTITIONS, - TEST_FSE_RAM_ADDR_W, TEST_FSE_RAM_DATA_W, TEST_FSE_RAM_NUM_PARTITIONS, + + TEST_SEQ_DPD_RAM_ADDR_W, TEST_SEQ_DPD_RAM_DATA_W, TEST_SEQ_DPD_RAM_NUM_PARTITIONS, + TEST_SEQ_TMP_RAM_ADDR_W, TEST_SEQ_TMP_RAM_DATA_W, TEST_SEQ_TMP_RAM_NUM_PARTITIONS, + TEST_SEQ_TMP2_RAM_ADDR_W, TEST_SEQ_TMP2_RAM_DATA_W, TEST_SEQ_TMP2_RAM_NUM_PARTITIONS, + TEST_SEQ_FSE_RAM_ADDR_W, TEST_SEQ_FSE_RAM_DATA_W, TEST_SEQ_FSE_RAM_NUM_PARTITIONS, + + TEST_HUFFMAN_WEIGHTS_DPD_RAM_ADDR_W, TEST_HUFFMAN_WEIGHTS_DPD_RAM_DATA_W, TEST_HUFFMAN_WEIGHTS_DPD_RAM_NUM_PARTITIONS, + TEST_HUFFMAN_WEIGHTS_TMP_RAM_ADDR_W, TEST_HUFFMAN_WEIGHTS_TMP_RAM_DATA_W, TEST_HUFFMAN_WEIGHTS_TMP_RAM_NUM_PARTITIONS, + TEST_HUFFMAN_WEIGHTS_TMP2_RAM_ADDR_W, TEST_HUFFMAN_WEIGHTS_TMP2_RAM_DATA_W, TEST_HUFFMAN_WEIGHTS_TMP2_RAM_NUM_PARTITIONS, + TEST_HUFFMAN_WEIGHTS_FSE_RAM_ADDR_W, TEST_HUFFMAN_WEIGHTS_FSE_RAM_DATA_W, TEST_HUFFMAN_WEIGHTS_FSE_RAM_NUM_PARTITIONS, >( req_r, resp_s, cmd_constr_out_s, @@ -1312,6 +1500,7 @@ proc CompressBlockDecoderTest { axi_ram_ar_s[7], axi_ram_r_r[7], axi_ram_ar_s[8], axi_ram_r_r[8], axi_ram_ar_s[9], axi_ram_r_r[9], + axi_ram_ar_s[10], axi_ram_r_r[10], litbuf_rd_req_s[0], litbuf_rd_req_s[1], litbuf_rd_req_s[2], litbuf_rd_req_s[3], litbuf_rd_req_s[4], litbuf_rd_req_s[5], litbuf_rd_req_s[6], litbuf_rd_req_s[7], litbuf_rd_resp_r[0], litbuf_rd_resp_r[1], litbuf_rd_resp_r[2], litbuf_rd_resp_r[3], @@ -1324,6 +1513,14 @@ proc CompressBlockDecoderTest { huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_resp_r, huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_resp_r, huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_resp_r, + huffman_lit_weights_dpd_rd_req_s, huffman_lit_weights_dpd_rd_resp_r, + huffman_lit_weights_dpd_wr_req_s, huffman_lit_weights_dpd_wr_resp_r, + huffman_lit_weights_tmp_rd_req_s, huffman_lit_weights_tmp_rd_resp_r, + huffman_lit_weights_tmp_wr_req_s, huffman_lit_weights_tmp_wr_resp_r, + huffman_lit_weights_tmp2_rd_req_s, huffman_lit_weights_tmp2_rd_resp_r, + huffman_lit_weights_tmp2_wr_req_s, huffman_lit_weights_tmp2_wr_resp_r, + huffman_lit_weights_fse_rd_req_s, huffman_lit_weights_fse_rd_resp_r, + huffman_lit_weights_fse_wr_req_s, huffman_lit_weights_fse_wr_resp_r, ); ( @@ -1350,10 +1547,10 @@ proc CompressBlockDecoderTest { trace_fmt!("Filling LL default FSE table"); let tok = send(tok, ll_sel_test_s, u1:0); let tok = unroll_for! (i, tok): (u32, token) in range(u32:0, array_size(sequence_dec::DEFAULT_LL_TABLE)) { - let req = FseRamWrReq { - addr: i as uN[TEST_FSE_RAM_ADDR_W], + let req = SeqFseRamWrReq { + addr: i as uN[TEST_SEQ_FSE_RAM_ADDR_W], data: fse_table_creator::fse_record_to_bits(sequence_dec::DEFAULT_LL_TABLE[i]), - mask: !uN[TEST_FSE_RAM_NUM_PARTITIONS]:0, + mask: !uN[TEST_SEQ_FSE_RAM_NUM_PARTITIONS]:0, }; let tok = send(tok, ll_def_test_wr_req_s, req); let (tok, _) = recv(tok, ll_def_test_wr_resp_r); @@ -1365,10 +1562,10 @@ proc CompressBlockDecoderTest { trace_fmt!("Filling OF default FSE table"); let tok = send(tok, of_sel_test_s, u1:0); let tok = unroll_for! (i, tok): (u32, token) in range(u32:0, array_size(sequence_dec::DEFAULT_OF_TABLE)) { - let req = FseRamWrReq { - addr: i as uN[TEST_FSE_RAM_ADDR_W], + let req = SeqFseRamWrReq { + addr: i as uN[TEST_SEQ_FSE_RAM_ADDR_W], data: fse_table_creator::fse_record_to_bits(sequence_dec::DEFAULT_OF_TABLE[i]), - mask: !uN[TEST_FSE_RAM_NUM_PARTITIONS]:0, + mask: !uN[TEST_SEQ_FSE_RAM_NUM_PARTITIONS]:0, }; let tok = send(tok, of_def_test_wr_req_s, req); let (tok, _) = recv(tok, of_def_test_wr_resp_r); @@ -1380,10 +1577,10 @@ proc CompressBlockDecoderTest { trace_fmt!("Filling ML default FSE table"); let tok = send(tok, ml_sel_test_s, u1:0); let tok = unroll_for! (i, tok): (u32, token) in range(u32:0, array_size(sequence_dec::DEFAULT_ML_TABLE)) { - let req = FseRamWrReq { - addr: i as uN[TEST_FSE_RAM_ADDR_W], + let req = SeqFseRamWrReq { + addr: i as uN[TEST_SEQ_FSE_RAM_ADDR_W], data: fse_table_creator::fse_record_to_bits(sequence_dec::DEFAULT_ML_TABLE[i]), - mask: !uN[TEST_FSE_RAM_NUM_PARTITIONS]:0, + mask: !uN[TEST_SEQ_FSE_RAM_NUM_PARTITIONS]:0, }; let tok = send(tok, ml_def_test_wr_req_s, req); let (tok, _) = recv(tok, ml_def_test_wr_resp_r); @@ -1397,8 +1594,8 @@ proc CompressBlockDecoderTest { trace_fmt!("Loading testcase {}", test_i); let tok = for ((i, input_data), tok): ((u32, u64), token) in enumerate(input) { let req = TestcaseRamWrReq { - addr: i as uN[TEST_CASE_RAM_ADDR_WIDTH], - data: input_data as uN[TEST_CASE_RAM_DATA_WIDTH], + addr: i as uN[TEST_CASE_RAM_ADDR_W], + data: input_data as uN[TEST_CASE_RAM_DATA_W], mask: !uN[TEST_CASE_RAM_NUM_PARTITIONS]:0 }; // Write to all RAMs diff --git a/xls/modules/zstd/comp_frame_huffman_fse.x b/xls/modules/zstd/comp_frame_huffman_fse.x new file mode 100644 index 0000000000..7732b115cd --- /dev/null +++ b/xls/modules/zstd/comp_frame_huffman_fse.x @@ -0,0 +1,21 @@ +pub struct DataArray{ + data: uN[BITS_PER_WORD][LENGTH], + length: u32, + array_length: u32 +} +pub const FRAMES:DataArray< + u32:64, + u32:8 +>[1] = [DataArray<64, 8>{ + length: u32:64, + array_length: u32:8, + data: uN[64][8]:[uN[64]:0x007e4f84fd2fb528, uN[64]:0x00068e00017d0000, uN[64]:0xd5764f39f0080008, uN[64]:0x04000400045c4f40, uN[64]:0xcfefff3e7fefff00, uN[64]:0x5dff77afbdffef3f, uN[64]:0x1de190b0000301fb, uN[64]:0x807e83a8084e0c21] +}]; +pub const DECOMPRESSED_FRAMES:DataArray< + u32:64, + u32:16 +>[1] = [DataArray<64, 16>{ + length: u32:126, + array_length: u32:16, + data: uN[64][16]:[uN[64]:0xe6e6e6e6e6e6e6e6, uN[64]:0xe6e6e6e6e680e6e6, uN[64]:0xe6e6e6b3e6e6e6e6, uN[64]:0xe6e6e6e6e6e6e6e6, uN[64]:0x80e6e6e6e6e6e6e6, uN[64]:0xe6e6e6e6e6e6e6e6, uN[64]:0xe6b3e6e6e6e6e6e6, uN[64]:0xe6e6e6e6e6e6e6e6, uN[64]:0xe6e6e6e6b3b3e6e6, uN[64]:0xe6e6e6b3e6e6e6b3, uN[64]:0xe6e6e6e6e6e6b3e6, uN[64]:0xe6e6e6e6e6e6e6e6, uN[64]:0xe6e6e6e6e6e6e6b3, uN[64]:0xb3e6e6b3b3e6b3e6, uN[64]:0xe6e6e6e6e6e6e6e6, uN[64]:0xe6e6b3e6e6b3] +}]; diff --git a/xls/modules/zstd/comp_lookup_dec.x b/xls/modules/zstd/comp_lookup_dec.x index 628326edf9..fd18e038b0 100644 --- a/xls/modules/zstd/comp_lookup_dec.x +++ b/xls/modules/zstd/comp_lookup_dec.x @@ -25,6 +25,15 @@ import xls.modules.zstd.fse_proba_freq_dec; import xls.modules.shift_buffer.shift_buffer; type AccuracyLog = common::FseAccuracyLog; +type ConsumedFseBytes = fse_proba_freq_dec::ConsumedFseBytes; + +pub type CompLookupDecoderReq = common::LookupDecoderReq; +pub type CompLookupDecoderStatus = common::LookupDecoderStatus; +pub struct CompLookupDecoderResp { + status: CompLookupDecoderStatus, + accuracy_log: AccuracyLog, + consumed_bytes: ConsumedFseBytes, +} pub proc CompLookupDecoder< AXI_DATA_W: u32, @@ -34,9 +43,9 @@ pub proc CompLookupDecoder< FSE_RAM_DATA_W: u32, FSE_RAM_ADDR_W: u32, FSE_RAM_NUM_PARTITIONS: u32, SB_LENGTH_W: u32 = {refilling_shift_buffer::length_width(AXI_DATA_W)}, > { - type Req = common::LookupDecoderReq; - type Resp = common::LookupDecoderResp; - type Status = common::LookupDecoderStatus; + type Req = CompLookupDecoderReq; + type Resp = CompLookupDecoderResp; + type Status = CompLookupDecoderStatus; type FseTableStart = fse_table_creator::FseStartMsg; @@ -158,7 +167,11 @@ pub proc CompLookupDecoder< trace_fmt!("FSE table created"); let resp = if pf_dec_ok { - Resp { status: Status::OK, accuracy_log: pf_dec_res.accuracy_log } + Resp { + status: Status::OK, + accuracy_log: pf_dec_res.accuracy_log, + consumed_bytes: pf_dec_res.consumed_bytes, + } } else { Resp { status: Status::ERROR, ..zero!() } }; @@ -214,7 +227,7 @@ const TEST_RAM_INITIALIZED = true; type FseTableRecord = common::FseTableRecord; -const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE], common::LookupDecoderResp)[12] = [ +const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE], CompLookupDecoderResp)[12] = [ ( u64[64]:[u64:0x72AAAAABBB1D25C0, u64:0, ...], FseTableRecord[TEST_FSE_RAM_SIZE]:[ @@ -252,7 +265,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x15 }, zero!(), ... ], - common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:3} ), ( u64[64]:[u64:0x1861862062081932, u64:0xC18628A106184184, u64:0x850720FACC49238, u64:0, ...], @@ -387,7 +400,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x6, base: u16:0x40 }, zero!(), ... ], - common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:7 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:7, consumed_bytes: ConsumedFseBytes:21} ), ( u64[64]:[u64:0x60C3082082085072, u64:0x1C06F8077D850F20, u64:0, ...], @@ -522,7 +535,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x58 }, zero!(), ... ], - common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:7 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:7, consumed_bytes: ConsumedFseBytes:13 } ), ( u64[64]:[u64:0x41081C158003A5D0, u64:0, ...], @@ -561,7 +574,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x17 }, zero!(), ... ], - common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:3 } ), ( u64[64]:[u64:0x1101141108088A1, u64:0xA210842108421011, u64:0xAC90E792007A5B4, u64:0, ...], @@ -632,7 +645,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x1c, num_of_bits: u8:0x3, base: u16:0x8 }, zero!(), ... ], - common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:6 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:6, consumed_bytes: ConsumedFseBytes:19 } ), ( u64[64]:[u64:0x4AF830AC90E7920, u64:0, ...], @@ -671,7 +684,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0xa }, zero!(), ... ], - common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:3 } ), ( u64[64]:[u64:0xF47FFEBBFF1D25C0, u64:0, ...], @@ -710,7 +723,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x15 }, zero!(), ... ], - common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:3 } ), ( u64[64]:[u64:0xA84DF134544CA40, u64:0xEEC609988403B0C, u64:0, ...], @@ -749,7 +762,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x3, base: u16:0x8 }, zero!(), ... ], - common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:10 } ), ( u64[64]:[u64:0x38100EEC60998840, u64:0, ...], @@ -788,7 +801,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x2, base: u16:0xc }, zero!(), ... ], - common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:6 } ), ( u64[64]:[u64:0x6B1CA24D0CE43810, u64:0x6651065104A4DFFD, u64:0, ...], @@ -827,7 +840,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x3, base: u16:0x8 }, zero!(), ... ], - common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:10 } ), ( u64[64]:[u64:0x604FC0502602814, u64:0xE030505040131FF6, u64:0, ...], @@ -1345,7 +1358,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1fc }, FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1fc }, ], - common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:9 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:9, consumed_bytes: ConsumedFseBytes:10 } ), ( u64[64]:[u64:0x140FE03050504013, u64:0, ...], @@ -1608,15 +1621,15 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xfc }, zero!(), ... ], - common::LookupDecoderResp { status: common::LookupDecoderStatus::OK, accuracy_log: AccuracyLog:8 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:8, consumed_bytes: ConsumedFseBytes:7 } ), ]; #[test_proc] proc CompLookupDecoderTest { - type Req = common::LookupDecoderReq; - type Resp = common::LookupDecoderResp; - type Status = common::LookupDecoderStatus; + type Req = CompLookupDecoderReq; + type Resp = CompLookupDecoderResp; + type Status = CompLookupDecoderStatus; type MemReaderReq = mem_reader::MemReaderReq; type MemReaderResp = mem_reader::MemReaderResp; @@ -1793,7 +1806,7 @@ proc CompLookupDecoderTest { tok }(tok); - trace_fmt!("Running FSE lookup decoder on testcase {:x}", test_i); + trace_fmt!("Running COMP lookup decoder on testcase {:x}", test_i); let tok = send(tok, refill_req_s, RefillStartReq { start_addr: uN[TEST_AXI_ADDR_WIDTH]:0x0 }); diff --git a/xls/modules/zstd/fse_lookup_dec.x b/xls/modules/zstd/fse_lookup_dec.x index 581dcac5d2..85664be982 100644 --- a/xls/modules/zstd/fse_lookup_dec.x +++ b/xls/modules/zstd/fse_lookup_dec.x @@ -68,17 +68,19 @@ pub proc FseLookupDecoder< type SBOutput = refilling_shift_buffer::RefillingShiftBufferOutput; type SBCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + type CompLookupDecoderReq = comp_lookup_dec::CompLookupDecoderReq; + type CompLookupDecoderResp = comp_lookup_dec::CompLookupDecoderResp; + type LookupDecoderReq = common::LookupDecoderReq; type LookupDecoderResp = common::LookupDecoderResp; - init {} fse_lookup_dec_req_r: chan in; fse_lookup_dec_resp_s: chan out; - comp_lookup_req_s: chan out; - comp_lookup_resp_r: chan in; + comp_lookup_req_s: chan out; + comp_lookup_resp_r: chan in; rle_lookup_req_s: chan out; rle_lookup_resp_r: chan in; @@ -164,8 +166,8 @@ pub proc FseLookupDecoder< fse_rd_req_s, fse_rd_resp_r, fse_wr_req_s, fse_wr_resp_r, ); - let (comp_lookup_req_s, comp_lookup_req_r) = chan("comp_lookup_req"); - let (comp_lookup_resp_s, comp_lookup_resp_r) = chan("comp_lookup_resp"); + let (comp_lookup_req_s, comp_lookup_req_r) = chan("comp_lookup_req"); + let (comp_lookup_resp_s, comp_lookup_resp_r) = chan("comp_lookup_resp"); spawn comp_lookup_dec::CompLookupDecoder< AXI_DATA_W, @@ -226,12 +228,17 @@ pub proc FseLookupDecoder< let tok4_0 = send_if(tok3, rle_lookup_req_s, req.is_rle, LookupDecoderReq {}); let (tok5_0, rle_lookup_resp) = recv_if(tok4_0, rle_lookup_resp_r, req.is_rle, zero!()); - let tok4_1 = send_if(tok3, comp_lookup_req_s, !req.is_rle, LookupDecoderReq {}); - let (tok5_1, comp_lookup_resp) = recv_if(tok4_1, comp_lookup_resp_r, !req.is_rle, zero!()); + let tok4_1 = send_if(tok3, comp_lookup_req_s, !req.is_rle, CompLookupDecoderReq {}); + let (tok5_1, comp_lookup_resp) = recv_if(tok4_1, comp_lookup_resp_r, !req.is_rle, zero!()); let tok5 = join(tok5_0, tok5_1); - let resp = if req.is_rle { rle_lookup_resp } else { comp_lookup_resp }; + let resp = if req.is_rle { rle_lookup_resp } else { + Resp { + status: comp_lookup_resp.status, + accuracy_log: comp_lookup_resp.accuracy_log + } + }; let tok6 = send(tok5, fse_lookup_dec_resp_s, resp); // unused channels diff --git a/xls/modules/zstd/huffman_literals_dec.x b/xls/modules/zstd/huffman_literals_dec.x index b3b650f2ae..825fbc05b6 100644 --- a/xls/modules/zstd/huffman_literals_dec.x +++ b/xls/modules/zstd/huffman_literals_dec.x @@ -37,20 +37,26 @@ pub type HuffmanLiteralsDecoderReq = ctrl::HuffmanControlAndSequenceCtrl; pub type HuffmanLiteralsDecoderResp = ctrl::HuffmanControlAndSequenceResp; pub type HuffmanLiteralsDecoderStatus = ctrl::HuffmanControlAndSequenceStatus; -pub const RAM_SIZE: u32 = prescan::RAM_SIZE; -pub const WEIGHTS_ADDR_WIDTH: u32 = prescan::RAM_ADDR_WIDTH; -pub const WEIGHTS_DATA_WIDTH: u32 = prescan::RAM_ACCESS_WIDTH; -pub const WEIGHTS_PARTITION_WORD_SIZE: u32 = WEIGHTS_DATA_WIDTH; -pub const WEIGHTS_NUM_PARTITIONS: u32 = u32:1; +pub const RAM_SIZE = prescan::RAM_SIZE; +pub const WEIGHTS_ADDR_WIDTH = prescan::RAM_ADDR_WIDTH; +pub const WEIGHTS_DATA_WIDTH = prescan::RAM_ACCESS_WIDTH; +pub const WEIGHTS_PARTITION_WORD_SIZE = WEIGHTS_DATA_WIDTH / u32:8; +pub const WEIGHTS_NUM_PARTITIONS = ram::num_partitions(WEIGHTS_PARTITION_WORD_SIZE, WEIGHTS_DATA_WIDTH); +// pub const WEIGHTS_NUM_PARTITIONS: u32 = u32:1; + pub const PRESCAN_ADDR_WIDTH: u32 = prescan::RAM_ADDR_WIDTH; pub const PRESCAN_DATA_WIDTH: u32 = prescan::WeightPreScanMetaDataSize(); pub const PRESCAN_PARTITION_WORD_SIZE: u32 = PRESCAN_DATA_WIDTH; -pub const PRESCAN_NUM_PARTITIONS: u32 = u32:1; +pub const PRESCAN_NUM_PARTITIONS = ram::num_partitions(PRESCAN_PARTITION_WORD_SIZE, PRESCAN_DATA_WIDTH); + +// pub const PRESCAN_NUM_PARTITIONS: u32 = u32:1; pub proc HuffmanLiteralsDecoder< AXI_DATA_W: u32, AXI_ADDR_W: u32, AXI_ID_W: u32, AXI_DEST_W: u32, WEIGHTS_DPD_RAM_ADDR_W: u32, WEIGHTS_DPD_RAM_DATA_W: u32, WEIGHTS_DPD_RAM_NUM_PARTITIONS: u32, WEIGHTS_TMP_RAM_ADDR_W: u32, WEIGHTS_TMP_RAM_DATA_W: u32, WEIGHTS_TMP_RAM_NUM_PARTITIONS: u32, + WEIGHTS_TMP2_RAM_ADDR_W: u32, WEIGHTS_TMP2_RAM_DATA_W: u32, WEIGHTS_TMP2_RAM_NUM_PARTITIONS: u32, + WEIGHTS_FSE_RAM_ADDR_W: u32, WEIGHTS_FSE_RAM_DATA_W: u32, WEIGHTS_FSE_RAM_NUM_PARTITIONS: u32, WEIGHTS_RAM_ADDR_WIDTH: u32 = {WEIGHTS_ADDR_WIDTH}, WEIGHTS_RAM_DATA_WIDTH: u32 = {WEIGHTS_DATA_WIDTH}, @@ -69,6 +75,7 @@ pub proc HuffmanLiteralsDecoder< type WeightsRamRdResp = ram::ReadResp; type WeightsRamWrReq = ram::WriteReq; type WeightsRamWrResp = ram::WriteResp; + type PrescanRamRdReq = ram::ReadReq; type PrescanRamRdResp = ram::ReadResp; type PrescanRamWrReq = ram::WriteReq; @@ -85,6 +92,11 @@ pub proc HuffmanLiteralsDecoder< type WeightsTmpRamWrReq = ram::WriteReq; type WeightsTmpRamWrResp = ram::WriteResp; + type WeightsTmp2RamRdReq = ram::ReadReq; + type WeightsTmp2RamRdResp = ram::ReadResp; + type WeightsTmp2RamWrReq = ram::WriteReq; + type WeightsTmp2RamWrResp = ram::WriteResp; + type WeightsFseRamRdReq = ram::ReadReq; type WeightsFseRamRdResp = ram::ReadResp; type WeightsFseRamWrReq = ram::WriteReq; @@ -136,10 +148,17 @@ pub proc HuffmanLiteralsDecoder< weights_dpd_rd_resp_r: chan in, weights_dpd_wr_req_s: chan out, weights_dpd_wr_resp_r: chan in, + weights_tmp_rd_req_s: chan out, weights_tmp_rd_resp_r: chan in, weights_tmp_wr_req_s: chan out, weights_tmp_wr_resp_r: chan in, + + weights_tmp2_rd_req_s: chan out, + weights_tmp2_rd_resp_r: chan in, + weights_tmp2_wr_req_s: chan out, + weights_tmp2_wr_resp_r: chan in, + weights_fse_rd_req_s: chan out, weights_fse_rd_resp_r: chan in, weights_fse_wr_req_s: chan out, @@ -215,6 +234,7 @@ pub proc HuffmanLiteralsDecoder< WEIGHTS_RAM_ADDR_WIDTH, WEIGHTS_RAM_DATA_WIDTH, WEIGHTS_RAM_NUM_PARTITIONS, WEIGHTS_DPD_RAM_ADDR_W, WEIGHTS_DPD_RAM_DATA_W, WEIGHTS_DPD_RAM_NUM_PARTITIONS, WEIGHTS_TMP_RAM_ADDR_W, WEIGHTS_TMP_RAM_DATA_W, WEIGHTS_TMP_RAM_NUM_PARTITIONS, + WEIGHTS_TMP2_RAM_ADDR_W, WEIGHTS_TMP2_RAM_DATA_W, WEIGHTS_TMP2_RAM_NUM_PARTITIONS, WEIGHTS_FSE_RAM_ADDR_W, WEIGHTS_FSE_RAM_DATA_W, WEIGHTS_FSE_RAM_NUM_PARTITIONS, >( weights_dec_req_r, weights_dec_resp_s, @@ -225,6 +245,7 @@ pub proc HuffmanLiteralsDecoder< weights_ram_wr_req_s, weights_ram_wr_resp_r, weights_dpd_rd_req_s, weights_dpd_rd_resp_r, weights_dpd_wr_req_s, weights_dpd_wr_resp_r, weights_tmp_rd_req_s, weights_tmp_rd_resp_r, weights_tmp_wr_req_s, weights_tmp_wr_resp_r, + weights_tmp2_rd_req_s, weights_tmp2_rd_resp_r, weights_tmp2_wr_req_s, weights_tmp2_wr_resp_r, weights_fse_rd_req_s, weights_fse_rd_resp_r, weights_fse_wr_req_s, weights_fse_wr_resp_r, ); @@ -314,6 +335,14 @@ const INST_WEIGHTS_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( INST_WEIGHTS_TMP_RAM_WORD_PARTITION_SIZE, INST_WEIGHTS_TMP_RAM_DATA_W ); +const INST_WEIGHTS_TMP2_RAM_DATA_W = u32:8; +const INST_WEIGHTS_TMP2_RAM_SIZE = u32:512; +const INST_WEIGHTS_TMP2_RAM_ADDR_W = std::clog2(INST_WEIGHTS_TMP2_RAM_SIZE); +const INST_WEIGHTS_TMP2_RAM_WORD_PARTITION_SIZE = INST_WEIGHTS_TMP2_RAM_DATA_W; +const INST_WEIGHTS_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions( + INST_WEIGHTS_TMP2_RAM_WORD_PARTITION_SIZE, INST_WEIGHTS_TMP2_RAM_DATA_W +); + proc HuffmanLiteralsDecoderInst { type Ctrl = HuffmanLiteralsDecoderReq; type Resp = HuffmanLiteralsDecoderResp; @@ -324,6 +353,7 @@ proc HuffmanLiteralsDecoderInst { type WeightsRamRdResp = ram::ReadResp; type WeightsRamWrReq = ram::WriteReq; type WeightsRamWrResp = ram::WriteResp; + type PrescanRamRdReq = ram::ReadReq; type PrescanRamRdResp = ram::ReadResp; type PrescanRamWrReq = ram::WriteReq; @@ -339,6 +369,11 @@ proc HuffmanLiteralsDecoderInst { type WeightsTmpRamWrReq = ram::WriteReq; type WeightsTmpRamWrResp = ram::WriteResp; + type WeightsTmp2RamRdReq = ram::ReadReq; + type WeightsTmp2RamRdResp = ram::ReadResp; + type WeightsTmp2RamWrReq = ram::WriteReq; + type WeightsTmp2RamWrResp = ram::WriteResp; + type WeightsFseRamRdReq = ram::ReadReq; type WeightsFseRamRdResp = ram::ReadResp; type WeightsFseRamWrReq = ram::WriteReq; @@ -350,32 +385,46 @@ proc HuffmanLiteralsDecoderInst { decoded_literals_s: chan out, axi_ar_s: chan out, axi_r_r: chan in, + jump_table_axi_ar_s: chan out, jump_table_axi_r_r: chan in, + weights_header_dec_axi_ar_s: chan out, weights_header_dec_axi_r_r: chan in, + weights_raw_dec_axi_ar_s: chan out, weights_raw_dec_axi_r_r: chan in, + weights_fse_lookup_dec_axi_ar_s: chan out, weights_fse_lookup_dec_axi_r_r: chan in, weights_fse_decoder_dec_axi_ar_s: chan out, weights_fse_decoder_dec_axi_r_r: chan in, + weights_ram_rd_req_s: chan out, weights_ram_rd_resp_r: chan in, weights_ram_wr_req_s: chan out, weights_ram_wr_resp_r: chan in, + prescan_ram_rd_req_s: chan out, prescan_ram_rd_resp_r: chan in, prescan_ram_wr_req_s: chan out, prescan_ram_wr_resp_r: chan in, + weights_dpd_rd_req_s: chan out, weights_dpd_rd_resp_r: chan in, weights_dpd_wr_req_s: chan out, weights_dpd_wr_resp_r: chan in, + weights_tmp_rd_req_s: chan out, weights_tmp_rd_resp_r: chan in, weights_tmp_wr_req_s: chan out, weights_tmp_wr_resp_r: chan in, + + weights_tmp2_rd_req_s: chan out, + weights_tmp2_rd_resp_r: chan in, + weights_tmp2_wr_req_s: chan out, + weights_tmp2_wr_resp_r: chan in, + weights_fse_rd_req_s: chan out, weights_fse_rd_resp_r: chan in, weights_fse_wr_req_s: chan out, @@ -385,10 +434,12 @@ proc HuffmanLiteralsDecoderInst { INST_AXI_DATA_W, INST_AXI_ADDR_W, INST_AXI_ID_W, INST_AXI_DEST_W, INST_WEIGHTS_DPD_RAM_ADDR_W, INST_WEIGHTS_DPD_RAM_DATA_W, INST_WEIGHTS_DPD_RAM_NUM_PARTITIONS, INST_WEIGHTS_TMP_RAM_ADDR_W, INST_WEIGHTS_TMP_RAM_DATA_W, INST_WEIGHTS_TMP_RAM_NUM_PARTITIONS, + INST_WEIGHTS_TMP2_RAM_ADDR_W, INST_WEIGHTS_TMP2_RAM_DATA_W, INST_WEIGHTS_TMP2_RAM_NUM_PARTITIONS, + INST_WEIGHTS_FSE_RAM_ADDR_W, INST_WEIGHTS_FSE_RAM_DATA_W, INST_WEIGHTS_FSE_RAM_NUM_PARTITIONS, INST_WEIGHTS_RAM_ADDR_WIDTH, INST_WEIGHTS_RAM_DATA_WIDTH, INST_WEIGHTS_RAM_NUM_PARTITIONS, INST_PRESCAN_RAM_ADDR_WIDTH, INST_PRESCAN_RAM_DATA_WIDTH, INST_PRESCAN_RAM_NUM_PARTITIONS - >( + >( ctrl_r, resp_s, decoded_literals_s, axi_ar_s, axi_r_r, @@ -405,6 +456,8 @@ proc HuffmanLiteralsDecoderInst { weights_dpd_wr_req_s, weights_dpd_wr_resp_r, weights_tmp_rd_req_s, weights_tmp_rd_resp_r, weights_tmp_wr_req_s, weights_tmp_wr_resp_r, + weights_tmp2_rd_req_s, weights_tmp2_rd_resp_r, + weights_tmp2_wr_req_s, weights_tmp2_wr_resp_r, weights_fse_rd_req_s, weights_fse_rd_resp_r, weights_fse_wr_req_s, weights_fse_wr_resp_r, ); @@ -438,8 +491,9 @@ const TEST_AXI_RAM_MODEL_NUM = u32:1; pub const TEST_WEIGHTS_RAM_SIZE = prescan::RAM_SIZE; pub const TEST_WEIGHTS_RAM_ADDR_WIDTH = WEIGHTS_ADDR_WIDTH; pub const TEST_WEIGHTS_RAM_DATA_WIDTH = WEIGHTS_DATA_WIDTH; + pub const TEST_WEIGHTS_RAM_NUM_PARTITIONS = WEIGHTS_NUM_PARTITIONS; -pub const TEST_WEIGHTS_WORD_PARTITION_SIZE = TEST_WEIGHTS_RAM_DATA_WIDTH; +pub const TEST_WEIGHTS_WORD_PARTITION_SIZE = WEIGHTS_PARTITION_WORD_SIZE; pub const TEST_PRESCAN_RAM_ADDR_WIDTH = PRESCAN_ADDR_WIDTH; pub const TEST_PRESCAN_RAM_DATA_WIDTH = PRESCAN_DATA_WIDTH; pub const TEST_PRESCAN_RAM_NUM_PARTITIONS = PRESCAN_NUM_PARTITIONS; @@ -470,6 +524,14 @@ const TEST_WEIGHTS_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( TEST_WEIGHTS_TMP_RAM_WORD_PARTITION_SIZE, TEST_WEIGHTS_TMP_RAM_DATA_W ); +const TEST_WEIGHTS_TMP2_RAM_DATA_W = u32:8; +const TEST_WEIGHTS_TMP2_RAM_SIZE = u32:512; +const TEST_WEIGHTS_TMP2_RAM_ADDR_W = std::clog2(TEST_WEIGHTS_TMP2_RAM_SIZE); +const TEST_WEIGHTS_TMP2_RAM_WORD_PARTITION_SIZE = TEST_WEIGHTS_TMP2_RAM_DATA_W; +const TEST_WEIGHTS_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_WEIGHTS_TMP2_RAM_WORD_PARTITION_SIZE, TEST_WEIGHTS_TMP2_RAM_DATA_W +); + type TestCtrl = HuffmanLiteralsDecoderReq; type TestResp = HuffmanLiteralsDecoderResp; type TestAxiR = axi::AxiR; @@ -506,6 +568,11 @@ type TestWeightsTmpRamRdResp = ram::ReadResp; type TestWeightsTmpRamWrReq = ram::WriteReq; type TestWeightsTmpRamWrResp = ram::WriteResp; +type TestWeightsTmp2RamRdReq = ram::ReadReq; +type TestWeightsTmp2RamRdResp = ram::ReadResp; +type TestWeightsTmp2RamWrReq = ram::WriteReq; +type TestWeightsTmp2RamWrResp = ram::WriteResp; + type TestWeightsFseRamRdReq = ram::ReadReq; type TestWeightsFseRamRdResp = ram::ReadResp; type TestWeightsFseRamWrReq = ram::WriteReq; @@ -774,6 +841,17 @@ proc HuffmanLiteralsDecoder_test { TEST_WEIGHTS_TMP_RAM_WORD_PARTITION_SIZE >(weights_tmp_rd_req_r, weights_tmp_rd_resp_s, weights_tmp_wr_req_r, weights_tmp_wr_resp_s); + let (weights_tmp2_rd_req_s, weights_tmp2_rd_req_r) = chan("weights_tmp_rd_req"); + let (weights_tmp2_rd_resp_s, weights_tmp2_rd_resp_r) = chan("weights_tmp_rd_resp"); + let (weights_tmp2_wr_req_s, weights_tmp2_wr_req_r) = chan("weights_tmp_wr_req"); + let (weights_tmp2_wr_resp_s, weights_tmp2_wr_resp_r) = chan("weights_tmp_wr_resp"); + + spawn ram::RamModel< + TEST_WEIGHTS_TMP2_RAM_DATA_W, + TEST_WEIGHTS_TMP2_RAM_SIZE, + TEST_WEIGHTS_TMP2_RAM_WORD_PARTITION_SIZE + >(weights_tmp2_rd_req_r, weights_tmp2_rd_resp_s, weights_tmp2_wr_req_r, weights_tmp2_wr_resp_s); + let (weights_fse_rd_req_s, weights_fse_rd_req_r) = chan("weights_tmp_rd_req"); let (weights_fse_rd_resp_s, weights_fse_rd_resp_r) = chan("weights_tmp_rd_resp"); let (weights_fse_wr_req_s, weights_fse_wr_req_r) = chan("weights_tmp_wr_req"); @@ -789,10 +867,11 @@ proc HuffmanLiteralsDecoder_test { TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_DEST_W, TEST_WEIGHTS_DPD_RAM_ADDR_W, TEST_WEIGHTS_DPD_RAM_DATA_W, TEST_WEIGHTS_DPD_RAM_NUM_PARTITIONS, TEST_WEIGHTS_TMP_RAM_ADDR_W, TEST_WEIGHTS_TMP_RAM_DATA_W, TEST_WEIGHTS_TMP_RAM_NUM_PARTITIONS, + TEST_WEIGHTS_TMP2_RAM_ADDR_W, TEST_WEIGHTS_TMP2_RAM_DATA_W, TEST_WEIGHTS_TMP2_RAM_NUM_PARTITIONS, TEST_WEIGHTS_FSE_RAM_ADDR_W, TEST_WEIGHTS_FSE_RAM_DATA_W, TEST_WEIGHTS_FSE_RAM_NUM_PARTITIONS, TEST_WEIGHTS_RAM_ADDR_WIDTH, TEST_WEIGHTS_RAM_DATA_WIDTH, TEST_WEIGHTS_RAM_NUM_PARTITIONS, TEST_PRESCAN_RAM_ADDR_WIDTH, TEST_PRESCAN_RAM_DATA_WIDTH, TEST_PRESCAN_RAM_NUM_PARTITIONS, - >( + >( ctrl_r, resp_s, decoded_literals_s, axi_ar_s, axi_r_r, jump_table_axi_ar_s, jump_table_axi_r_r, @@ -806,6 +885,7 @@ proc HuffmanLiteralsDecoder_test { prescan_ram_wr_req_s, prescan_ram_wr_resp_r, weights_dpd_rd_req_s, weights_dpd_rd_resp_r, weights_dpd_wr_req_s, weights_dpd_wr_resp_r, weights_tmp_rd_req_s, weights_tmp_rd_resp_r, weights_tmp_wr_req_s, weights_tmp_wr_resp_r, + weights_tmp2_rd_req_s, weights_tmp2_rd_resp_r, weights_tmp2_wr_req_s, weights_tmp2_wr_resp_r, weights_fse_rd_req_s, weights_fse_rd_resp_r, weights_fse_wr_req_s, weights_fse_wr_resp_r, ); diff --git a/xls/modules/zstd/huffman_prescan.x b/xls/modules/zstd/huffman_prescan.x index 6acf0284aa..47487a45c6 100644 --- a/xls/modules/zstd/huffman_prescan.x +++ b/xls/modules/zstd/huffman_prescan.x @@ -92,6 +92,8 @@ fn struct_to_bots_to_struct_qtest(x: WeightPreScanMetaData) -> bool { pub const RAM_SIZE = MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH * u32:8 / WEIGHT_LOG; pub const RAM_ADDR_WIDTH = {std::clog2(RAM_SIZE)}; pub const RAM_ACCESS_WIDTH = PARALLEL_ACCESS_WIDTH * WEIGHT_LOG; +const RAM_PARTITION_SIZE = RAM_ACCESS_WIDTH / u32:8; +const RAM_NUM_PARTITIONS = ram::num_partitions(RAM_PARTITION_SIZE, RAM_ACCESS_WIDTH); const MAX_RAM_ADDR = MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH; enum WeightPreScanFSM: u2 { @@ -124,7 +126,7 @@ pub proc WeightPreScan type OutData = WeightPreScanOutput; - type ReadReq = ram::ReadReq; + type ReadReq = ram::ReadReq; type ReadResp = ram::ReadResp; type InternalRamAddr = uN[RAM_ADDR_WIDTH]; @@ -241,7 +243,7 @@ pub proc WeightPreScan let external_ram_req = ReadReq { addr: addr, - mask: u1:1, + mask: !uN[RAM_NUM_PARTITIONS]:0, }; let tok3 = send_if(tok, read_req_s, send_addr, external_ram_req); if send_addr { @@ -323,9 +325,9 @@ proc Prescan_test{ type PrescanOut = WeightPreScanOutput; - type ReadReq = ram::ReadReq; + type ReadReq = ram::ReadReq; type ReadResp = ram::ReadResp; - type WriteReq = ram::WriteReq; + type WriteReq = ram::WriteReq; type WriteResp = ram::WriteResp<>; type InternalReadReq = ram::ReadReq; @@ -338,6 +340,7 @@ proc Prescan_test{ external_ram_resp: chan in; start_prescan: chan out; prescan_response: chan in; + init{()} config (terminator: chan out) { // Emulate external memory @@ -345,7 +348,7 @@ proc Prescan_test{ let (RAMExternalWriteResp_s, RAMExternalWriteResp_r) = chan("Write_channel_resp"); let (RAMExternalReadReq_s, RAMExternalReadReq_r) = chan("Read_channel_req"); let (RAMExternalReadResp_s, RAMExternalReadResp_r) = chan("Read_channel_resp"); - spawn ram::RamModel( + spawn ram::RamModel( RAMExternalReadReq_r, RAMExternalReadResp_s, RAMExternalWriteReq_r, RAMExternalWriteResp_s ); @@ -379,7 +382,7 @@ proc Prescan_test{ let external_w_req = WriteReq { addr: i as uN[RAM_ADDR_WIDTH], data: data_to_send, - mask: u1:1 + mask: !uN[RAM_NUM_PARTITIONS]:0 }; send(tok, external_ram_req, external_w_req); recv(tok, external_ram_resp); diff --git a/xls/modules/zstd/huffman_weights_dec.x b/xls/modules/zstd/huffman_weights_dec.x index 6c7b3aed36..287cf7ba17 100644 --- a/xls/modules/zstd/huffman_weights_dec.x +++ b/xls/modules/zstd/huffman_weights_dec.x @@ -22,7 +22,7 @@ import xls.modules.zstd.memory.axi_ram; import xls.modules.zstd.memory.mem_reader; import xls.modules.zstd.ram_mux; import xls.modules.zstd.refilling_shift_buffer; -import xls.modules.zstd.fse_lookup_dec; +import xls.modules.zstd.comp_lookup_dec; import xls.modules.zstd.fse_table_creator; import xls.modules.zstd.math; @@ -881,8 +881,8 @@ proc HuffmanFseWeightsDecoder< type WeightsRamWrReq = ram::WriteReq; type WeightsRamWrResp = ram::WriteResp; - type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; - type FseLookupDecoderResp = fse_lookup_dec::FseLookupDecoderResp; + type CompLookupDecoderReq = comp_lookup_dec::CompLookupDecoderReq; + type CompLookupDecoderResp = comp_lookup_dec::CompLookupDecoderResp; type DpdRamRdReq = ram::ReadReq; type DpdRamRdResp = ram::ReadResp; @@ -927,8 +927,8 @@ proc HuffmanFseWeightsDecoder< fd_rsb_flushing_done_r: chan<()> in; // FSE Lookup Decoder - fld_req_s: chan out; - fld_resp_r: chan in; + fld_req_s: chan out; + fld_resp_r: chan in; // Huffman FSE Decoder fd_ctrl_s: chan out; @@ -976,7 +976,7 @@ proc HuffmanFseWeightsDecoder< ) { const CHANNEL_DEPTH = u32:1; - // FseLookupDecoder + // CompLookupDecoder let (fld_rsb_start_req_s, fld_rsb_start_req_r) = chan("fd_rsb_start_req"); let (fld_rsb_stop_flush_req_s, fld_rsb_stop_flush_req_r) = chan<(), CHANNEL_DEPTH>("fd_rsb_stop_flush_req"); let (fld_rsb_ctrl_s, fld_rsb_ctrl_r) = chan("fd_rsb_ctrl"); @@ -990,10 +990,10 @@ proc HuffmanFseWeightsDecoder< fld_rsb_flushing_done_s, ); - let (fld_req_s, fld_req_r) = chan("fse_req"); - let (fld_resp_s, fld_resp_r) = chan("fse_resp"); + let (fld_req_s, fld_req_r) = chan("fse_req"); + let (fld_resp_s, fld_resp_r) = chan("fse_resp"); - spawn fse_lookup_dec::FseLookupDecoder< + spawn comp_lookup_dec::CompLookupDecoder< AXI_DATA_W, DPD_RAM_DATA_W, DPD_RAM_ADDR_W, DPD_RAM_NUM_PARTITIONS, TMP_RAM_DATA_W, TMP_RAM_ADDR_W, TMP_RAM_NUM_PARTITIONS, @@ -1059,7 +1059,7 @@ proc HuffmanFseWeightsDecoder< let tok = send(tok, fld_rsb_start_req_s, fld_rsb_start_req); trace_fmt!("[FSE] Sent refilling shift buffer start request {:#x}", fld_rsb_start_req); - let fld_req = FseLookupDecoderReq {}; + let fld_req = CompLookupDecoderReq {}; let tok = send(tok, fld_req_s, fld_req); trace_fmt!("[FSE] Sent FSE lookup decoding request {:#x}", fld_req); @@ -1266,18 +1266,15 @@ pub proc HuffmanWeightsDecoder< let (raw_weights_ram_wr_resp_s, raw_weights_ram_wr_resp_r) = chan("raw_weights_ram_wr_resp"); // Internal RAM Write interface with decoded Fse Huffman Tree Description - let (fse_weights_ram_wr_req_s, fse_weights_ram_wr_req_r) = chan("raw_weights_ram_wr_req"); - let (fse_weights_ram_wr_resp_s, fse_weights_ram_wr_resp_r) = chan("raw_weights_ram_wr_resp"); - - let (fse_lookup_weights_ram_wr_req_s, fse_lookup_weights_ram_wr_req_r) = chan("fse_weights_ram_wr_req"); - let (fse_lookup_weights_ram_wr_resp_s, fse_lookup_weights_ram_wr_resp_r) = chan("fse_weights_ram_wr_resp"); - let (fse_decoder_weights_ram_wr_req_s, fse_decoder_weights_ram_wr_req_r) = chan("fse_weights_ram_wr_req"); - let (fse_decoder_weights_ram_wr_resp_s, fse_decoder_weights_ram_wr_resp_r) = chan("fse_weights_ram_wr_resp"); + let (fse_weights_ram_wr_req_s, fse_weights_ram_wr_req_r) = chan("fse_weights_ram_wr_req"); + let (fse_weights_ram_wr_resp_s, fse_weights_ram_wr_resp_r) = chan("fse_weights_ram_wr_resp_s"); let (raw_weights_ram_rd_req_s, raw_weights_ram_rd_req_r) = chan("raw_weights_ram_rd_req"); let (raw_weights_ram_rd_resp_s, raw_weights_ram_rd_resp_r) = chan("raw_weights_ram_rd_resp"); + let (fse_weights_ram_rd_req_s, fse_weights_ram_rd_req_r) = chan("fse_weights_ram_rd_req"); let (fse_weights_ram_rd_resp_s, fse_weights_ram_rd_resp_r) = chan("fse_weights_ram_rd_resp"); + let (weights_ram_rd_req_s, weights_ram_rd_req_r) = chan("weights_ram_rd_req"); let (weights_ram_rd_resp_s, weights_ram_rd_resp_r) = chan("weights_ram_rd_resp"); @@ -1658,247 +1655,247 @@ const TESTCASES_FSE: (u8[32], u8[128])[7] = [ ( u8[32]:[ u8:10, - u8:0xC0, u8:0x25, u8:0x1D, u8:0x49, u8:0x6E, u8:0xC2, u8:0xFF, u8:0xFF, + u8:0xC0, u8:0x25, u8:0x1D, u8:0x49, u8:0x6E, u8:0xC2, u8:0xFF, u8:0xFF, u8:0xEE, u8:0x06, u8:0, ... ], u8[128]:[ - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, u8:0x01, u8:0x12, u8:0x34, u8:0x56, u8:0, ... ], ), ( u8[32]:[ u8:15, - u8:0xC0, u8:0x25, u8:0x1D, u8:0x9B, u8:0x1E, u8:0xAD, u8:0xFE, u8:0xFF, + u8:0xC0, u8:0x25, u8:0x1D, u8:0x9B, u8:0x1E, u8:0xAD, u8:0xFE, u8:0xFF, u8:0x7F, u8:0x67, u8:0xFE, u8:0xD3, u8:0xFF, u8:0xCE, u8:0x05, u8:0, ... ], u8[128]:[ - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x10, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x30, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x50, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x01, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x02, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x04, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x10, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x30, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x50, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x01, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x02, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x04, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, u8:0x00, u8:0x06, u8:0, ... ], ), ( u8[32]:[ u8:23, - u8:0x90, u8:0x25, u8:0x49, u8:0x3A, u8:0xEB, u8:0x3B, u8:0xBD, u8:0x7E, - u8:0xD6, u8:0x5D, u8:0x3C, u8:0xB3, u8:0x66, u8:0x77, u8:0xA8, u8:0xBB, + u8:0x90, u8:0x25, u8:0x49, u8:0x3A, u8:0xEB, u8:0x3B, u8:0xBD, u8:0x7E, + u8:0xD6, u8:0x5D, u8:0x3C, u8:0xB3, u8:0x66, u8:0x77, u8:0xA8, u8:0xBB, u8:0x25, u8:0x76, u8:0xBA, u8:0xFF, u8:0x20, u8:0xA8, u8:0x01, u8:0, ... ], u8[128]:[ - u8:0x00, u8:0x10, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x02, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x30, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x04, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x50, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x06, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x70, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x08, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x01, u8:0x00, + u8:0x00, u8:0x10, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x02, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x30, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x04, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x50, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x06, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x70, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x08, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x01, u8:0x00, u8:0x00, u8:0x90, u8:0, ... ], - ), + ), ( u8[32]:[ u8:8, u8:0xF0, u8:0x39, u8:0xFF, u8:0x23, u8:0x45, u8:0x55, u8:0xCF, u8:0x99, u8:0, ... ], u8[128]:[ - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x20, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x01, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x10, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x20, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x01, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x10, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, u8:0x00, u8:0x03, u8:0, ... ], ), ( u8[32]:[ u8:24, - u8:0xB0, u8:0xA5, u8:0x92, u8:0x0E, u8:0x14, u8:0x3B, u8:0x7B, u8:0x58, - u8:0xED, u8:0xB0, u8:0x1D, u8:0x9C, u8:0x43, u8:0x82, u8:0xC5, u8:0x8E, + u8:0xB0, u8:0xA5, u8:0x92, u8:0x0E, u8:0x14, u8:0x3B, u8:0x7B, u8:0x58, + u8:0xED, u8:0xB0, u8:0x1D, u8:0x9C, u8:0x43, u8:0x82, u8:0xC5, u8:0x8E, u8:0xD3, u8:0x38, u8:0x36, u8:0x87, u8:0x73, u8:0x08, u8:0x58, u8:0x02, u8:0, ... ], u8[128]:[ - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x20, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x10, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x10, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x07, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x10, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x05, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x10, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x02, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x01, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x01, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x80, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x01, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x60, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x01, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x20, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x10, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x10, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x07, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x10, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x05, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x10, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x02, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x01, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x01, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x80, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x01, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x60, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x01, u8:0x00, u8:0x00, u8:0x00, u8:0x00, u8:0x00, u8:0x30, u8:0, ... ], - ), + ), ( u8[32]:[ u8:9, - u8:0xE0, u8:0xE9, u8:0x40, u8:0x0D, u8:0x80, u8:0x0A, u8:0x10, u8:0x59, + u8:0xE0, u8:0xE9, u8:0x40, u8:0x0D, u8:0x80, u8:0x0A, u8:0x10, u8:0x59, u8:0x04, u8:0, ... ], u8[128]:[ - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x03, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x20, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x01, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x03, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x20, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x01, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, u8:0x00, u8:0x00, u8:0x00, u8:0x00, u8:0x10, u8:0, ... ], ), ( u8[32]:[ u8:9, - u8:0xF0, u8:0x19, u8:0x03, u8:0x23, u8:0x7D, u8:0x9F, u8:0xD7, u8:0xB5, + u8:0xF0, u8:0x19, u8:0x03, u8:0x23, u8:0x7D, u8:0x9F, u8:0xD7, u8:0xB5, u8:0x06, u8:0, ... ], u8[128]:[ - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x10, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x30, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x01, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, - u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x10, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x30, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x01, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, + u8:0x00, u8:0x00, u8:0x00, u8:0x00, u8:0x00, u8:0x02, u8:0, ... ], ), diff --git a/xls/modules/zstd/literals_decoder.x b/xls/modules/zstd/literals_decoder.x index 4aab622bfd..f242d9bd68 100644 --- a/xls/modules/zstd/literals_decoder.x +++ b/xls/modules/zstd/literals_decoder.x @@ -614,14 +614,22 @@ pub proc LiteralsDecoder< HISTORY_BUFFER_SIZE_KB: u32, // AXI parameters AXI_DATA_W: u32, AXI_ADDR_W: u32, AXI_ID_W: u32, AXI_DEST_W: u32, + + HUFFMAN_WEIGHTS_DPD_RAM_ADDR_WIDTH: u32, HUFFMAN_WEIGHTS_DPD_RAM_DATA_WIDTH: u32, HUFFMAN_WEIGHTS_DPD_RAM_NUM_PARTITIONS: u32, + HUFFMAN_WEIGHTS_TMP_RAM_ADDR_WIDTH: u32, HUFFMAN_WEIGHTS_TMP_RAM_DATA_WIDTH: u32, HUFFMAN_WEIGHTS_TMP_RAM_NUM_PARTITIONS: u32, + HUFFMAN_WEIGHTS_TMP2_RAM_ADDR_WIDTH: u32, HUFFMAN_WEIGHTS_TMP2_RAM_DATA_WIDTH: u32, HUFFMAN_WEIGHTS_TMP2_RAM_NUM_PARTITIONS: u32, + HUFFMAN_WEIGHTS_FSE_RAM_ADDR_WIDTH: u32, HUFFMAN_WEIGHTS_FSE_RAM_DATA_WIDTH: u32, HUFFMAN_WEIGHTS_FSE_RAM_NUM_PARTITIONS: u32, + // Huffman weights memory parameters HUFFMAN_WEIGHTS_RAM_ADDR_WIDTH: u32 = {huffman_literals_dec::WEIGHTS_ADDR_WIDTH}, HUFFMAN_WEIGHTS_RAM_DATA_WIDTH: u32 = {huffman_literals_dec::WEIGHTS_DATA_WIDTH}, HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS: u32 = {huffman_literals_dec::WEIGHTS_NUM_PARTITIONS}, + // Huffman prescan memory parameters HUFFMAN_PRESCAN_RAM_ADDR_WIDTH: u32 = {huffman_literals_dec::PRESCAN_ADDR_WIDTH}, HUFFMAN_PRESCAN_RAM_DATA_WIDTH: u32 = {huffman_literals_dec::PRESCAN_DATA_WIDTH}, HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS: u32 = {huffman_literals_dec::PRESCAN_NUM_PARTITIONS}, + // Literals buffer memory parameters LITERALS_BUFFER_RAM_ADDR_WIDTH: u32 = {parallel_rams::ram_addr_width(HISTORY_BUFFER_SIZE_KB)}, LITERALS_BUFFER_RAM_SIZE: u32 = {parallel_rams::ram_size(HISTORY_BUFFER_SIZE_KB)}, @@ -645,11 +653,32 @@ pub proc LiteralsDecoder< type HuffmanWeightsReadResp = ram::ReadResp; type HuffmanWeightsWriteReq = ram::WriteReq; type HuffmanWeightsWriteResp = ram::WriteResp; + type HuffmanPrescanReadReq = ram::ReadReq; type HuffmanPrescanReadResp = ram::ReadResp; type HuffmanPrescanWriteReq = ram::WriteReq; type HuffmanPrescanWriteResp = ram::WriteResp; + type HuffmanWeightsDpdRamRdReq = ram::ReadReq; + type HuffmanWeightsDpdRamRdResp = ram::ReadResp; + type HuffmanWeightsDpdRamWrReq = ram::WriteReq; + type HuffmanWeightsDpdRamWrResp = ram::WriteResp; + + type HuffmanWeightsTmpRamRdReq = ram::ReadReq; + type HuffmanWeightsTmpRamRdResp = ram::ReadResp; + type HuffmanWeightsTmpRamWrReq = ram::WriteReq; + type HuffmanWeightsTmpRamWrResp = ram::WriteResp; + + type HuffmanWeightsTmp2RamRdReq = ram::ReadReq; + type HuffmanWeightsTmp2RamRdResp = ram::ReadResp; + type HuffmanWeightsTmp2RamWrReq = ram::WriteReq; + type HuffmanWeightsTmp2RamWrResp = ram::WriteResp; + + type HuffmanWeightsFseRamRdReq = ram::ReadReq; + type HuffmanWeightsFseRamRdResp = ram::ReadResp; + type HuffmanWeightsFseRamWrReq = ram::WriteReq; + type HuffmanWeightsFseRamWrResp = ram::WriteResp; + type HeaderResp = literals_block_header_dec::LiteralsHeaderDecoderResp; config ( @@ -678,8 +707,10 @@ pub proc LiteralsDecoder< huffman_weights_raw_axi_r_r: chan in, // AXI Huffman Weights FSE Decoder (manager) - huffman_weights_fse_axi_ar_s: chan out, - huffman_weights_fse_axi_r_r: chan in, + huffman_weights_fse_lookup_dec_axi_ar_s: chan out, + huffman_weights_fse_lookup_dec_axi_r_r: chan in, + huffman_weights_fse_decoder_dec_axi_ar_s: chan out, + huffman_weights_fse_decoder_dec_axi_r_r: chan in, // Literals Decoder control lit_ctrl_req_r: chan in, @@ -734,6 +765,26 @@ pub proc LiteralsDecoder< huffman_lit_prescan_mem_rd_resp_r: chan in, huffman_lit_prescan_mem_wr_req_s: chan out, huffman_lit_prescan_mem_wr_resp_r: chan in, + + huffman_lit_weights_dpd_rd_req_s: chan out, + huffman_lit_weights_dpd_rd_resp_r: chan in, + huffman_lit_weights_dpd_wr_req_s: chan out, + huffman_lit_weights_dpd_wr_resp_r: chan in, + + huffman_lit_weights_tmp_rd_req_s: chan out, + huffman_lit_weights_tmp_rd_resp_r: chan in, + huffman_lit_weights_tmp_wr_req_s: chan out, + huffman_lit_weights_tmp_wr_resp_r: chan in, + + huffman_lit_weights_tmp2_rd_req_s: chan out, + huffman_lit_weights_tmp2_rd_resp_r: chan in, + huffman_lit_weights_tmp2_wr_req_s: chan out, + huffman_lit_weights_tmp2_wr_resp_r: chan in, + + huffman_lit_weights_fse_rd_req_s: chan out, + huffman_lit_weights_fse_rd_resp_r: chan in, + huffman_lit_weights_fse_wr_req_s: chan out, + huffman_lit_weights_fse_wr_resp_r: chan in, ) { type HeaderReq = literals_block_header_dec::LiteralsHeaderDecoderReq; type HeaderResp = literals_block_header_dec::LiteralsHeaderDecoderResp; @@ -798,6 +849,10 @@ pub proc LiteralsDecoder< spawn huffman_literals_dec::HuffmanLiteralsDecoder< AXI_DATA_W, AXI_ADDR_W, AXI_ID_W, AXI_DEST_W, + HUFFMAN_WEIGHTS_DPD_RAM_ADDR_WIDTH, HUFFMAN_WEIGHTS_DPD_RAM_DATA_WIDTH, HUFFMAN_WEIGHTS_DPD_RAM_NUM_PARTITIONS, + HUFFMAN_WEIGHTS_TMP_RAM_ADDR_WIDTH, HUFFMAN_WEIGHTS_TMP_RAM_DATA_WIDTH, HUFFMAN_WEIGHTS_TMP_RAM_NUM_PARTITIONS, + HUFFMAN_WEIGHTS_TMP2_RAM_ADDR_WIDTH, HUFFMAN_WEIGHTS_TMP2_RAM_DATA_WIDTH, HUFFMAN_WEIGHTS_TMP2_RAM_NUM_PARTITIONS, + HUFFMAN_WEIGHTS_FSE_RAM_ADDR_WIDTH, HUFFMAN_WEIGHTS_FSE_RAM_DATA_WIDTH, HUFFMAN_WEIGHTS_FSE_RAM_NUM_PARTITIONS, HUFFMAN_WEIGHTS_RAM_ADDR_WIDTH, HUFFMAN_WEIGHTS_RAM_DATA_WIDTH, HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS, HUFFMAN_PRESCAN_RAM_ADDR_WIDTH, HUFFMAN_PRESCAN_RAM_DATA_WIDTH, HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS >( @@ -806,11 +861,20 @@ pub proc LiteralsDecoder< huffman_jump_table_axi_ar_s, huffman_jump_table_axi_r_r, huffman_weights_header_axi_ar_s, huffman_weights_header_axi_r_r, huffman_weights_raw_axi_ar_s, huffman_weights_raw_axi_r_r, - huffman_weights_fse_axi_ar_s, huffman_weights_fse_axi_r_r, + huffman_weights_fse_lookup_dec_axi_ar_s, huffman_weights_fse_lookup_dec_axi_r_r, + huffman_weights_fse_decoder_dec_axi_ar_s, huffman_weights_fse_decoder_dec_axi_r_r, huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_resp_r, huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_resp_r, huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_resp_r, huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_resp_r, + huffman_lit_weights_dpd_rd_req_s, huffman_lit_weights_dpd_rd_resp_r, + huffman_lit_weights_dpd_wr_req_s, huffman_lit_weights_dpd_wr_resp_r, + huffman_lit_weights_tmp_rd_req_s, huffman_lit_weights_tmp_rd_resp_r, + huffman_lit_weights_tmp_wr_req_s, huffman_lit_weights_tmp_wr_resp_r, + huffman_lit_weights_tmp2_rd_req_s, huffman_lit_weights_tmp2_rd_resp_r, + huffman_lit_weights_tmp2_wr_req_s, huffman_lit_weights_tmp2_wr_resp_r, + huffman_lit_weights_fse_rd_req_s, huffman_lit_weights_fse_rd_resp_r, + huffman_lit_weights_fse_wr_req_s, huffman_lit_weights_fse_wr_resp_r, ); // Literals Buffer @@ -856,10 +920,42 @@ const INST_AXI_DEST_W:u32 = u32:4; const INST_HUFFMAN_WEIGHTS_RAM_ADDR_WIDTH = huffman_literals_dec::INST_WEIGHTS_RAM_ADDR_WIDTH; const INST_HUFFMAN_WEIGHTS_RAM_DATA_WIDTH = huffman_literals_dec::INST_WEIGHTS_RAM_DATA_WIDTH; const INST_HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS = huffman_literals_dec::INST_WEIGHTS_RAM_NUM_PARTITIONS; + const INST_HUFFMAN_PRESCAN_RAM_ADDR_WIDTH = huffman_literals_dec::INST_PRESCAN_RAM_ADDR_WIDTH; const INST_HUFFMAN_PRESCAN_RAM_DATA_WIDTH = huffman_literals_dec::INST_PRESCAN_RAM_DATA_WIDTH; const INST_HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS = huffman_literals_dec::INST_PRESCAN_RAM_NUM_PARTITIONS; +const INST_HUFFMAN_WEIGHTS_DPD_RAM_DATA_WIDTH = u32:16; +const INST_HUFFMAN_WEIGHTS_DPD_RAM_SIZE = u32:256; +const INST_HUFFMAN_WEIGHTS_DPD_RAM_ADDR_WIDTH = std::clog2(INST_HUFFMAN_WEIGHTS_DPD_RAM_SIZE); +const INST_HUFFMAN_WEIGHTS_DPD_RAM_WORD_PARTITION_SIZE = INST_HUFFMAN_WEIGHTS_DPD_RAM_DATA_WIDTH; +const INST_HUFFMAN_WEIGHTS_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( + INST_HUFFMAN_WEIGHTS_DPD_RAM_WORD_PARTITION_SIZE, INST_HUFFMAN_WEIGHTS_DPD_RAM_DATA_WIDTH +); +const INST_HUFFMAN_WEIGHTS_FSE_RAM_DATA_WIDTH = u32:32; +const INST_HUFFMAN_WEIGHTS_FSE_RAM_SIZE = u32:256; +const INST_HUFFMAN_WEIGHTS_FSE_RAM_ADDR_WIDTH = std::clog2(INST_HUFFMAN_WEIGHTS_FSE_RAM_SIZE); +const INST_HUFFMAN_WEIGHTS_FSE_RAM_WORD_PARTITION_SIZE = INST_HUFFMAN_WEIGHTS_FSE_RAM_DATA_WIDTH / u32:3; +const INST_HUFFMAN_WEIGHTS_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( + INST_HUFFMAN_WEIGHTS_FSE_RAM_WORD_PARTITION_SIZE, INST_HUFFMAN_WEIGHTS_FSE_RAM_DATA_WIDTH +); + +const INST_HUFFMAN_WEIGHTS_TMP_RAM_DATA_WIDTH = u32:16; +const INST_HUFFMAN_WEIGHTS_TMP_RAM_SIZE = u32:256; +const INST_HUFFMAN_WEIGHTS_TMP_RAM_ADDR_WIDTH = std::clog2(INST_HUFFMAN_WEIGHTS_TMP_RAM_SIZE); +const INST_HUFFMAN_WEIGHTS_TMP_RAM_WORD_PARTITION_SIZE = INST_HUFFMAN_WEIGHTS_TMP_RAM_DATA_WIDTH; +const INST_HUFFMAN_WEIGHTS_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( + INST_HUFFMAN_WEIGHTS_TMP_RAM_WORD_PARTITION_SIZE, INST_HUFFMAN_WEIGHTS_TMP_RAM_DATA_WIDTH +); + +const INST_HUFFMAN_WEIGHTS_TMP2_RAM_DATA_WIDTH = u32:8; +const INST_HUFFMAN_WEIGHTS_TMP2_RAM_SIZE = u32:512; +const INST_HUFFMAN_WEIGHTS_TMP2_RAM_ADDR_WIDTH = std::clog2(INST_HUFFMAN_WEIGHTS_TMP2_RAM_SIZE); +const INST_HUFFMAN_WEIGHTS_TMP2_RAM_WORD_PARTITION_SIZE = INST_HUFFMAN_WEIGHTS_TMP2_RAM_DATA_WIDTH; +const INST_HUFFMAN_WEIGHTS_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions( + INST_HUFFMAN_WEIGHTS_TMP2_RAM_WORD_PARTITION_SIZE, INST_HUFFMAN_WEIGHTS_TMP2_RAM_DATA_WIDTH +); + proc LiteralsDecoderInst { type ReadReq = ram::ReadReq; type ReadResp = ram::ReadResp; @@ -877,11 +973,32 @@ proc LiteralsDecoderInst { type HuffmanWeightsReadResp = ram::ReadResp; type HuffmanWeightsWriteReq = ram::WriteReq; type HuffmanWeightsWriteResp = ram::WriteResp; - type HuffmanPrescanReadReq = ram::ReadReq; + + type HuffmanPrescanReadReq = ram::ReadReq; type HuffmanPrescanReadResp = ram::ReadResp; type HuffmanPrescanWriteReq = ram::WriteReq; type HuffmanPrescanWriteResp = ram::WriteResp; + type HuffmanWeightsDpdRamRdReq = ram::ReadReq; + type HuffmanWeightsDpdRamRdResp = ram::ReadResp; + type HuffmanWeightsDpdRamWrReq = ram::WriteReq; + type HuffmanWeightsDpdRamWrResp = ram::WriteResp; + + type HuffmanWeightsTmpRamRdReq = ram::ReadReq; + type HuffmanWeightsTmpRamRdResp = ram::ReadResp; + type HuffmanWeightsTmpRamWrReq = ram::WriteReq; + type HuffmanWeightsTmpRamWrResp = ram::WriteResp; + + type HuffmanWeightsTmp2RamRdReq = ram::ReadReq; + type HuffmanWeightsTmp2RamRdResp = ram::ReadResp; + type HuffmanWeightsTmp2RamWrReq = ram::WriteReq; + type HuffmanWeightsTmp2RamWrResp = ram::WriteResp; + + type HuffmanWeightsFseRamRdReq = ram::ReadReq; + type HuffmanWeightsFseRamRdResp = ram::ReadResp; + type HuffmanWeightsFseRamWrReq = ram::WriteReq; + type HuffmanWeightsFseRamWrResp = ram::WriteResp; + type HeaderResp = literals_block_header_dec::LiteralsHeaderDecoderResp; config ( @@ -901,7 +1018,7 @@ proc LiteralsDecoderInst { huffman_jump_table_axi_ar_s: chan out, huffman_jump_table_axi_r_r: chan in, - // AXI Huffman Weights header Decoder (manager) + // AXI Huffman Weights Header Decoder (manager) huffman_weights_header_axi_ar_s: chan out, huffman_weights_header_axi_r_r: chan in, @@ -910,8 +1027,10 @@ proc LiteralsDecoderInst { huffman_weights_raw_axi_r_r: chan in, // AXI Huffman Weights FSE Decoder (manager) - huffman_weights_fse_axi_ar_s: chan out, - huffman_weights_fse_axi_r_r: chan in, + huffman_weights_fse_lookup_dec_axi_ar_s: chan out, + huffman_weights_fse_lookup_dec_axi_r_r: chan in, + huffman_weights_fse_decoder_dec_axi_ar_s: chan out, + huffman_weights_fse_decoder_dec_axi_r_r: chan in, // Literals Decoder control lit_ctrl_req_r: chan in, @@ -922,6 +1041,7 @@ proc LiteralsDecoderInst { lit_buf_ctrl_r: chan in, lit_buf_out_s: chan out, + // Internal memory rd_req_m0_s: chan out, rd_req_m1_s: chan out, rd_req_m2_s: chan out, @@ -964,22 +1084,59 @@ proc LiteralsDecoderInst { huffman_lit_prescan_mem_rd_req_s: chan out, huffman_lit_prescan_mem_rd_resp_r: chan in, huffman_lit_prescan_mem_wr_req_s: chan out, - huffman_lit_prescan_mem_wr_resp_r: chan in + huffman_lit_prescan_mem_wr_resp_r: chan in, + + huffman_lit_weights_dpd_rd_req_s: chan out, + huffman_lit_weights_dpd_rd_resp_r: chan in, + huffman_lit_weights_dpd_wr_req_s: chan out, + huffman_lit_weights_dpd_wr_resp_r: chan in, + + huffman_lit_weights_tmp_rd_req_s: chan out, + huffman_lit_weights_tmp_rd_resp_r: chan in, + huffman_lit_weights_tmp_wr_req_s: chan out, + huffman_lit_weights_tmp_wr_resp_r: chan in, + + huffman_lit_weights_tmp2_rd_req_s: chan out, + huffman_lit_weights_tmp2_rd_resp_r: chan in, + huffman_lit_weights_tmp2_wr_req_s: chan out, + huffman_lit_weights_tmp2_wr_resp_r: chan in, + + huffman_lit_weights_fse_rd_req_s: chan out, + huffman_lit_weights_fse_rd_resp_r: chan in, + huffman_lit_weights_fse_wr_req_s: chan out, + huffman_lit_weights_fse_wr_resp_r: chan in, ) { - spawn LiteralsDecoder ( + > ( + // AXI Literals Header Decoder (manager) lit_header_axi_ar_s, lit_header_axi_r_r, + // AXI Raw Literals Decoder (manager) raw_lit_axi_ar_s, raw_lit_axi_r_r, + // AXI Huffman Literals Decoder (manager) huffman_lit_axi_ar_s, huffman_lit_axi_r_r, + // AXI Huffman Jump Table Decoder (manager) huffman_jump_table_axi_ar_s, huffman_jump_table_axi_r_r, + // AXI Huffman Weights Header Decoder (manager) huffman_weights_header_axi_ar_s, huffman_weights_header_axi_r_r, + // AXI Huffman Weights RAW Decoder (manager) huffman_weights_raw_axi_ar_s, huffman_weights_raw_axi_r_r, - huffman_weights_fse_axi_ar_s, huffman_weights_fse_axi_r_r, + // AXI Huffman Weights FSE Decoder (manager) + huffman_weights_fse_lookup_dec_axi_ar_s, huffman_weights_fse_lookup_dec_axi_r_r, + huffman_weights_fse_decoder_dec_axi_ar_s, huffman_weights_fse_decoder_dec_axi_r_r, + // Literals Decoder control lit_ctrl_req_r, lit_ctrl_resp_s, lit_ctrl_header_s, + // Literals Decoder output control lit_buf_ctrl_r, lit_buf_out_s, + // Internal memory 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, @@ -988,10 +1145,24 @@ proc LiteralsDecoderInst { 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, + // Huffman weights memory huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_resp_r, huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_resp_r, + // Huffman prescan memory huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_resp_r, huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_resp_r, + + huffman_lit_weights_dpd_rd_req_s, huffman_lit_weights_dpd_rd_resp_r, + huffman_lit_weights_dpd_wr_req_s, huffman_lit_weights_dpd_wr_resp_r, + + huffman_lit_weights_tmp_rd_req_s, huffman_lit_weights_tmp_rd_resp_r, + huffman_lit_weights_tmp_wr_req_s, huffman_lit_weights_tmp_wr_resp_r, + + huffman_lit_weights_tmp2_rd_req_s, huffman_lit_weights_tmp2_rd_resp_r, + huffman_lit_weights_tmp2_wr_req_s, huffman_lit_weights_tmp2_wr_resp_r, + + huffman_lit_weights_fse_rd_req_s, huffman_lit_weights_fse_rd_resp_r, + huffman_lit_weights_fse_wr_req_s, huffman_lit_weights_fse_wr_resp_r, ); } @@ -1053,6 +1224,46 @@ const TEST_HUFFMAN_WEIGHTS_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::Sim const TEST_HUFFMAN_WEIGHTS_RAM_MODEL_INITIALIZED = true; const TEST_HUFFMAN_WEIGHTS_RAM_MODEL_ASSERT_VALID_READ = true; +const TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_DATA_WIDTH = u32:16; +const TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_SIZE = u32:256; +const TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_ADDR_WIDTH = std::clog2(TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_SIZE); +const TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_WORD_PARTITION_SIZE = TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_DATA_WIDTH; +const TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_NUM_PARTITIONS = ram::num_partitions( + TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_WORD_PARTITION_SIZE, TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_DATA_WIDTH); +const TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_INITIALIZED = true; +const TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_ASSERT_VALID_READ = true; + +const TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_DATA_WIDTH = u32:32; +const TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_SIZE = u32:256; +const TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_ADDR_WIDTH = std::clog2(TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_SIZE); +const TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_WORD_PARTITION_SIZE = TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_DATA_WIDTH / u32:3; +const TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_NUM_PARTITIONS = ram::num_partitions( + TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_WORD_PARTITION_SIZE, TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_DATA_WIDTH); +const TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_INITIALIZED = true; +const TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_ASSERT_VALID_READ = true; + +const TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_DATA_WIDTH = u32:16; +const TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_SIZE = u32:256; +const TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_ADDR_WIDTH = std::clog2(TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_SIZE); +const TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_WORD_PARTITION_SIZE = TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_DATA_WIDTH; +const TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_NUM_PARTITIONS = ram::num_partitions( + TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_WORD_PARTITION_SIZE, TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_DATA_WIDTH); +const TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_INITIALIZED = true; +const TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_ASSERT_VALID_READ = true; + +const TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_DATA_WIDTH = u32:8; +const TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_SIZE = u32:512; +const TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_ADDR_WIDTH = std::clog2(TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_SIZE); +const TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_WORD_PARTITION_SIZE = TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_DATA_WIDTH; +const TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_NUM_PARTITIONS = ram::num_partitions( + TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_WORD_PARTITION_SIZE, TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_DATA_WIDTH); +const TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_INITIALIZED = true; +const TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_ASSERT_VALID_READ = true; + #[test_proc] proc LiteralsDecoder_test { // LiteralsBuffer internal memory @@ -1083,6 +1294,26 @@ proc LiteralsDecoder_test { type HuffmanPrescanRamWrReq = ram::WriteReq; type HuffmanPrescanRamWrResp = ram::WriteResp; + type HuffmanWeightsDpdRamRdReq = ram::ReadReq; + type HuffmanWeightsDpdRamRdResp = ram::ReadResp; + type HuffmanWeightsDpdRamWrReq = ram::WriteReq; + type HuffmanWeightsDpdRamWrResp = ram::WriteResp; + + type HuffmanWeightsTmpRamRdReq = ram::ReadReq; + type HuffmanWeightsTmpRamRdResp = ram::ReadResp; + type HuffmanWeightsTmpRamWrReq = ram::WriteReq; + type HuffmanWeightsTmpRamWrResp = ram::WriteResp; + + type HuffmanWeightsTmp2RamRdReq = ram::ReadReq; + type HuffmanWeightsTmp2RamRdResp = ram::ReadResp; + type HuffmanWeightsTmp2RamWrReq = ram::WriteReq; + type HuffmanWeightsTmp2RamWrResp = ram::WriteResp; + + type HuffmanWeightsFseRamRdReq = ram::ReadReq; + type HuffmanWeightsFseRamRdResp = ram::ReadResp; + type HuffmanWeightsFseRamWrReq = ram::WriteReq; + type HuffmanWeightsFseRamWrResp = ram::WriteResp; + // Control and output type CtrlReq = LiteralsDecoderCtrlReq; type CtrlResp = LiteralsDecoderCtrlResp; @@ -1124,8 +1355,10 @@ proc LiteralsDecoder_test { ram_wr_resp_huffman_weights_header_r : chan in; ram_wr_req_huffman_weights_raw_s : chan out; ram_wr_resp_huffman_weights_raw_r : chan in; - ram_wr_req_huffman_weights_fse_s : chan out; - ram_wr_resp_huffman_weights_fse_r : chan in; + ram_wr_req_huffman_weights_fse_lookup_dec_s : chan out; + ram_wr_resp_huffman_weights_fse_lookup_dec_r : chan in; + ram_wr_req_huffman_weights_fse_decoder_dec_s : chan out; + ram_wr_resp_huffman_weights_fse_decoder_dec_r : chan in; config (terminator: chan out) { let (lit_header_axi_ar_s, lit_header_axi_ar_r) = chan("lit_header_axi_ar"); @@ -1136,18 +1369,26 @@ proc LiteralsDecoder_test { let (huffman_lit_axi_ar_s, huffman_lit_axi_ar_r) = chan("huffman_lit_axi_ar"); let (huffman_lit_axi_r_s, huffman_lit_axi_r_r) = chan("huffman_lit_axi_r"); + let (huffman_jump_table_axi_ar_s, huffman_jump_table_axi_ar_r) = chan("huffman_jump_table_axi_ar"); let (huffman_jump_table_axi_r_s, huffman_jump_table_axi_r_r) = chan("huffman_jump_table_axi_r"); + let (huffman_weights_header_axi_ar_s, huffman_weights_header_axi_ar_r) = chan("huffman_weights_header_axi_ar"); let (huffman_weights_header_axi_r_s, huffman_weights_header_axi_r_r) = chan("huffman_weights_header_axi_r"); + let (huffman_weights_raw_axi_ar_s, huffman_weights_raw_axi_ar_r) = chan("huffman_weights_raw_axi_ar"); let (huffman_weights_raw_axi_r_s, huffman_weights_raw_axi_r_r) = chan("huffman_weights_raw_axi_r"); - let (huffman_weights_fse_axi_ar_s, huffman_weights_fse_axi_ar_r) = chan("huffman_weights_fse_axi_ar"); - let (huffman_weights_fse_axi_r_s, huffman_weights_fse_axi_r_r) = chan("huffman_weights_fse_axi_r"); + + let (huffman_weights_fse_lookup_dec_axi_ar_s, huffman_weights_fse_lookup_dec_axi_ar_r) = chan("huffman_weights_fse_lookup_dec_axi_ar"); + let (huffman_weights_fse_lookup_dec_axi_r_s, huffman_weights_fse_lookup_dec_axi_r_r) = chan("huffman_weights_fse_lookup_dec_axi_r_r"); + + let (huffman_weights_fse_decoder_dec_axi_ar_s, huffman_weights_fse_decoder_dec_axi_ar_r) = chan("huffman_weights_fse_decoder_dec_axi_ar"); + let (huffman_weights_fse_decoder_dec_axi_r_s, huffman_weights_fse_decoder_dec_axi_r_r) = chan("huffman_weights_fse_decoder_dec_axi_r"); let (ctrl_req_s, ctrl_req_r) = chan("ctrl_req"); let (ctrl_resp_s, ctrl_resp_r) = chan("ctrl_resp"); let (ctrl_header_s, ctrl_header_r) = chan("ctrl_header"); + let (buf_ctrl_s, buf_ctrl_r) = chan("buf_ctrl"); let (buf_out_s, buf_out_r) = chan("buf_out"); @@ -1169,12 +1410,35 @@ proc LiteralsDecoder_test { let (huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_req_r) = chan("huffman_lit_prescan_mem_wr_req"); let (huffman_lit_prescan_mem_wr_resp_s, huffman_lit_prescan_mem_wr_resp_r) = chan("huffman_lit_prescan_mem_wr_resp"); + let (huffman_lit_weights_dpd_rd_req_s, huffman_lit_weights_dpd_rd_req_r) = chan("huffman_lit_weights_dpd_rd_req"); + let (huffman_lit_weights_dpd_rd_resp_s, huffman_lit_weights_dpd_rd_resp_r) = chan("huffman_lit_weights_dpd_rd_resp"); + let (huffman_lit_weights_dpd_wr_req_s, huffman_lit_weights_dpd_wr_req_r) = chan("huffman_lit_weights_dpd_wr_req"); + let (huffman_lit_weights_dpd_wr_resp_s, huffman_lit_weights_dpd_wr_resp_r) = chan("huffman_lit_weights_dpd_wr_resp"); + + let (huffman_lit_weights_tmp_rd_req_s, huffman_lit_weights_tmp_rd_req_r) = chan("huffman_lit_weights_tmp_rd_req"); + let (huffman_lit_weights_tmp_rd_resp_s, huffman_lit_weights_tmp_rd_resp_r) = chan("huffman_lit_weights_tmp_rd_resp"); + let (huffman_lit_weights_tmp_wr_req_s, huffman_lit_weights_tmp_wr_req_r) = chan("huffman_lit_weights_tmp_wr_req"); + let (huffman_lit_weights_tmp_wr_resp_s, huffman_lit_weights_tmp_wr_resp_r) = chan("huffman_lit_weights_tmp_wr_resp"); + + let (huffman_lit_weights_tmp2_rd_req_s, huffman_lit_weights_tmp2_rd_req_r) = chan("huffman_lit_weights_tmp2_rd_req"); + let (huffman_lit_weights_tmp2_rd_resp_s, huffman_lit_weights_tmp2_rd_resp_r) = chan("huffman_lit_weights_tmp2_rd_resp"); + let (huffman_lit_weights_tmp2_wr_req_s, huffman_lit_weights_tmp2_wr_req_r) = chan("huffman_lit_weights_tmp2_wr_req"); + let (huffman_lit_weights_tmp2_wr_resp_s, huffman_lit_weights_tmp2_wr_resp_r) = chan("huffman_lit_weights_tmp2_wr_resp"); + + let (huffman_lit_weights_fse_rd_req_s, huffman_lit_weights_fse_rd_req_r) = chan("huffman_lit_weights_fse_rd_req"); + let (huffman_lit_weights_fse_rd_resp_s, huffman_lit_weights_fse_rd_resp_r) = chan("huffman_lit_weights_fse_rd_resp"); + let (huffman_lit_weights_fse_wr_req_s, huffman_lit_weights_fse_wr_req_r) = chan("huffman_lit_weights_fse_wr_req"); + let (huffman_lit_weights_fse_wr_resp_s, huffman_lit_weights_fse_wr_resp_r) = chan("huffman_lit_weights_fse_wr_resp"); + spawn LiteralsDecoder< TEST_HISTORY_BUFFER_SIZE_KB, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_DEST_W, + TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_ADDR_WIDTH, TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_DATA_WIDTH, TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_NUM_PARTITIONS, + TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_ADDR_WIDTH, TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_DATA_WIDTH, TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_NUM_PARTITIONS, + TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_ADDR_WIDTH, TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_DATA_WIDTH, TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_NUM_PARTITIONS, + TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_ADDR_WIDTH, TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_DATA_WIDTH, TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_NUM_PARTITIONS, TEST_HUFFMAN_WEIGHTS_RAM_MODEL_ADDR_WIDTH, TEST_HUFFMAN_WEIGHTS_RAM_MODEL_DATA_WIDTH, TEST_HUFFMAN_WEIGHTS_RAM_MODEL_NUM_PARTITIONS, TEST_HUFFMAN_PRESCAN_RAM_MODEL_ADDR_WIDTH, TEST_HUFFMAN_PRESCAN_RAM_MODEL_DATA_WIDTH, TEST_HUFFMAN_PRESCAN_RAM_MODEL_NUM_PARTITIONS, - TEST_LITERALS_BUFFER_RAM_MODEL_ADDR_WIDTH, TEST_LITERALS_BUFFER_RAM_MODEL_SIZE, TEST_LITERALS_BUFFER_RAM_MODEL_DATA_WIDTH, TEST_LITERALS_BUFFER_RAM_MODEL_NUM_PARTITIONS > ( lit_header_axi_ar_s, lit_header_axi_r_r, raw_lit_axi_ar_s, raw_lit_axi_r_r, @@ -1182,7 +1446,8 @@ proc LiteralsDecoder_test { huffman_jump_table_axi_ar_s, huffman_jump_table_axi_r_r, huffman_weights_header_axi_ar_s, huffman_weights_header_axi_r_r, huffman_weights_raw_axi_ar_s, huffman_weights_raw_axi_r_r, - huffman_weights_fse_axi_ar_s, huffman_weights_fse_axi_r_r, + huffman_weights_fse_lookup_dec_axi_ar_s, huffman_weights_fse_lookup_dec_axi_r_r, + huffman_weights_fse_decoder_dec_axi_ar_s, huffman_weights_fse_decoder_dec_axi_r_r, ctrl_req_r, ctrl_resp_s, ctrl_header_s, buf_ctrl_r, buf_out_s, ram_rd_req_s[0], ram_rd_req_s[1], ram_rd_req_s[2], ram_rd_req_s[3], @@ -1197,6 +1462,14 @@ proc LiteralsDecoder_test { huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_resp_r, huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_resp_r, huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_resp_r, + huffman_lit_weights_dpd_rd_req_s, huffman_lit_weights_dpd_rd_resp_r, + huffman_lit_weights_dpd_wr_req_s, huffman_lit_weights_dpd_wr_resp_r, + huffman_lit_weights_tmp_rd_req_s, huffman_lit_weights_tmp_rd_resp_r, + huffman_lit_weights_tmp_wr_req_s, huffman_lit_weights_tmp_wr_resp_r, + huffman_lit_weights_tmp2_rd_req_s, huffman_lit_weights_tmp2_rd_resp_r, + huffman_lit_weights_tmp2_wr_req_s, huffman_lit_weights_tmp2_wr_resp_r, + huffman_lit_weights_fse_rd_req_s, huffman_lit_weights_fse_rd_resp_r, + huffman_lit_weights_fse_wr_req_s, huffman_lit_weights_fse_wr_resp_r, ); spawn ram_printer::RamPrinter< @@ -1438,11 +1711,11 @@ proc LiteralsDecoder_test { ram_rd_req_huffman_weights_raw_s, ram_rd_resp_huffman_weights_raw_r ); - // Mock RAM for HuffmanWeights fse decoder MemReader - let (ram_rd_req_huffman_weights_fse_s, ram_rd_req_huffman_weights_fse_r) = chan("ram_rd_req_huffman_weights_fse"); - let (ram_rd_resp_huffman_weights_fse_s, ram_rd_resp_huffman_weights_fse_r) = chan("ram_rd_resp_huffman_weights_fse"); - let (ram_wr_req_huffman_weights_fse_s, ram_wr_req_huffman_weights_fse_r) = chan("ram_wr_req_huffman_weights_fse"); - let (ram_wr_resp_huffman_weights_fse_s, ram_wr_resp_huffman_weights_fse_r) = chan("ram_wr_resp_huffman_weights_fse"); + // Mock RAM for HuffmanWeights FseLookupDecoder MemReader + let (ram_rd_req_huffman_weights_fse_lookup_dec_s, ram_rd_req_huffman_weights_fse_lookup_dec_r) = chan("ram_rd_req_huffman_weights_fse_lookup_dec"); + let (ram_rd_resp_huffman_weights_fse_lookup_dec_s, ram_rd_resp_huffman_weights_fse_lookup_dec_r) = chan("ram_rd_resp_huffman_weights_fse_lookup_dec"); + let (ram_wr_req_huffman_weights_fse_lookup_dec_s, ram_wr_req_huffman_weights_fse_lookup_dec_r) = chan("ram_wr_req_huffman_weights_fse_lookup_dec"); + let (ram_wr_resp_huffman_weights_fse_lookup_dec_s, ram_wr_resp_huffman_weights_fse_lookup_dec_r) = chan("ram_wr_resp_huffman_weights_fse_lookup_dec"); spawn ram::RamModel< TEST_AXI_RAM_MODEL_DATA_WIDTH, @@ -1453,15 +1726,43 @@ proc LiteralsDecoder_test { TEST_AXI_RAM_MODEL_ASSERT_VALID_READ, TEST_AXI_RAM_MODEL_ADDR_WIDTH > ( - ram_rd_req_huffman_weights_fse_r, ram_rd_resp_huffman_weights_fse_s, ram_wr_req_huffman_weights_fse_r, ram_wr_resp_huffman_weights_fse_s + ram_rd_req_huffman_weights_fse_lookup_dec_r, ram_rd_resp_huffman_weights_fse_lookup_dec_s, + ram_wr_req_huffman_weights_fse_lookup_dec_r, ram_wr_resp_huffman_weights_fse_lookup_dec_s ); spawn axi_ram::AxiRamReader< TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH > ( - huffman_weights_fse_axi_ar_r, huffman_weights_fse_axi_r_s, - ram_rd_req_huffman_weights_fse_s, ram_rd_resp_huffman_weights_fse_r + huffman_weights_fse_lookup_dec_axi_ar_r, huffman_weights_fse_lookup_dec_axi_r_s, + ram_rd_req_huffman_weights_fse_lookup_dec_s, ram_rd_resp_huffman_weights_fse_lookup_dec_r + ); + + // Mock RAM for HuffmanWeights FseDecoder MemReader + let (ram_rd_req_huffman_weights_fse_decoder_dec_s, ram_rd_req_huffman_weights_fse_decoder_dec_r) = chan("ram_rd_req_huffman_weights_fse_decoder_dec"); + let (ram_rd_resp_huffman_weights_fse_decoder_dec_s, ram_rd_resp_huffman_weights_fse_decoder_dec_r) = chan("ram_rd_resp_huffman_weights_fse_decoder_dec"); + let (ram_wr_req_huffman_weights_fse_decoder_dec_s, ram_wr_req_huffman_weights_fse_decoder_dec_r) = chan("ram_wr_req_huffman_weights_fse_decoder_dec"); + let (ram_wr_resp_huffman_weights_fse_decoder_dec_s, ram_wr_resp_huffman_weights_fse_decoder_dec_r) = chan("ram_wr_resp_huffman_weights_fse_decoder_dec"); + + spawn ram::RamModel< + TEST_AXI_RAM_MODEL_DATA_WIDTH, + TEST_AXI_RAM_MODEL_SIZE, + TEST_AXI_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_AXI_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_AXI_RAM_MODEL_INITIALIZED, + TEST_AXI_RAM_MODEL_ASSERT_VALID_READ, + TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + ram_rd_req_huffman_weights_fse_decoder_dec_r, ram_rd_resp_huffman_weights_fse_decoder_dec_s, + ram_wr_req_huffman_weights_fse_decoder_dec_r, ram_wr_resp_huffman_weights_fse_decoder_dec_s + ); + + spawn axi_ram::AxiRamReader< + TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, + TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH + > ( + huffman_weights_fse_decoder_dec_axi_ar_r, huffman_weights_fse_decoder_dec_axi_r_s, + ram_rd_req_huffman_weights_fse_decoder_dec_s, ram_rd_resp_huffman_weights_fse_decoder_dec_r ); // Huffman weigths memory @@ -1492,6 +1793,59 @@ proc LiteralsDecoder_test { huffman_lit_prescan_mem_wr_req_r, huffman_lit_prescan_mem_wr_resp_s ); + spawn ram::RamModel< + TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_DATA_WIDTH, + TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_SIZE, + TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_INITIALIZED, + TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_ASSERT_VALID_READ, + TEST_HUFFMAN_WEIGHTS_DPD_RAM_MODEL_ADDR_WIDTH + > ( + huffman_lit_weights_dpd_rd_req_r, huffman_lit_weights_dpd_rd_resp_s, + huffman_lit_weights_dpd_wr_req_r, huffman_lit_weights_dpd_wr_resp_s + ); + + + spawn ram::RamModel< + TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_DATA_WIDTH, + TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_SIZE, + TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_INITIALIZED, + TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_ASSERT_VALID_READ, + TEST_HUFFMAN_WEIGHTS_TMP_RAM_MODEL_ADDR_WIDTH + > ( + huffman_lit_weights_tmp_rd_req_r, huffman_lit_weights_tmp_rd_resp_s, + huffman_lit_weights_tmp_wr_req_r, huffman_lit_weights_tmp_wr_resp_s + ); + + spawn ram::RamModel< + TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_DATA_WIDTH, + TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_SIZE, + TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_INITIALIZED, + TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_ASSERT_VALID_READ, + TEST_HUFFMAN_WEIGHTS_TMP2_RAM_MODEL_ADDR_WIDTH + > ( + huffman_lit_weights_tmp2_rd_req_r, huffman_lit_weights_tmp2_rd_resp_s, + huffman_lit_weights_tmp2_wr_req_r, huffman_lit_weights_tmp2_wr_resp_s + ); + + spawn ram::RamModel< + TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_DATA_WIDTH, + TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_SIZE, + TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_WORD_PARTITION_SIZE, + TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_SIMULTANEOUS_READ_WRITE_BEHAVIOR, + TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_INITIALIZED, + TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_ASSERT_VALID_READ, + TEST_HUFFMAN_WEIGHTS_FSE_RAM_MODEL_ADDR_WIDTH + > ( + huffman_lit_weights_fse_rd_req_r, huffman_lit_weights_fse_rd_resp_s, + huffman_lit_weights_fse_wr_req_r, huffman_lit_weights_fse_wr_resp_s + ); + ( terminator, ctrl_req_s, ctrl_resp_r, ctrl_header_r, @@ -1503,7 +1857,8 @@ proc LiteralsDecoder_test { ram_wr_req_huffman_jump_table_s, ram_wr_resp_huffman_jump_table_r, ram_wr_req_huffman_weights_header_s, ram_wr_resp_huffman_weights_header_r, ram_wr_req_huffman_weights_raw_s, ram_wr_resp_huffman_weights_raw_r, - ram_wr_req_huffman_weights_fse_s, ram_wr_resp_huffman_weights_fse_r + ram_wr_req_huffman_weights_fse_lookup_dec_s, ram_wr_resp_huffman_weights_fse_lookup_dec_r, + ram_wr_req_huffman_weights_fse_decoder_dec_s, ram_wr_resp_huffman_weights_fse_decoder_dec_r ) } @@ -1741,8 +2096,10 @@ proc LiteralsDecoder_test { let (tok, _) = recv(tok, ram_wr_resp_huffman_weights_header_r); let tok = send(tok, ram_wr_req_huffman_weights_raw_s, mem_req); let (tok, _) = recv(tok, ram_wr_resp_huffman_weights_raw_r); - let tok = send(tok, ram_wr_req_huffman_weights_fse_s, mem_req); - let (tok, _) = recv(tok, ram_wr_resp_huffman_weights_fse_r); + let tok = send(tok, ram_wr_req_huffman_weights_fse_lookup_dec_s, mem_req); + let (tok, _) = recv(tok, ram_wr_resp_huffman_weights_fse_lookup_dec_r); + let tok = send(tok, ram_wr_req_huffman_weights_fse_decoder_dec_s, mem_req); + let (tok, _) = recv(tok, ram_wr_resp_huffman_weights_fse_decoder_dec_r); tok }(tok); diff --git a/xls/modules/zstd/zstd_dec.x b/xls/modules/zstd/zstd_dec.x index 77ddea55f6..b0c0d67a06 100644 --- a/xls/modules/zstd/zstd_dec.x +++ b/xls/modules/zstd/zstd_dec.x @@ -950,6 +950,11 @@ pub proc ZstdDecoder< TMP2_RAM_ADDR_W: u32, TMP2_RAM_DATA_W: u32, TMP2_RAM_NUM_PARTITIONS: u32, FSE_RAM_ADDR_W: u32, FSE_RAM_DATA_W: u32, FSE_RAM_NUM_PARTITIONS: u32, + HUFFMAN_WEIGHTS_DPD_RAM_ADDR_W: u32, HUFFMAN_WEIGHTS_DPD_RAM_DATA_W: u32, HUFFMAN_WEIGHTS_DPD_RAM_NUM_PARTITIONS: u32, + HUFFMAN_WEIGHTS_TMP_RAM_ADDR_W: u32, HUFFMAN_WEIGHTS_TMP_RAM_DATA_W: u32, HUFFMAN_WEIGHTS_TMP_RAM_NUM_PARTITIONS: u32, + HUFFMAN_WEIGHTS_TMP2_RAM_ADDR_W: u32, HUFFMAN_WEIGHTS_TMP2_RAM_DATA_W: u32, HUFFMAN_WEIGHTS_TMP2_RAM_NUM_PARTITIONS: u32, + HUFFMAN_WEIGHTS_FSE_RAM_ADDR_W: u32, HUFFMAN_WEIGHTS_FSE_RAM_DATA_W: u32, HUFFMAN_WEIGHTS_FSE_RAM_NUM_PARTITIONS: u32, + HISTORY_BUFFER_SIZE_KB: u32, AXI_CHAN_N: u32, @@ -1046,11 +1051,32 @@ pub proc ZstdDecoder< type HuffmanWeightsReadResp = ram::ReadResp; type HuffmanWeightsWriteReq = ram::WriteReq; type HuffmanWeightsWriteResp = ram::WriteResp; + type HuffmanPrescanReadReq = ram::ReadReq; type HuffmanPrescanReadResp = ram::ReadResp; type HuffmanPrescanWriteReq = ram::WriteReq; type HuffmanPrescanWriteResp = ram::WriteResp; + type HuffmanWeightsDpdRamRdReq = ram::ReadReq; + type HuffmanWeightsDpdRamRdResp = ram::ReadResp; + type HuffmanWeightsDpdRamWrReq = ram::WriteReq; + type HuffmanWeightsDpdRamWrResp = ram::WriteResp; + + type HuffmanWeightsTmpRamRdReq = ram::ReadReq; + type HuffmanWeightsTmpRamRdResp = ram::ReadResp; + type HuffmanWeightsTmpRamWrReq = ram::WriteReq; + type HuffmanWeightsTmpRamWrResp = ram::WriteResp; + + type HuffmanWeightsTmp2RamRdReq = ram::ReadReq; + type HuffmanWeightsTmp2RamRdResp = ram::ReadResp; + type HuffmanWeightsTmp2RamWrReq = ram::WriteReq; + type HuffmanWeightsTmp2RamWrResp = ram::WriteResp; + + type HuffmanWeightsFseRamRdReq = ram::ReadReq; + type HuffmanWeightsFseRamRdResp = ram::ReadResp; + type HuffmanWeightsFseRamWrReq = ram::WriteReq; + type HuffmanWeightsFseRamWrResp = ram::WriteResp; + type LitBufRamRdReq = ram::ReadReq; type LitBufRamRdResp = ram::ReadResp; type LitBufRamWrReq = ram::WriteReq; @@ -1142,7 +1168,27 @@ pub proc ZstdDecoder< huffman_lit_prescan_mem_wr_req_s: chan out, huffman_lit_prescan_mem_wr_resp_r: chan in, - //// AXI Output Writer (manager) + huffman_lit_weights_dpd_rd_req_s: chan out, + huffman_lit_weights_dpd_rd_resp_r: chan in, + huffman_lit_weights_dpd_wr_req_s: chan out, + huffman_lit_weights_dpd_wr_resp_r: chan in, + + huffman_lit_weights_tmp_rd_req_s: chan out, + huffman_lit_weights_tmp_rd_resp_r: chan in, + huffman_lit_weights_tmp_wr_req_s: chan out, + huffman_lit_weights_tmp_wr_resp_r: chan in, + + huffman_lit_weights_tmp2_rd_req_s: chan out, + huffman_lit_weights_tmp2_rd_resp_r: chan in, + huffman_lit_weights_tmp2_wr_req_s: chan out, + huffman_lit_weights_tmp2_wr_resp_r: chan in, + + huffman_lit_weights_fse_rd_req_s: chan out, + huffman_lit_weights_fse_rd_resp_r: chan in, + huffman_lit_weights_fse_wr_req_s: chan out, + huffman_lit_weights_fse_wr_resp_r: chan in, + + // AXI Output Writer (manager) output_axi_aw_s: chan out, output_axi_w_s: chan out, output_axi_b_r: chan in, @@ -1294,11 +1340,16 @@ pub proc ZstdDecoder< spawn comp_block_dec::CompressBlockDecoder< AXI_DATA_W, AXI_ADDR_W, AXI_ID_W, AXI_DEST_W, - // FSE lookup table RAMs + DPD_RAM_ADDR_W, DPD_RAM_DATA_W, DPD_RAM_NUM_PARTITIONS, TMP_RAM_ADDR_W, TMP_RAM_DATA_W, TMP_RAM_NUM_PARTITIONS, TMP2_RAM_ADDR_W, TMP2_RAM_DATA_W, TMP2_RAM_NUM_PARTITIONS, FSE_RAM_ADDR_W, FSE_RAM_DATA_W, FSE_RAM_NUM_PARTITIONS, + + HUFFMAN_WEIGHTS_DPD_RAM_ADDR_W, HUFFMAN_WEIGHTS_DPD_RAM_DATA_W, HUFFMAN_WEIGHTS_DPD_RAM_NUM_PARTITIONS, + HUFFMAN_WEIGHTS_TMP_RAM_ADDR_W, HUFFMAN_WEIGHTS_TMP_RAM_DATA_W, HUFFMAN_WEIGHTS_TMP_RAM_NUM_PARTITIONS, + HUFFMAN_WEIGHTS_TMP2_RAM_ADDR_W, HUFFMAN_WEIGHTS_TMP2_RAM_DATA_W, HUFFMAN_WEIGHTS_TMP2_RAM_NUM_PARTITIONS, + HUFFMAN_WEIGHTS_FSE_RAM_ADDR_W, HUFFMAN_WEIGHTS_FSE_RAM_DATA_W, HUFFMAN_WEIGHTS_FSE_RAM_NUM_PARTITIONS, >( // MAIN IOs @@ -1331,6 +1382,7 @@ pub proc ZstdDecoder< comp_axi_ram_ar_s[7], comp_axi_ram_r_r[7], comp_axi_ram_ar_s[8], comp_axi_ram_r_r[8], comp_axi_ram_ar_s[9], comp_axi_ram_r_r[9], + comp_axi_ram_ar_s[10], comp_axi_ram_r_r[10], litbuf_rd_req_s[0], litbuf_rd_req_s[1], litbuf_rd_req_s[2], litbuf_rd_req_s[3], litbuf_rd_req_s[4], litbuf_rd_req_s[5], litbuf_rd_req_s[6], litbuf_rd_req_s[7], litbuf_rd_resp_r[0], litbuf_rd_resp_r[1], litbuf_rd_resp_r[2], litbuf_rd_resp_r[3], @@ -1343,6 +1395,14 @@ pub proc ZstdDecoder< huffman_lit_weights_write_side_wr_req_s, huffman_lit_weights_write_side_wr_resp_r, huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_resp_r, huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_resp_r, + huffman_lit_weights_dpd_rd_req_s, huffman_lit_weights_dpd_rd_resp_r, + huffman_lit_weights_dpd_wr_req_s, huffman_lit_weights_dpd_wr_resp_r, + huffman_lit_weights_tmp_rd_req_s, huffman_lit_weights_tmp_rd_resp_r, + huffman_lit_weights_tmp_wr_req_s, huffman_lit_weights_tmp_wr_resp_r, + huffman_lit_weights_tmp2_rd_req_s, huffman_lit_weights_tmp2_rd_resp_r, + huffman_lit_weights_tmp2_wr_req_s, huffman_lit_weights_tmp2_wr_resp_r, + huffman_lit_weights_fse_rd_req_s, huffman_lit_weights_fse_rd_resp_r, + huffman_lit_weights_fse_wr_req_s, huffman_lit_weights_fse_wr_resp_r, ); spawn ram_merge::RamMerge( @@ -1401,34 +1461,6 @@ pub proc ZstdDecoder< notify_s, reset_s, ); - - // // Sequence config (section header) decoder's memory reader - // let (scd_mem_rd_req_s, scd_mem_rd_req_r) = chan("scd_mem_rd_req"); - // let (scd_mem_rd_resp_s, scd_mem_rd_resp_r) = chan("scd_mem_rd_resp"); - - // spawn mem_reader::MemReader( - // scd_mem_rd_req_r, scd_mem_rd_resp_s, - // scd_axi_ar_s, scd_axi_r_r, - // ); - - // // FSE lookup decoder's memory reader for RefillingShiftBuffer - // let (fse_lookup_mem_rd_req_s, fse_lookup_mem_rd_req_r) = chan("fse_lookup_mem_rd_req"); - // let (fse_lookup_mem_rd_resp_s, fse_lookup_mem_rd_resp_r) = chan("fse_lookup_mem_rd_resp"); - - // spawn mem_reader::MemReader( - // fse_lookup_mem_rd_req_r, fse_lookup_mem_rd_resp_s, - // fse_lookup_axi_ar_s, fse_lookup_axi_r_r, - // ); - - // // FSE decoder's memory reader for RefillingShiftBuffer - // let (fse_dec_mem_rd_req_s, fse_dec_mem_rd_req_r) = chan("scd_mem_rd_req"); - // let (fse_dec_mem_rd_resp_s, fse_dec_mem_rd_resp_r) = chan("scd_mem_rd_resp"); - - // spawn mem_reader::MemReader( - // fse_dec_mem_rd_req_r, fse_dec_mem_rd_resp_s, - // fse_dec_axi_ar_s, fse_dec_axi_r_r, - // ); - () } @@ -1488,8 +1520,36 @@ const HUFFMAN_PRESCAN_RAM_ADDR_W: u32 = huffman_literals_dec::PRESCAN_ADDR_WIDTH const HUFFMAN_PRESCAN_RAM_DATA_W: u32 = huffman_literals_dec::PRESCAN_DATA_WIDTH; const HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::PRESCAN_NUM_PARTITIONS; +const INST_HUFFMAN_WEIGHTS_DPD_RAM_DATA_W = u32:16; +const INST_HUFFMAN_WEIGHTS_DPD_RAM_SIZE = u32:256; +const INST_HUFFMAN_WEIGHTS_DPD_RAM_ADDR_W = std::clog2(INST_HUFFMAN_WEIGHTS_DPD_RAM_SIZE); +const INST_HUFFMAN_WEIGHTS_DPD_RAM_WORD_PARTITION_SIZE = INST_HUFFMAN_WEIGHTS_DPD_RAM_DATA_W; +const INST_HUFFMAN_WEIGHTS_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( + INST_HUFFMAN_WEIGHTS_DPD_RAM_WORD_PARTITION_SIZE, INST_HUFFMAN_WEIGHTS_DPD_RAM_DATA_W); + +const INST_HUFFMAN_WEIGHTS_FSE_RAM_DATA_W = u32:32; +const INST_HUFFMAN_WEIGHTS_FSE_RAM_SIZE = u32:256; +const INST_HUFFMAN_WEIGHTS_FSE_RAM_ADDR_W = std::clog2(INST_HUFFMAN_WEIGHTS_FSE_RAM_SIZE); +const INST_HUFFMAN_WEIGHTS_FSE_RAM_WORD_PARTITION_SIZE = INST_HUFFMAN_WEIGHTS_FSE_RAM_DATA_W; +const INST_HUFFMAN_WEIGHTS_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( + INST_HUFFMAN_WEIGHTS_FSE_RAM_WORD_PARTITION_SIZE, INST_HUFFMAN_WEIGHTS_FSE_RAM_DATA_W); + +const INST_HUFFMAN_WEIGHTS_TMP_RAM_DATA_W = u32:16; +const INST_HUFFMAN_WEIGHTS_TMP_RAM_SIZE = u32:256; +const INST_HUFFMAN_WEIGHTS_TMP_RAM_ADDR_W = std::clog2(INST_HUFFMAN_WEIGHTS_TMP_RAM_SIZE); +const INST_HUFFMAN_WEIGHTS_TMP_RAM_WORD_PARTITION_SIZE = INST_HUFFMAN_WEIGHTS_TMP_RAM_DATA_W; +const INST_HUFFMAN_WEIGHTS_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( + INST_HUFFMAN_WEIGHTS_TMP_RAM_WORD_PARTITION_SIZE, INST_HUFFMAN_WEIGHTS_TMP_RAM_DATA_W); + +const INST_HUFFMAN_WEIGHTS_TMP2_RAM_DATA_W = u32:8; +const INST_HUFFMAN_WEIGHTS_TMP2_RAM_SIZE = u32:512; +const INST_HUFFMAN_WEIGHTS_TMP2_RAM_ADDR_W = std::clog2(INST_HUFFMAN_WEIGHTS_TMP2_RAM_SIZE); +const INST_HUFFMAN_WEIGHTS_TMP2_RAM_WORD_PARTITION_SIZE = INST_HUFFMAN_WEIGHTS_TMP2_RAM_DATA_W; +const INST_HUFFMAN_WEIGHTS_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions( + INST_HUFFMAN_WEIGHTS_TMP2_RAM_WORD_PARTITION_SIZE, INST_HUFFMAN_WEIGHTS_TMP2_RAM_DATA_W); + const INST_HISTORY_BUFFER_SIZE_KB = u32:64; -const INST_AXI_CHAN_N = u32:10; +const INST_AXI_CHAN_N = u32:11; // Literals buffer memory parameters const LITERALS_BUFFER_RAM_ADDR_W: u32 = parallel_rams::ram_addr_width(INST_HISTORY_BUFFER_SIZE_KB); @@ -1632,12 +1692,31 @@ proc ZstdDecoderInst { type HuffmanWeightsReadResp = ram::ReadResp; type HuffmanWeightsWriteReq = ram::WriteReq; type HuffmanWeightsWriteResp = ram::WriteResp; + type HuffmanPrescanReadReq = ram::ReadReq; type HuffmanPrescanReadResp = ram::ReadResp; type HuffmanPrescanWriteReq = ram::WriteReq; type HuffmanPrescanWriteResp = ram::WriteResp; - const CHANNEL_DEPTH = u32:1; + type HuffmanWeightsDpdRamRdReq = ram::ReadReq; + type HuffmanWeightsDpdRamRdResp = ram::ReadResp; + type HuffmanWeightsDpdRamWrReq = ram::WriteReq; + type HuffmanWeightsDpdRamWrResp = ram::WriteResp; + + type HuffmanWeightsTmpRamRdReq = ram::ReadReq; + type HuffmanWeightsTmpRamRdResp = ram::ReadResp; + type HuffmanWeightsTmpRamWrReq = ram::WriteReq; + type HuffmanWeightsTmpRamWrResp = ram::WriteResp; + + type HuffmanWeightsTmp2RamRdReq = ram::ReadReq; + type HuffmanWeightsTmp2RamRdResp = ram::ReadResp; + type HuffmanWeightsTmp2RamWrReq = ram::WriteReq; + type HuffmanWeightsTmp2RamWrResp = ram::WriteResp; + + type HuffmanWeightsFseRamRdReq = ram::ReadReq; + type HuffmanWeightsFseRamRdResp = ram::ReadResp; + type HuffmanWeightsFseRamWrReq = ram::WriteReq; + type HuffmanWeightsFseRamWrResp = ram::WriteResp; init { } @@ -1695,7 +1774,27 @@ proc ZstdDecoderInst { huffman_lit_prescan_mem_wr_req_s: chan out, huffman_lit_prescan_mem_wr_resp_r: chan in, - //// AXI Output Writer (manager) + huffman_lit_weights_dpd_rd_req_s: chan out, + huffman_lit_weights_dpd_rd_resp_r: chan in, + huffman_lit_weights_dpd_wr_req_s: chan out, + huffman_lit_weights_dpd_wr_resp_r: chan in, + + huffman_lit_weights_tmp_rd_req_s: chan out, + huffman_lit_weights_tmp_rd_resp_r: chan in, + huffman_lit_weights_tmp_wr_req_s: chan out, + huffman_lit_weights_tmp_wr_resp_r: chan in, + + huffman_lit_weights_tmp2_rd_req_s: chan out, + huffman_lit_weights_tmp2_rd_resp_r: chan in, + huffman_lit_weights_tmp2_wr_req_s: chan out, + huffman_lit_weights_tmp2_wr_resp_r: chan in, + + huffman_lit_weights_fse_rd_req_s: chan out, + huffman_lit_weights_fse_rd_resp_r: chan in, + huffman_lit_weights_fse_wr_req_s: chan out, + huffman_lit_weights_fse_wr_resp_r: chan in, + + // AXI Output Writer (manager) output_axi_aw_s: chan out, output_axi_w_s: chan out, output_axi_b_r: chan in, @@ -1738,10 +1837,11 @@ proc ZstdDecoderInst { reset_s: chan<()> out, ) { // FIXME: Remove inline Huffman Weights memory once HuffmanLiteralsDecoder's memory ports are able to be rewritten - let (huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_req_r) = chan("huffman_lit_weights_mem_rd_req"); - let (huffman_lit_weights_mem_rd_resp_s, huffman_lit_weights_mem_rd_resp_r) = chan("huffman_lit_weights_mem_rd_resp"); - let (huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_req_r) = chan("huffman_lit_weights_mem_wr_req"); - let (huffman_lit_weights_mem_wr_resp_s, huffman_lit_weights_mem_wr_resp_r) = chan("huffman_lit_weights_mem_wr_resp"); + let (huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_req_r) = chan("huffman_lit_weights_mem_rd_req"); + let (huffman_lit_weights_mem_rd_resp_s, huffman_lit_weights_mem_rd_resp_r) = chan("huffman_lit_weights_mem_rd_resp"); + let (huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_req_r) = chan("huffman_lit_weights_mem_wr_req"); + let (huffman_lit_weights_mem_wr_resp_s, huffman_lit_weights_mem_wr_resp_r) = chan("huffman_lit_weights_mem_wr_resp"); + spawn ram::RamModel< HUFFMAN_WEIGHTS_RAM_DATA_W, HUFFMAN_WEIGHTS_RAM_SIZE, HUFFMAN_WEIGHTS_RAM_PARTITION_WORD_SIZE, INST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, INST_RAM_INITIALIZED @@ -1754,10 +1854,17 @@ proc ZstdDecoderInst { INST_AXI_DATA_W, INST_AXI_ADDR_W, INST_AXI_ID_W, INST_AXI_DEST_W, INST_REGS_N, INST_WINDOW_LOG_MAX, INST_HB_ADDR_W, INST_HB_DATA_W, INST_HB_NUM_PARTITIONS, INST_HB_SIZE_KB, + INST_DPD_RAM_ADDR_W, INST_DPD_RAM_DATA_W, INST_DPD_RAM_NUM_PARTITIONS, INST_TMP_RAM_ADDR_W, INST_TMP_RAM_DATA_W, INST_TMP_RAM_NUM_PARTITIONS, INST_TMP2_RAM_ADDR_W, INST_TMP2_RAM_DATA_W, INST_TMP2_RAM_NUM_PARTITIONS, INST_FSE_RAM_ADDR_W, INST_FSE_RAM_DATA_W, INST_FSE_RAM_NUM_PARTITIONS, + + INST_HUFFMAN_WEIGHTS_DPD_RAM_ADDR_W, INST_HUFFMAN_WEIGHTS_DPD_RAM_DATA_W, INST_HUFFMAN_WEIGHTS_DPD_RAM_NUM_PARTITIONS, + INST_HUFFMAN_WEIGHTS_TMP_RAM_ADDR_W, INST_HUFFMAN_WEIGHTS_TMP_RAM_DATA_W, INST_HUFFMAN_WEIGHTS_TMP_RAM_NUM_PARTITIONS, + INST_HUFFMAN_WEIGHTS_TMP2_RAM_ADDR_W, INST_HUFFMAN_WEIGHTS_TMP2_RAM_DATA_W, INST_HUFFMAN_WEIGHTS_TMP2_RAM_NUM_PARTITIONS, + INST_HUFFMAN_WEIGHTS_FSE_RAM_ADDR_W, INST_HUFFMAN_WEIGHTS_FSE_RAM_DATA_W, INST_HUFFMAN_WEIGHTS_FSE_RAM_NUM_PARTITIONS, + INST_HISTORY_BUFFER_SIZE_KB, INST_AXI_CHAN_N, >( @@ -1782,6 +1889,14 @@ proc ZstdDecoderInst { huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_resp_r, huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_resp_r, huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_resp_r, + huffman_lit_weights_dpd_rd_req_s, huffman_lit_weights_dpd_rd_resp_r, + huffman_lit_weights_dpd_wr_req_s, huffman_lit_weights_dpd_wr_resp_r, + huffman_lit_weights_tmp_rd_req_s, huffman_lit_weights_tmp_rd_resp_r, + huffman_lit_weights_tmp_wr_req_s, huffman_lit_weights_tmp_wr_resp_r, + huffman_lit_weights_tmp2_rd_req_s, huffman_lit_weights_tmp2_rd_resp_r, + huffman_lit_weights_tmp2_wr_req_s, huffman_lit_weights_tmp2_wr_resp_r, + huffman_lit_weights_fse_rd_req_s, huffman_lit_weights_fse_rd_resp_r, + huffman_lit_weights_fse_wr_req_s, huffman_lit_weights_fse_wr_resp_r, output_axi_aw_s, output_axi_w_s, output_axi_b_r, 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, diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x index 52306111ce..a47531ca34 100644 --- a/xls/modules/zstd/zstd_dec_test.x +++ b/xls/modules/zstd/zstd_dec_test.x @@ -94,18 +94,46 @@ const TEST_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions( TEST_TMP2_RAM_WORD_PARTITION_SIZE, TEST_TMP2_RAM_DATA_W); // Huffman weights memory parameters -const HUFFMAN_WEIGHTS_RAM_SIZE: u32 = huffman_literals_dec::RAM_SIZE; -const HUFFMAN_WEIGHTS_RAM_ADDR_W: u32 = huffman_literals_dec::WEIGHTS_ADDR_WIDTH; -const HUFFMAN_WEIGHTS_RAM_DATA_W: u32 = huffman_literals_dec::WEIGHTS_DATA_WIDTH; -const HUFFMAN_WEIGHTS_RAM_PARTITION_WORD_SIZE: u32 = huffman_literals_dec::WEIGHTS_PARTITION_WORD_SIZE; -const HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::WEIGHTS_NUM_PARTITIONS; +const TEST_HUFFMAN_WEIGHTS_RAM_SIZE: u32 = huffman_literals_dec::RAM_SIZE; +const TEST_HUFFMAN_WEIGHTS_RAM_ADDR_W: u32 = huffman_literals_dec::WEIGHTS_ADDR_WIDTH; +const TEST_HUFFMAN_WEIGHTS_RAM_DATA_W: u32 = huffman_literals_dec::WEIGHTS_DATA_WIDTH; +const TEST_HUFFMAN_WEIGHTS_RAM_WORD_PARTITION_SIZE: u32 = huffman_literals_dec::WEIGHTS_PARTITION_WORD_SIZE; +const TEST_HUFFMAN_WEIGHTS_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::WEIGHTS_NUM_PARTITIONS; // Huffman prescan memory parameters -const HUFFMAN_PRESCAN_RAM_SIZE: u32 = huffman_literals_dec::RAM_SIZE; -const HUFFMAN_PRESCAN_RAM_ADDR_W: u32 = huffman_literals_dec::PRESCAN_ADDR_WIDTH; -const HUFFMAN_PRESCAN_RAM_DATA_W: u32 = huffman_literals_dec::PRESCAN_DATA_WIDTH; -const HUFFMAN_PRESCAN_RAM_PARTITION_WORD_SIZE: u32 = huffman_literals_dec::PRESCAN_PARTITION_WORD_SIZE; -const HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::PRESCAN_NUM_PARTITIONS; +const TEST_HUFFMAN_PRESCAN_RAM_SIZE: u32 = huffman_literals_dec::RAM_SIZE; +const TEST_HUFFMAN_PRESCAN_RAM_ADDR_W: u32 = huffman_literals_dec::PRESCAN_ADDR_WIDTH; +const TEST_HUFFMAN_PRESCAN_RAM_DATA_W: u32 = huffman_literals_dec::PRESCAN_DATA_WIDTH; +const TEST_HUFFMAN_PRESCAN_RAM_WORD_PARTITION_SIZE: u32 = huffman_literals_dec::PRESCAN_PARTITION_WORD_SIZE; +const TEST_HUFFMAN_PRESCAN_RAM_NUM_PARTITIONS: u32 = huffman_literals_dec::PRESCAN_NUM_PARTITIONS; + +const TEST_HUFFMAN_WEIGHTS_DPD_RAM_DATA_W = u32:16; +const TEST_HUFFMAN_WEIGHTS_DPD_RAM_SIZE = u32:256; +const TEST_HUFFMAN_WEIGHTS_DPD_RAM_ADDR_W = std::clog2(TEST_HUFFMAN_WEIGHTS_DPD_RAM_SIZE); +const TEST_HUFFMAN_WEIGHTS_DPD_RAM_WORD_PARTITION_SIZE = TEST_HUFFMAN_WEIGHTS_DPD_RAM_DATA_W; +const TEST_HUFFMAN_WEIGHTS_DPD_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_HUFFMAN_WEIGHTS_DPD_RAM_WORD_PARTITION_SIZE, TEST_HUFFMAN_WEIGHTS_DPD_RAM_DATA_W); + +const TEST_HUFFMAN_WEIGHTS_FSE_RAM_DATA_W = u32:32; +const TEST_HUFFMAN_WEIGHTS_FSE_RAM_SIZE = u32:1 << common::FSE_MAX_ACCURACY_LOG; +const TEST_HUFFMAN_WEIGHTS_FSE_RAM_ADDR_W = std::clog2(TEST_HUFFMAN_WEIGHTS_FSE_RAM_SIZE); +const TEST_HUFFMAN_WEIGHTS_FSE_RAM_WORD_PARTITION_SIZE = TEST_HUFFMAN_WEIGHTS_FSE_RAM_DATA_W; +const TEST_HUFFMAN_WEIGHTS_FSE_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_HUFFMAN_WEIGHTS_FSE_RAM_WORD_PARTITION_SIZE, TEST_HUFFMAN_WEIGHTS_FSE_RAM_DATA_W); + +const TEST_HUFFMAN_WEIGHTS_TMP_RAM_DATA_W = u32:16; +const TEST_HUFFMAN_WEIGHTS_TMP_RAM_SIZE = u32:256; +const TEST_HUFFMAN_WEIGHTS_TMP_RAM_ADDR_W = std::clog2(TEST_HUFFMAN_WEIGHTS_TMP_RAM_SIZE); +const TEST_HUFFMAN_WEIGHTS_TMP_RAM_WORD_PARTITION_SIZE = TEST_HUFFMAN_WEIGHTS_TMP_RAM_DATA_W; +const TEST_HUFFMAN_WEIGHTS_TMP_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_HUFFMAN_WEIGHTS_TMP_RAM_WORD_PARTITION_SIZE, TEST_HUFFMAN_WEIGHTS_TMP_RAM_DATA_W); + +const TEST_HUFFMAN_WEIGHTS_TMP2_RAM_DATA_W = u32:8; +const TEST_HUFFMAN_WEIGHTS_TMP2_RAM_SIZE = u32:512; +const TEST_HUFFMAN_WEIGHTS_TMP2_RAM_ADDR_W = std::clog2(TEST_HUFFMAN_WEIGHTS_TMP2_RAM_SIZE); +const TEST_HUFFMAN_WEIGHTS_TMP2_RAM_WORD_PARTITION_SIZE = TEST_HUFFMAN_WEIGHTS_TMP2_RAM_DATA_W; +const TEST_HUFFMAN_WEIGHTS_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions( + TEST_HUFFMAN_WEIGHTS_TMP2_RAM_WORD_PARTITION_SIZE, TEST_HUFFMAN_WEIGHTS_TMP2_RAM_DATA_W); const HISTORY_BUFFER_SIZE_KB = common::HISTORY_BUFFER_SIZE_KB; @@ -116,7 +144,7 @@ const LITERALS_BUFFER_RAM_DATA_W: u32 = literals_buffer::RAM_DATA_WIDTH; const LITERALS_BUFFER_RAM_NUM_PARTITIONS: u32 = literals_buffer::RAM_NUM_PARTITIONS; const LITERALS_BUFFER_RAM_WORD_PARTITION_SIZE: u32 = LITERALS_BUFFER_RAM_DATA_W; -const AXI_CHAN_N = u32:10; +const AXI_CHAN_N = u32:11; const TEST_MOCK_OUTPUT_RAM_SIZE:u32 = TEST_RAM_SIZE; @@ -194,15 +222,36 @@ proc ZstdDecoderTest { type SequenceExecutorPacket = common::SequenceExecutorPacket; type CommandConstructorData = common::CommandConstructorData; - type HuffmanWeightsReadReq = ram::ReadReq; - type HuffmanWeightsReadResp = ram::ReadResp; - type HuffmanWeightsWriteReq = ram::WriteReq; + type HuffmanWeightsReadReq = ram::ReadReq; + type HuffmanWeightsReadResp = ram::ReadResp; + type HuffmanWeightsWriteReq = ram::WriteReq; type HuffmanWeightsWriteResp = ram::WriteResp; - type HuffmanPrescanReadReq = ram::ReadReq; - type HuffmanPrescanReadResp = ram::ReadResp; - type HuffmanPrescanWriteReq = ram::WriteReq; + + type HuffmanPrescanReadReq = ram::ReadReq; + type HuffmanPrescanReadResp = ram::ReadResp; + type HuffmanPrescanWriteReq = ram::WriteReq; type HuffmanPrescanWriteResp = ram::WriteResp; + type HuffmanWeightsDpdRamRdReq = ram::ReadReq; + type HuffmanWeightsDpdRamRdResp = ram::ReadResp; + type HuffmanWeightsDpdRamWrReq = ram::WriteReq; + type HuffmanWeightsDpdRamWrResp = ram::WriteResp; + + type HuffmanWeightsTmpRamRdReq = ram::ReadReq; + type HuffmanWeightsTmpRamRdResp = ram::ReadResp; + type HuffmanWeightsTmpRamWrReq = ram::WriteReq; + type HuffmanWeightsTmpRamWrResp = ram::WriteResp; + + type HuffmanWeightsTmp2RamRdReq = ram::ReadReq; + type HuffmanWeightsTmp2RamRdResp = ram::ReadResp; + type HuffmanWeightsTmp2RamWrReq = ram::WriteReq; + type HuffmanWeightsTmp2RamWrResp = ram::WriteResp; + + type HuffmanWeightsFseRamRdReq = ram::ReadReq; + type HuffmanWeightsFseRamRdResp = ram::ReadResp; + type HuffmanWeightsFseRamWrReq = ram::WriteReq; + type HuffmanWeightsFseRamWrResp = ram::WriteResp; + type LitBufRamRdReq = ram::ReadReq; type LitBufRamRdResp = ram::ReadResp; type LitBufRamWrReq = ram::WriteReq; @@ -308,31 +357,83 @@ proc ZstdDecoderTest { let (reset_s, reset_r) = chan<()>("reset"); // Huffman weights memory - let (huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_req_r) = chan("huffman_lit_weights_mem_rd_req"); - let (huffman_lit_weights_mem_rd_resp_s, huffman_lit_weights_mem_rd_resp_r) = chan("huffman_lit_weights_mem_rd_resp"); - let (huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_req_r) = chan("huffman_lit_weights_mem_wr_req"); - let (huffman_lit_weights_mem_wr_resp_s, huffman_lit_weights_mem_wr_resp_r) = chan("huffman_lit_weights_mem_wr_resp"); + let (huffman_lit_weights_rd_req_s, huffman_lit_weights_rd_req_r) = chan("huffman_lit_weights_rd_req"); + let (huffman_lit_weights_rd_resp_s, huffman_lit_weights_rd_resp_r) = chan("huffman_lit_weights_rd_resp"); + let (huffman_lit_weights_wr_req_s, huffman_lit_weights_wr_req_r) = chan("huffman_lit_weights_wr_req"); + let (huffman_lit_weights_wr_resp_s, huffman_lit_weights_wr_resp_r) = chan("huffman_lit_weights_wr_resp"); spawn ram::RamModel< - HUFFMAN_WEIGHTS_RAM_DATA_W, HUFFMAN_WEIGHTS_RAM_SIZE, HUFFMAN_WEIGHTS_RAM_PARTITION_WORD_SIZE, + TEST_HUFFMAN_WEIGHTS_RAM_DATA_W, TEST_HUFFMAN_WEIGHTS_RAM_SIZE, TEST_HUFFMAN_WEIGHTS_RAM_WORD_PARTITION_SIZE, TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED >( - huffman_lit_weights_mem_rd_req_r, huffman_lit_weights_mem_rd_resp_s, - huffman_lit_weights_mem_wr_req_r, huffman_lit_weights_mem_wr_resp_s, + huffman_lit_weights_rd_req_r, huffman_lit_weights_rd_resp_s, + huffman_lit_weights_wr_req_r, huffman_lit_weights_wr_resp_s, ); // Huffman prescan memory - let (huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_req_r) = chan("huffman_lit_prescan_mem_rd_req"); - let (huffman_lit_prescan_mem_rd_resp_s, huffman_lit_prescan_mem_rd_resp_r) = chan("huffman_lit_prescan_mem_rd_resp"); - let (huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_req_r) = chan("huffman_lit_prescan_mem_wr_req"); - let (huffman_lit_prescan_mem_wr_resp_s, huffman_lit_prescan_mem_wr_resp_r) = chan("huffman_lit_prescan_mem_wr_resp"); + let (huffman_lit_prescan_rd_req_s, huffman_lit_prescan_rd_req_r) = chan("huffman_lit_prescan_rd_req"); + let (huffman_lit_prescan_rd_resp_s, huffman_lit_prescan_rd_resp_r) = chan("huffman_lit_prescan_rd_resp"); + let (huffman_lit_prescan_wr_req_s, huffman_lit_prescan_wr_req_r) = chan("huffman_lit_prescan_wr_req"); + let (huffman_lit_prescan_wr_resp_s, huffman_lit_prescan_wr_resp_r) = chan("huffman_lit_prescan_wr_resp"); + + spawn ram::RamModel< + TEST_HUFFMAN_PRESCAN_RAM_DATA_W, TEST_HUFFMAN_PRESCAN_RAM_SIZE, TEST_HUFFMAN_PRESCAN_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED + >( + huffman_lit_prescan_rd_req_r, huffman_lit_prescan_rd_resp_s, + huffman_lit_prescan_wr_req_r, huffman_lit_prescan_wr_resp_s, + ); + + let (huffman_lit_weights_dpd_rd_req_s, huffman_lit_weights_dpd_rd_req_r) = chan("huffman_lit_weights_dpd_rd_req"); + let (huffman_lit_weights_dpd_rd_resp_s, huffman_lit_weights_dpd_rd_resp_r) = chan("huffman_lit_weights_dpd_rd_resp_r"); + let (huffman_lit_weights_dpd_wr_req_s, huffman_lit_weights_dpd_wr_req_r) = chan("huffman_lit_weights_dpd_wr_req"); + let (huffman_lit_weights_dpd_wr_resp_s, huffman_lit_weights_dpd_wr_resp_r) = chan("huffman_lit_weights_dpd_wr_resp"); + + spawn ram::RamModel< + TEST_HUFFMAN_WEIGHTS_DPD_RAM_DATA_W, TEST_HUFFMAN_WEIGHTS_DPD_RAM_SIZE, TEST_HUFFMAN_WEIGHTS_DPD_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED + >( + huffman_lit_weights_dpd_rd_req_r, huffman_lit_weights_dpd_rd_resp_s, + huffman_lit_weights_dpd_wr_req_r, huffman_lit_weights_dpd_wr_resp_s, + ); + + let (huffman_lit_weights_tmp_rd_req_s, huffman_lit_weights_tmp_rd_req_r) = chan("huffman_lit_weights_tmp_rd_req"); + let (huffman_lit_weights_tmp_rd_resp_s, huffman_lit_weights_tmp_rd_resp_r) = chan("huffman_lit_weights_tmp_rd_resp"); + let (huffman_lit_weights_tmp_wr_req_s, huffman_lit_weights_tmp_wr_req_r) = chan("huffman_lit_weights_tmp_wr_req"); + let (huffman_lit_weights_tmp_wr_resp_s, huffman_lit_weights_tmp_wr_resp_r) = chan("huffman_lit_weights_tmp_wr_resp"); + + spawn ram::RamModel< + TEST_HUFFMAN_WEIGHTS_TMP_RAM_DATA_W, TEST_HUFFMAN_WEIGHTS_TMP_RAM_SIZE, TEST_HUFFMAN_WEIGHTS_TMP_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED + >( + huffman_lit_weights_tmp_rd_req_r, huffman_lit_weights_tmp_rd_resp_s, + huffman_lit_weights_tmp_wr_req_r, huffman_lit_weights_tmp_wr_resp_s, + ); + + let (huffman_lit_weights_tmp2_rd_req_s, huffman_lit_weights_tmp2_rd_req_r) = chan("huffman_lit_weights_tmp2_rd_req"); + let (huffman_lit_weights_tmp2_rd_resp_s, huffman_lit_weights_tmp2_rd_resp_r) = chan("huffman_lit_weights_tmp2_rd_resp"); + let (huffman_lit_weights_tmp2_wr_req_s, huffman_lit_weights_tmp2_wr_req_r) = chan("huffman_lit_weights_tmp2_wr_req"); + let (huffman_lit_weights_tmp2_wr_resp_s, huffman_lit_weights_tmp2_wr_resp_r) = chan("huffman_lit_weights_tmp2_wr_resp"); + + spawn ram::RamModel< + TEST_HUFFMAN_WEIGHTS_TMP2_RAM_DATA_W, TEST_HUFFMAN_WEIGHTS_TMP2_RAM_SIZE, TEST_HUFFMAN_WEIGHTS_TMP2_RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED + >( + huffman_lit_weights_tmp2_rd_req_r, huffman_lit_weights_tmp2_rd_resp_s, + huffman_lit_weights_tmp2_wr_req_r, huffman_lit_weights_tmp2_wr_resp_s, + ); + + let (huffman_lit_weights_fse_rd_req_s, huffman_lit_weights_fse_rd_req_r) = chan("huffman_lit_weights_fse_rd_req"); + let (huffman_lit_weights_fse_rd_resp_s, huffman_lit_weights_fse_rd_resp_r) = chan("huffman_lit_weights_fse_rd_resp_r"); + let (huffman_lit_weights_fse_wr_req_s, huffman_lit_weights_fse_wr_req_r) = chan("huffman_lit_weights_fse_wr_req"); + let (huffman_lit_weights_fse_wr_resp_s, huffman_lit_weights_fse_wr_resp_r) = chan("huffman_lit_weights_fse_wr_resp"); spawn ram::RamModel< - HUFFMAN_PRESCAN_RAM_DATA_W, HUFFMAN_PRESCAN_RAM_SIZE, HUFFMAN_PRESCAN_RAM_PARTITION_WORD_SIZE, + TEST_HUFFMAN_WEIGHTS_FSE_RAM_DATA_W, TEST_HUFFMAN_WEIGHTS_FSE_RAM_SIZE, TEST_HUFFMAN_WEIGHTS_FSE_RAM_WORD_PARTITION_SIZE, TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED >( - huffman_lit_prescan_mem_rd_req_r, huffman_lit_prescan_mem_rd_resp_s, - huffman_lit_prescan_mem_wr_req_r, huffman_lit_prescan_mem_wr_resp_s, + huffman_lit_weights_fse_rd_req_r, huffman_lit_weights_fse_rd_resp_s, + huffman_lit_weights_fse_wr_req_r, huffman_lit_weights_fse_wr_resp_s, ); // AXI channels for various blocks @@ -435,9 +536,7 @@ proc ZstdDecoderTest { let (ll_def_fse_wr_resp_s, ll_def_fse_wr_resp_r) = chan("ll_def_fse_wr_resp"); spawn ram_mux::RamMux< - TEST_FSE_RAM_ADDR_W, - TEST_FSE_RAM_DATA_W, - TEST_FSE_RAM_NUM_PARTITIONS, + TEST_FSE_RAM_ADDR_W, TEST_FSE_RAM_DATA_W, TEST_FSE_RAM_NUM_PARTITIONS, >( ll_sel_test_r, ll_def_test_rd_req_r, ll_def_test_rd_resp_s, ll_def_test_wr_req_r, ll_def_test_wr_resp_s, @@ -460,9 +559,7 @@ proc ZstdDecoderTest { let (ml_def_fse_wr_resp_s, ml_def_fse_wr_resp_r) = chan("ml_def_fse_wr_resp"); spawn ram_mux::RamMux< - TEST_FSE_RAM_ADDR_W, - TEST_FSE_RAM_DATA_W, - TEST_FSE_RAM_NUM_PARTITIONS, + TEST_FSE_RAM_ADDR_W, TEST_FSE_RAM_DATA_W, TEST_FSE_RAM_NUM_PARTITIONS, >( ml_sel_test_r, ml_def_test_rd_req_r, ml_def_test_rd_resp_s, ml_def_test_wr_req_r, ml_def_test_wr_resp_s, @@ -485,9 +582,7 @@ proc ZstdDecoderTest { let (of_def_fse_wr_resp_s, of_def_fse_wr_resp_r) = chan("of_def_fse_wr_resp"); spawn ram_mux::RamMux< - TEST_FSE_RAM_ADDR_W, - TEST_FSE_RAM_DATA_W, - TEST_FSE_RAM_NUM_PARTITIONS, + TEST_FSE_RAM_ADDR_W, TEST_FSE_RAM_DATA_W, TEST_FSE_RAM_NUM_PARTITIONS, >( of_sel_test_r, of_def_test_rd_req_r, of_def_test_rd_resp_s, of_def_test_wr_req_r, of_def_test_wr_resp_s, @@ -499,10 +594,17 @@ proc ZstdDecoderTest { TEST_AXI_DATA_W, TEST_AXI_ADDR_W, TEST_AXI_ID_W, TEST_AXI_DEST_W, TEST_REGS_N, TEST_WINDOW_LOG_MAX, TEST_HB_ADDR_W, TEST_HB_DATA_W, TEST_HB_NUM_PARTITIONS, TEST_HB_SIZE_KB, + TEST_DPD_RAM_ADDR_W, TEST_DPD_RAM_DATA_W, TEST_DPD_RAM_NUM_PARTITIONS, TEST_TMP_RAM_ADDR_W, TEST_TMP_RAM_DATA_W, TEST_TMP_RAM_NUM_PARTITIONS, TEST_TMP2_RAM_ADDR_W, TEST_TMP2_RAM_DATA_W, TEST_TMP2_RAM_NUM_PARTITIONS, TEST_FSE_RAM_ADDR_W, TEST_FSE_RAM_DATA_W, TEST_FSE_RAM_NUM_PARTITIONS, + + TEST_HUFFMAN_WEIGHTS_DPD_RAM_ADDR_W, TEST_HUFFMAN_WEIGHTS_DPD_RAM_DATA_W, TEST_HUFFMAN_WEIGHTS_DPD_RAM_NUM_PARTITIONS, + TEST_HUFFMAN_WEIGHTS_TMP_RAM_ADDR_W, TEST_HUFFMAN_WEIGHTS_TMP_RAM_DATA_W, TEST_HUFFMAN_WEIGHTS_TMP_RAM_NUM_PARTITIONS, + TEST_HUFFMAN_WEIGHTS_TMP2_RAM_ADDR_W, TEST_HUFFMAN_WEIGHTS_TMP2_RAM_DATA_W, TEST_HUFFMAN_WEIGHTS_TMP2_RAM_NUM_PARTITIONS, + TEST_HUFFMAN_WEIGHTS_FSE_RAM_ADDR_W, TEST_HUFFMAN_WEIGHTS_FSE_RAM_DATA_W, TEST_HUFFMAN_WEIGHTS_FSE_RAM_NUM_PARTITIONS, + HISTORY_BUFFER_SIZE_KB, AXI_CHAN_N, >( csr_axi_aw_r, csr_axi_w_r, csr_axi_b_s, csr_axi_ar_r, csr_axi_r_s, @@ -525,10 +627,19 @@ proc ZstdDecoderTest { litbuf_rd_req_s, litbuf_rd_resp_r, litbuf_wr_req_s, litbuf_wr_resp_r, - huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_resp_r, - huffman_lit_weights_mem_wr_req_s, huffman_lit_weights_mem_wr_resp_r, - huffman_lit_prescan_mem_rd_req_s, huffman_lit_prescan_mem_rd_resp_r, - huffman_lit_prescan_mem_wr_req_s, huffman_lit_prescan_mem_wr_resp_r, + huffman_lit_weights_rd_req_s, huffman_lit_weights_rd_resp_r, + huffman_lit_weights_wr_req_s, huffman_lit_weights_wr_resp_r, + huffman_lit_prescan_rd_req_s, huffman_lit_prescan_rd_resp_r, + huffman_lit_prescan_wr_req_s, huffman_lit_prescan_wr_resp_r, + huffman_lit_weights_dpd_rd_req_s, huffman_lit_weights_dpd_rd_resp_r, + huffman_lit_weights_dpd_wr_req_s, huffman_lit_weights_dpd_wr_resp_r, + huffman_lit_weights_tmp_rd_req_s, huffman_lit_weights_tmp_rd_resp_r, + huffman_lit_weights_tmp_wr_req_s, huffman_lit_weights_tmp_wr_resp_r, + huffman_lit_weights_tmp2_rd_req_s, huffman_lit_weights_tmp2_rd_resp_r, + huffman_lit_weights_tmp2_wr_req_s, huffman_lit_weights_tmp2_wr_resp_r, + huffman_lit_weights_fse_rd_req_s, huffman_lit_weights_fse_rd_resp_r, + huffman_lit_weights_fse_wr_req_s, huffman_lit_weights_fse_wr_resp_r, + output_axi_aw_s, output_axi_w_s, output_axi_b_r, // RAMs for SequenceExecutor From d2fb888e1f668f15d4b2e7011d46898765f82d07 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 29 Jan 2025 14:28:41 +0100 Subject: [PATCH 64/85] Remove duplicate fifo.v file Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 4 ++-- xls/modules/zstd/fifo.v | 52 ----------------------------------------- 2 files changed, 2 insertions(+), 54 deletions(-) delete mode 100644 xls/modules/zstd/fifo.v diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 747d0d1deb..95686b28ff 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -2598,7 +2598,7 @@ verilog_library( name = "huffman_prescan_verilog_lib", srcs = [ ":huffman_prescan.v", - "fifo.v" + "xls_fifo_wrapper.v" ], tags = ["manual"], ) @@ -2684,7 +2684,7 @@ verilog_library( name = "huffman_code_builder_verilog_lib", srcs = [ ":huffman_code_builder.v", - "fifo.v" + "xls_fifo_wrapper.v" ], tags = ["manual"], ) diff --git a/xls/modules/zstd/fifo.v b/xls/modules/zstd/fifo.v deleted file mode 100644 index 3a1633b093..0000000000 --- a/xls/modules/zstd/fifo.v +++ /dev/null @@ -1,52 +0,0 @@ -module xls_fifo_wrapper ( -clk, rst, -push_ready, push_data, push_valid, -pop_ready, pop_data, pop_valid); - parameter Width = 32, - Depth = 32, - EnableBypass = 0, - RegisterPushOutputs = 1, - RegisterPopOutputs = 1; - localparam AddrWidth = $clog2(Depth) + 1; - input wire clk; - input wire rst; - output wire push_ready; - input wire [Width-1:0] push_data; - input wire push_valid; - input wire pop_ready; - output wire [Width-1:0] pop_data; - output wire pop_valid; - - // Require depth be 1 and bypass disabled. - initial begin - if (EnableBypass || Depth != 1 || !RegisterPushOutputs || RegisterPopOutputs) begin - // FIFO configuration not supported. - // $fatal(1); - end - end - - - reg [Width-1:0] mem; - reg full; - - assign push_ready = !full; - assign pop_valid = full; - assign pop_data = mem; - - always @(posedge clk) begin - if (rst == 1'b1) begin - full <= 1'b0; - end else begin - if (push_valid && push_ready) begin - mem <= push_data; - full <= 1'b1; - end else if (pop_valid && pop_ready) begin - mem <= mem; - full <= 1'b0; - end else begin - mem <= mem; - full <= full; - end - end - end -endmodule From 5c414bffb125f5f870ee653a083eb03c44e9fd8b Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 29 Jan 2025 14:31:28 +0100 Subject: [PATCH 65/85] Move test files to a separate directory Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 10 +++++----- xls/modules/zstd/{ => data}/comp_frame.x | 0 xls/modules/zstd/{ => data}/comp_frame_fse_comp.x | 0 xls/modules/zstd/{ => data}/comp_frame_fse_repeated.x | 0 xls/modules/zstd/{ => data}/comp_frame_huffman.x | 0 xls/modules/zstd/{ => data}/comp_frame_huffman_fse.x | 0 xls/modules/zstd/zstd_dec_test.x | 8 ++++---- 7 files changed, 9 insertions(+), 9 deletions(-) rename xls/modules/zstd/{ => data}/comp_frame.x (100%) rename xls/modules/zstd/{ => data}/comp_frame_fse_comp.x (100%) rename xls/modules/zstd/{ => data}/comp_frame_fse_repeated.x (100%) rename xls/modules/zstd/{ => data}/comp_frame_huffman.x (100%) rename xls/modules/zstd/{ => data}/comp_frame_huffman_fse.x (100%) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 95686b28ff..4bc079e952 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1284,11 +1284,11 @@ xls_dslx_test( "zstd_dec.x", "zstd_dec_test.x", "zstd_frame_testcases.x", - "comp_frame.x", - "comp_frame_huffman.x", - "comp_frame_huffman_fse.x", - "comp_frame_fse_comp.x", - "comp_frame_fse_repeated.x", + "data/comp_frame.x", + "data/comp_frame_huffman.x", + "data/comp_frame_huffman_fse.x", + "data/comp_frame_fse_comp.x", + "data/comp_frame_fse_repeated.x", ], tags = ["manual"], deps = zstd_dec_deps, diff --git a/xls/modules/zstd/comp_frame.x b/xls/modules/zstd/data/comp_frame.x similarity index 100% rename from xls/modules/zstd/comp_frame.x rename to xls/modules/zstd/data/comp_frame.x diff --git a/xls/modules/zstd/comp_frame_fse_comp.x b/xls/modules/zstd/data/comp_frame_fse_comp.x similarity index 100% rename from xls/modules/zstd/comp_frame_fse_comp.x rename to xls/modules/zstd/data/comp_frame_fse_comp.x diff --git a/xls/modules/zstd/comp_frame_fse_repeated.x b/xls/modules/zstd/data/comp_frame_fse_repeated.x similarity index 100% rename from xls/modules/zstd/comp_frame_fse_repeated.x rename to xls/modules/zstd/data/comp_frame_fse_repeated.x diff --git a/xls/modules/zstd/comp_frame_huffman.x b/xls/modules/zstd/data/comp_frame_huffman.x similarity index 100% rename from xls/modules/zstd/comp_frame_huffman.x rename to xls/modules/zstd/data/comp_frame_huffman.x diff --git a/xls/modules/zstd/comp_frame_huffman_fse.x b/xls/modules/zstd/data/comp_frame_huffman_fse.x similarity index 100% rename from xls/modules/zstd/comp_frame_huffman_fse.x rename to xls/modules/zstd/data/comp_frame_huffman_fse.x diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x index a47531ca34..c56032cc0b 100644 --- a/xls/modules/zstd/zstd_dec_test.x +++ b/xls/modules/zstd/zstd_dec_test.x @@ -29,10 +29,10 @@ import xls.modules.zstd.literals_buffer; import xls.modules.zstd.fse_table_creator; import xls.modules.zstd.ram_mux; // import xls.modules.zstd.zstd_frame_testcases as comp_frame; -// import xls.modules.zstd.comp_frame_huffman as comp_frame; -// import xls.modules.zstd.comp_frame_fse_comp as comp_frame; -// import xls.modules.zstd.comp_frame_fse_repeated as comp_frame; -import xls.modules.zstd.comp_frame; +// import xls.modules.zstd.data.comp_frame_huffman as comp_frame; +// import xls.modules.zstd.data.comp_frame_fse_comp as comp_frame; +// import xls.modules.zstd.data.comp_frame_fse_repeated as comp_frame; +import xls.modules.zstd.data.comp_frame; const TEST_WINDOW_LOG_MAX = u32:30; From 54aef6a1231c64c0e10724199034bfd3b6042f42 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 29 Jan 2025 15:08:43 +0100 Subject: [PATCH 66/85] Move shift_buffer to zstd directory Signed-off-by: Robert Winkler --- xls/modules/shift_buffer/BUILD | 285 ------------------ xls/modules/shift_buffer/fixme.x | 52 ---- xls/modules/shift_buffer/math.x | 97 ------ xls/modules/shift_buffer/shift_buffer_inst.x | 71 ----- xls/modules/zstd/BUILD | 269 +++++++++++++++-- xls/modules/zstd/common.x | 2 +- xls/modules/zstd/comp_lookup_dec.x | 2 +- xls/modules/zstd/fse_lookup_dec.x | 2 +- xls/modules/zstd/fse_proba_freq_dec.x | 2 +- xls/modules/zstd/math.x | 32 +- xls/modules/zstd/refilling_shift_buffer.x | 2 +- xls/modules/zstd/sequence_dec.x | 2 +- .../{shift_buffer => zstd}/shift_buffer.x | 56 +++- 13 files changed, 329 insertions(+), 545 deletions(-) delete mode 100644 xls/modules/shift_buffer/BUILD delete mode 100644 xls/modules/shift_buffer/fixme.x delete mode 100644 xls/modules/shift_buffer/math.x delete mode 100644 xls/modules/shift_buffer/shift_buffer_inst.x rename xls/modules/{shift_buffer => zstd}/shift_buffer.x (94%) diff --git a/xls/modules/shift_buffer/BUILD b/xls/modules/shift_buffer/BUILD deleted file mode 100644 index 201bbf6fcc..0000000000 --- a/xls/modules/shift_buffer/BUILD +++ /dev/null @@ -1,285 +0,0 @@ -# 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 is a pipeline-aware data buffer. The implementation aligns incoming data -# before it enters the buffer and mutates its state. - -load("@rules_hdl//place_and_route:build_defs.bzl", "place_and_route") -load("@rules_hdl//synthesis:build_defs.bzl", "benchmark_synth", "synthesize_rtl") -load("@rules_hdl//verilog:providers.bzl", "verilog_library") -load( - "//xls/build_rules:xls_build_defs.bzl", - "xls_benchmark_ir", - "xls_benchmark_verilog", - "xls_dslx_ir", - "xls_dslx_library", - "xls_dslx_opt_ir", - "xls_dslx_test", - "xls_dslx_verilog", - "xls_ir_opt_ir", - "xls_ir_verilog", -) - -package( - default_applicable_licenses = ["//:license"], - default_visibility = ["//xls:xls_users"], - licenses = ["notice"], -) - -xls_dslx_library( - name = "fixme_dslx", - srcs = ["fixme.x"], -) - -xls_dslx_test( - name = "fixme_dslx_test", - library = ":fixme_dslx", -) - -xls_dslx_library( - name = "math_dslx", - srcs = ["math.x"], - deps = [":fixme_dslx"] -) - -xls_dslx_test( - name = "math_dslx_test", - library = ":math_dslx", -) - -## ShiftBuffer - -xls_dslx_library( - name = "shift_buffer_dslx", - srcs = ["shift_buffer.x"], - deps = [ - ":math_dslx", - ":fixme_dslx", - ], -) - -xls_dslx_test( - name = "shift_buffer_dslx_test", - library = ":shift_buffer_dslx", -) - -xls_dslx_library( - name = "shift_buffer_inst_dslx", - srcs = ["shift_buffer_inst.x"], - deps = [ - ":shift_buffer_dslx", - ], -) - -# ShiftBufferAligner - -xls_dslx_verilog( - name = "shift_buffer_aligner_verilog", - codegen_args = { - "module_name": "ShiftBufferAligner", - "generator": "pipeline", - "delay_model": "asap7", - "pipeline_stages": "2", - "reset": "rst", - "worst_case_throughput": "1", - "use_system_verilog": "false", - }, - dslx_top = "ShiftBufferAlignerInst", - library = ":shift_buffer_inst_dslx", - opt_ir_args = { - "top": "__xls_modules_shift_buffer_shift_buffer__ShiftBufferAlignerInst__ShiftBufferAligner_0__64_128_7_next", - }, - verilog_file = "shift_buffer_aligner.v", -) - -xls_benchmark_ir( - name = "shift_buffer_aligner_opt_ir_benchmark", - src = ":shift_buffer_aligner_verilog.opt.ir", - benchmark_ir_args = { - "pipeline_stages": "2", - "delay_model": "asap7", - }, -) - -verilog_library( - name = "shift_buffer_aligner_verilog_lib", - srcs = [ - ":shift_buffer_aligner.v", - ], -) - -synthesize_rtl( - name = "shift_buffer_aligner_synth_asap7", - standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", - top_module = "ShiftBufferAligner", - deps = [ - ":shift_buffer_aligner_verilog_lib", - ], -) - -benchmark_synth( - name = "shift_buffer_aligner_benchmark_synth", - synth_target = ":shift_buffer_aligner_synth_asap7", -) - -place_and_route( - name = "shift_buffer_aligner_place_and_route", - clock_period = "650", - core_padding_microns = 2, - min_pin_distance = "0.5", - placement_density = "0.30", - stop_after_step = "global_routing", - synthesized_rtl = ":shift_buffer_aligner_synth_asap7", - target_die_utilization_percentage = "5", -) - -xls_benchmark_verilog( - name = "shift_buffer_aligner_verilog_benchmark", - verilog_target = "shift_buffer_aligner_verilog", -) - -## ShiftBufferStorage - -xls_dslx_verilog( - name = "shift_buffer_storage_verilog", - codegen_args = { - "module_name": "ShiftBufferStorage", - "generator": "pipeline", - "delay_model": "asap7", - "pipeline_stages": "2", - "reset": "rst", - "worst_case_throughput": "1", - "use_system_verilog": "false", - }, - dslx_top = "ShiftBufferStorageInst", - library = ":shift_buffer_inst_dslx", - opt_ir_args = { - "top": "__xls_modules_shift_buffer_shift_buffer__ShiftBufferStorageInst__ShiftBufferStorage_0__64_7_next", - }, - verilog_file = "shift_buffer_storage.v", -) - -xls_benchmark_ir( - name = "shift_buffer_storage_opt_ir_benchmark", - src = ":shift_buffer_storage_verilog.opt.ir", - benchmark_ir_args = { - "pipeline_stages": "2", - "delay_model": "asap7", - }, -) - -verilog_library( - name = "shift_buffer_storage_verilog_lib", - srcs = [ - ":shift_buffer_storage.v", - ], -) - -synthesize_rtl( - name = "shift_buffer_storage_synth_asap7", - standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", - top_module = "ShiftBufferStorage", - deps = [ - ":shift_buffer_storage_verilog_lib", - ], -) - -benchmark_synth( - name = "shift_buffer_storage_benchmark_synth", - synth_target = ":shift_buffer_storage_synth_asap7", -) - -place_and_route( - name = "shift_buffer_storage_place_and_route", - clock_period = "650", - core_padding_microns = 2, - min_pin_distance = "0.5", - placement_density = "0.30", - stop_after_step = "global_routing", - synthesized_rtl = ":shift_buffer_storage_synth_asap7", - target_die_utilization_percentage = "5", -) - -xls_benchmark_verilog( - name = "shift_buffer_storage_verilog_benchmark", - verilog_target = "shift_buffer_storage_verilog", -) - -## ShiftBuffer - -xls_dslx_verilog( - name = "shift_buffer_verilog", - codegen_args = { - "module_name": "ShiftBuffer", - "generator": "pipeline", - "delay_model": "asap7", - "pipeline_stages": "2", - "reset": "rst", - "worst_case_throughput": "1", - "use_system_verilog": "false", - }, - dslx_top = "ShiftBufferInst", - library = ":shift_buffer_inst_dslx", - opt_ir_args = { - "inline_procs": "true", - "top": "__xls_modules_shift_buffer_shift_buffer__ShiftBufferInst__ShiftBuffer_0__ShiftBufferStorage_0__64_7_next", - }, - verilog_file = "shift_buffer.v", -) - -xls_benchmark_ir( - name = "shift_buffer_opt_ir_benchmark", - src = ":shift_buffer_verilog.opt.ir", - benchmark_ir_args = { - "pipeline_stages": "2", - "delay_model": "asap7", - }, -) - -verilog_library( - name = "shift_buffer_verilog_lib", - srcs = [ - ":shift_buffer.v", - ], -) - -synthesize_rtl( - name = "shift_buffer_synth_asap7", - standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", - top_module = "ShiftBuffer", - deps = [ - ":shift_buffer_verilog_lib", - ], -) - -benchmark_synth( - name = "shift_buffer_benchmark_synth", - synth_target = ":shift_buffer_synth_asap7", -) - -place_and_route( - name = "shift_buffer_place_and_route", - clock_period = "650", - core_padding_microns = 2, - min_pin_distance = "0.5", - placement_density = "0.30", - stop_after_step = "global_routing", - synthesized_rtl = ":shift_buffer_synth_asap7", - target_die_utilization_percentage = "5", -) - -xls_benchmark_verilog( - name = "shift_buffer_verilog_benchmark", - verilog_target = "shift_buffer_verilog", -) diff --git a/xls/modules/shift_buffer/fixme.x b/xls/modules/shift_buffer/fixme.x deleted file mode 100644 index 9fa79247c1..0000000000 --- a/xls/modules/shift_buffer/fixme.x +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2024 The XLS Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pub fn fast_if(cond: bool, arg1: uN[N], arg2: uN[N]) -> uN[N] { - let mask = if cond { !bits[N]:0 } else { bits[N]:0 }; - (arg1 & mask) | (arg2 & !mask) -} - -pub fn fast_if_tuple_2 - (cond: bool, arg1: (uN[N], uN[M]), arg2: (uN[N], uN[M])) -> (uN[N], uN[M]) { - (fast_if(cond, arg1.0, arg2.0), fast_if(cond, arg1.1, arg2.1)) -} - -pub fn fast_if_tuple_3 - (cond: bool, arg1: (uN[N], uN[M], uN[O]), arg2: (uN[N], uN[M], uN[O])) - -> (uN[N], uN[M], uN[O]) { - (fast_if(cond, arg1.0, arg2.0), fast_if(cond, arg1.1, arg2.1), fast_if(cond, arg1.2, arg2.2)) -} - -#[test] -fn fast_if_test() { - assert_eq(if true { u32:1 } else { u32:5 }, fast_if(true, u32:1, u32:5)); - assert_eq(if false { u32:1 } else { u32:5 }, fast_if(false, u32:1, u32:5)); - - assert_eq( - if true { - trace_fmt!("if: true"); - u16:74 - } else { - trace_fmt!("if: false"); - u16:32 - }, - fast_if(true, { - trace_fmt!("fast_if: true"); - u16:74 - }, { - trace_fmt!("fast_if: false"); - u16:32 - }) - ); -} diff --git a/xls/modules/shift_buffer/math.x b/xls/modules/shift_buffer/math.x deleted file mode 100644 index 057a843556..0000000000 --- a/xls/modules/shift_buffer/math.x +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2024 The XLS Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import std; -import xls.modules.shift_buffer.fixme; - -// Log-depth shift bits left -pub fn logshiftl(n: bits[N], r: bits[R]) -> bits[N] { - for (i, y) in u32:0..R { - fixme::fast_if(r[i+:u1], { y << (bits[R]:1 << i) }, { y }) - }(n as bits[N]) -} - -#[test] -fn logshiftl_test() { - // Test varying base - assert_eq(logshiftl(bits[64]:0, bits[6]:3), bits[64]:0 << u32:3); - assert_eq(logshiftl(bits[64]:1, bits[6]:3), bits[64]:1 << u32:3); - assert_eq(logshiftl(bits[64]:2, bits[6]:3), bits[64]:2 << u32:3); - assert_eq(logshiftl(bits[64]:3, bits[6]:3), bits[64]:3 << u32:3); - assert_eq(logshiftl(bits[64]:4, bits[6]:3), bits[64]:4 << u32:3); - - // Test varying exponent - assert_eq(logshiftl(bits[64]:50, bits[6]:0), bits[64]:50 << u32:0); - assert_eq(logshiftl(bits[64]:50, bits[6]:1), bits[64]:50 << u32:1); - assert_eq(logshiftl(bits[64]:50, bits[6]:2), bits[64]:50 << u32:2); - assert_eq(logshiftl(bits[64]:50, bits[6]:3), bits[64]:50 << u32:3); - assert_eq(logshiftl(bits[64]:50, bits[6]:4), bits[64]:50 << u32:4); - - // Test overflow - let max = std::unsigned_max_value(); - assert_eq(logshiftl(max, u4:4), max << u4:4); - assert_eq(logshiftl(max, u4:5), max << u4:5); - assert_eq(logshiftl(max, u4:15), max << u4:15); - assert_eq(logshiftl(bits[24]:0xc0ffee, u8:12), bits[24]:0xfee000); -} - - -// Log-depth shift bits right -pub fn logshiftr(n: bits[N], r: bits[R]) -> bits[N] { - for (i, y) in u32:0..R { - fixme::fast_if(r[i+:u1], { y >> (bits[R]:1 << i) }, { y }) - }(n as bits[N]) -} - -#[test] -fn logshiftr_test() { - // Test varying base - assert_eq(logshiftr(bits[64]:0x0fac4e782, bits[6]:3), bits[64]:0x0fac4e782 >> u32:3); - assert_eq(logshiftr(bits[64]:0x1fac4e782, bits[6]:3), bits[64]:0x1fac4e782 >> u32:3); - assert_eq(logshiftr(bits[64]:0x2fac4e782, bits[6]:3), bits[64]:0x2fac4e782 >> u32:3); - assert_eq(logshiftr(bits[64]:0x3fac4e782, bits[6]:3), bits[64]:0x3fac4e782 >> u32:3); - assert_eq(logshiftr(bits[64]:0x4fac4e782, bits[6]:3), bits[64]:0x4fac4e782 >> u32:3); - - // Test varying exponent - assert_eq(logshiftr(bits[64]:0x50fac4e782, bits[6]:0), bits[64]:0x50fac4e782 >> u32:0); - assert_eq(logshiftr(bits[64]:0x50fac4e782, bits[6]:1), bits[64]:0x50fac4e782 >> u32:1); - assert_eq(logshiftr(bits[64]:0x50fac4e782, bits[6]:2), bits[64]:0x50fac4e782 >> u32:2); - assert_eq(logshiftr(bits[64]:0x50fac4e782, bits[6]:3), bits[64]:0x50fac4e782 >> u32:3); - assert_eq(logshiftr(bits[64]:0x50fac4e782, bits[6]:4), bits[64]:0x50fac4e782 >> u32:4); - - // Test overflow - let max = std::unsigned_max_value(); - assert_eq(logshiftr(max, u4:4), max >> u4:4); - assert_eq(logshiftr(max, u4:5), max >> u4:5); - assert_eq(logshiftr(max, u4:15), max >> u4:15); - assert_eq(logshiftr(bits[24]:0xc0ffee, u8:12), bits[24]:0x000c0f); -} - -// Return given value with m first bits masked -pub fn mask(n: bits[N], m: bits[M]) -> bits[N] { - n & (std::mask_bits() >> (N as bits[M] - m)) -} - -#[test] -fn mask_test() { - assert_eq(mask(u8:0b11111111, u4:0), u8:0b00000000); - assert_eq(mask(u8:0b11111111, u4:1), u8:0b00000001); - assert_eq(mask(u8:0b11111111, u4:2), u8:0b00000011); - assert_eq(mask(u8:0b11111111, u4:4), u8:0b00001111); - assert_eq(mask(u8:0b11111111, u4:8), u8:0b11111111); - assert_eq(mask(u8:0b11111111, u4:9), u8:0b00000000); // FIXME: sketchy result, I would expect - // 0b11111111 -} - - diff --git a/xls/modules/shift_buffer/shift_buffer_inst.x b/xls/modules/shift_buffer/shift_buffer_inst.x deleted file mode 100644 index 44c2df8be9..0000000000 --- a/xls/modules/shift_buffer/shift_buffer_inst.x +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2024 The XLS Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import std; -import xls.modules.shift_buffer.shift_buffer; - -const TEST_DATA_WIDTH = u32:64; -const TEST_DATA_WIDTH_X2 = u32:128; -const TEST_LENGTH_WIDTH = std::clog2(TEST_DATA_WIDTH) + u32:1; - -proc ShiftBufferInst { - type Input = shift_buffer::ShiftBufferPacket; - type Ctrl = shift_buffer::ShiftBufferCtrl; - type Output = shift_buffer::ShiftBufferOutput; - input_r: chan> in; - ctrl_r: chan> in; - output_s: chan> out; - - config(input_r: chan> in, - ctrl_r: chan> in, - output_s: chan> out) { - - spawn shift_buffer::ShiftBuffer(ctrl_r, input_r, output_s); - - (input_r, ctrl_r, output_s) - } - - init { } - - next(state: ()) {} -} - -proc ShiftBufferAlignerInst { - type Input = shift_buffer::ShiftBufferPacket; - type Inter = shift_buffer::ShiftBufferPacket; - - config(input: chan in, inter: chan out) { - spawn shift_buffer::ShiftBufferAligner(input, inter); - } - - init { } - - next(state: ()) { } -} - -proc ShiftBufferStorageInst { - type Ctrl = shift_buffer::ShiftBufferCtrl; - type Inter = shift_buffer::ShiftBufferPacket; - type Output = shift_buffer::ShiftBufferOutput; - - config(ctrl: chan in, inter: chan in, output: chan out) { - spawn shift_buffer::ShiftBufferStorage(ctrl, inter, output); - } - - init { } - - next(state: ()) { } -} - - diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 4bc079e952..c1bc01988d 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -151,6 +151,237 @@ place_and_route( target_die_utilization_percentage = "10", ) +xls_dslx_library( + name = "shift_buffer_dslx", + srcs = ["shift_buffer.x"], + deps = [ + ":math_dslx", + ], +) + +xls_dslx_test( + name = "shift_buffer_dslx_test", + library = ":shift_buffer_dslx", + tags = ["manual"], +) + +xls_dslx_verilog( + name = "shift_buffer_aligner_verilog", + codegen_args = { + "module_name": "ShiftBufferAligner", + "generator": "pipeline", + "delay_model": "asap7", + "pipeline_stages": "2", + "reset": "rst", + "worst_case_throughput": "1", + "use_system_verilog": "false", + }, + dslx_top = "ShiftBufferAlignerInst", + library = ":shift_buffer_dslx", + opt_ir_args = { + "top": "__xls_modules_shift_buffer_shift_buffer__ShiftBufferAlignerInst__ShiftBufferAligner_0__64_128_7_next", + }, + verilog_file = "shift_buffer_aligner.v", + tags = ["manual"], +) + +xls_benchmark_ir( + name = "shift_buffer_aligner_opt_ir_benchmark", + src = ":shift_buffer_aligner_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "2", + "delay_model": "asap7", + }, + tags = ["manual"], +) + +verilog_library( + name = "shift_buffer_aligner_verilog_lib", + srcs = [ + ":shift_buffer_aligner.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "shift_buffer_aligner_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "ShiftBufferAligner", + deps = [ + ":shift_buffer_aligner_verilog_lib", + ], + tags = ["manual"], +) + +benchmark_synth( + name = "shift_buffer_aligner_benchmark_synth", + synth_target = ":shift_buffer_aligner_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "shift_buffer_aligner_place_and_route", + clock_period = "650", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":shift_buffer_aligner_synth_asap7", + target_die_utilization_percentage = "5", + tags = ["manual"], +) + +xls_benchmark_verilog( + name = "shift_buffer_aligner_verilog_benchmark", + verilog_target = "shift_buffer_aligner_verilog", + tags = ["manual"], +) + +xls_dslx_verilog( + name = "shift_buffer_storage_verilog", + codegen_args = { + "module_name": "ShiftBufferStorage", + "generator": "pipeline", + "delay_model": "asap7", + "pipeline_stages": "2", + "reset": "rst", + "worst_case_throughput": "1", + "use_system_verilog": "false", + }, + dslx_top = "ShiftBufferStorageInst", + library = ":shift_buffer_dslx", + opt_ir_args = { + "top": "__xls_modules_shift_buffer_shift_buffer__ShiftBufferStorageInst__ShiftBufferStorage_0__64_7_next", + }, + verilog_file = "shift_buffer_storage.v", + tags = ["manual"], +) + +xls_benchmark_ir( + name = "shift_buffer_storage_opt_ir_benchmark", + src = ":shift_buffer_storage_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "2", + "delay_model": "asap7", + }, + tags = ["manual"], +) + +verilog_library( + name = "shift_buffer_storage_verilog_lib", + srcs = [ + ":shift_buffer_storage.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "shift_buffer_storage_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "ShiftBufferStorage", + deps = [ + ":shift_buffer_storage_verilog_lib", + ], + tags = ["manual"], +) + +benchmark_synth( + name = "shift_buffer_storage_benchmark_synth", + synth_target = ":shift_buffer_storage_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "shift_buffer_storage_place_and_route", + clock_period = "650", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":shift_buffer_storage_synth_asap7", + target_die_utilization_percentage = "5", + tags = ["manual"], +) + +xls_benchmark_verilog( + name = "shift_buffer_storage_verilog_benchmark", + verilog_target = "shift_buffer_storage_verilog", + tags = ["manual"], +) + +xls_dslx_verilog( + name = "shift_buffer_verilog", + codegen_args = { + "module_name": "ShiftBuffer", + "generator": "pipeline", + "delay_model": "asap7", + "pipeline_stages": "2", + "reset": "rst", + "worst_case_throughput": "1", + "use_system_verilog": "false", + }, + dslx_top = "ShiftBufferInst", + library = ":shift_buffer_dslx", + opt_ir_args = { + "inline_procs": "true", + "top": "__xls_modules_shift_buffer_shift_buffer__ShiftBufferInst__ShiftBuffer_0__ShiftBufferStorage_0__64_7_next", + }, + verilog_file = "shift_buffer.v", + tags = ["manual"], +) + +xls_benchmark_ir( + name = "shift_buffer_opt_ir_benchmark", + src = ":shift_buffer_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "2", + "delay_model": "asap7", + }, + tags = ["manual"], +) + +verilog_library( + name = "shift_buffer_verilog_lib", + srcs = [ + ":shift_buffer.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "shift_buffer_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "ShiftBuffer", + deps = [ + ":shift_buffer_verilog_lib", + ], + tags = ["manual"], +) + +benchmark_synth( + name = "shift_buffer_benchmark_synth", + synth_target = ":shift_buffer_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "shift_buffer_place_and_route", + clock_period = "650", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":shift_buffer_synth_asap7", + target_die_utilization_percentage = "5", + tags = ["manual"], +) + +xls_benchmark_verilog( + name = "shift_buffer_verilog_benchmark", + verilog_target = "shift_buffer_verilog", + tags = ["manual"], +) + cc_library( name = "data_generator", srcs = ["data_generator.cc"], @@ -178,7 +409,7 @@ xls_dslx_library( "common.x", ], deps = [ - "//xls/modules/shift_buffer:shift_buffer_dslx", + ":shift_buffer_dslx", "//xls/examples:ram_dslx", ], ) @@ -971,7 +1202,7 @@ xls_dslx_library( ":common_dslx", ":ram_wr_handler_dslx", ":refilling_shift_buffer_dslx", - "//xls/modules/shift_buffer:shift_buffer_dslx", + ":shift_buffer_dslx", "//xls/examples:ram_dslx", ], ) @@ -1147,7 +1378,7 @@ xls_dslx_library( name = "refilling_shift_buffer_dslx", srcs = ["refilling_shift_buffer.x"], deps = [ - "//xls/modules/shift_buffer:shift_buffer_dslx", + ":shift_buffer_dslx", "//xls/modules/zstd/memory:mem_reader_dslx", ], ) @@ -1360,14 +1591,14 @@ xls_dslx_library( "comp_lookup_dec.x", ], deps = [ + ":shift_buffer_dslx", + ":common_dslx", + ":fse_table_creator_dslx", + ":refilling_shift_buffer_dslx", + ":fse_proba_freq_dec_dslx", "//xls/examples:ram_dslx", "//xls/modules/zstd/memory:axi_dslx", "//xls/modules/zstd/memory:axi_ram_dslx", - "//xls/modules/zstd:common_dslx", - "//xls/modules/zstd:fse_table_creator_dslx", - "//xls/modules/zstd:refilling_shift_buffer_dslx", - "//xls/modules/zstd:fse_proba_freq_dec_dslx", - "//xls/modules/shift_buffer:shift_buffer_dslx", ] ) @@ -1384,9 +1615,9 @@ xls_dslx_library( ], deps = [ ":common_dslx", + ":refilling_shift_buffer_dslx", + ":fse_table_creator_dslx", "//xls/examples:ram_dslx", - "//xls/modules/zstd:refilling_shift_buffer_dslx", - "//xls/modules/zstd:fse_table_creator_dslx", ] ) @@ -1842,7 +2073,7 @@ xls_dslx_library( ":common_dslx", ":math_dslx", ":fse_table_creator_dslx", - "//xls/modules/zstd:refilling_shift_buffer_dslx", + ":refilling_shift_buffer_dslx", "//xls/examples:ram_dslx", ], ) @@ -2040,10 +2271,10 @@ xls_dslx_library( ":ram_mux_dslx", ":fse_table_creator_dslx", ":fse_lookup_dec_dslx", + ":shift_buffer_dslx", + ":refilling_shift_buffer_dslx", "//xls/modules/zstd/memory:mem_reader_dslx", "//xls/modules/zstd/memory:axi_ram_dslx", - "//xls/modules/zstd:refilling_shift_buffer_dslx", - "//xls/modules/shift_buffer:shift_buffer_dslx", ], ) @@ -3051,15 +3282,15 @@ xls_dslx_library( "huffman_weights_dec.x", ], deps = [ - "//xls/modules/zstd:huffman_prescan_dslx", - "//xls/modules/zstd:refilling_shift_buffer_dslx", - "//xls/modules/zstd:comp_lookup_dec_dslx", - "//xls/modules/zstd:fse_table_creator_dslx", - "//xls/modules/zstd:math_dslx", + ":math_dslx", + ":huffman_prescan_dslx", + ":refilling_shift_buffer_dslx", + ":comp_lookup_dec_dslx", + ":fse_table_creator_dslx", + ":ram_mux_dslx", "//xls/modules/zstd/memory:mem_reader_dslx", "//xls/modules/zstd/memory:axi_ram_dslx", "//xls/examples:ram_dslx", - ":ram_mux_dslx", ], ) diff --git a/xls/modules/zstd/common.x b/xls/modules/zstd/common.x index 377a5aa2c4..3174ee6a40 100644 --- a/xls/modules/zstd/common.x +++ b/xls/modules/zstd/common.x @@ -14,7 +14,7 @@ import std; import xls.examples.ram; -import xls.modules.shift_buffer.shift_buffer; +import xls.modules.zstd.shift_buffer; pub const DATA_WIDTH = u32:64; pub const MAX_ID = u32::MAX; diff --git a/xls/modules/zstd/comp_lookup_dec.x b/xls/modules/zstd/comp_lookup_dec.x index fd18e038b0..5b32b1fc4e 100644 --- a/xls/modules/zstd/comp_lookup_dec.x +++ b/xls/modules/zstd/comp_lookup_dec.x @@ -22,7 +22,7 @@ import xls.modules.zstd.memory.axi_ram; import xls.modules.zstd.fse_table_creator; import xls.modules.zstd.refilling_shift_buffer; import xls.modules.zstd.fse_proba_freq_dec; -import xls.modules.shift_buffer.shift_buffer; +import xls.modules.zstd.shift_buffer; type AccuracyLog = common::FseAccuracyLog; type ConsumedFseBytes = fse_proba_freq_dec::ConsumedFseBytes; diff --git a/xls/modules/zstd/fse_lookup_dec.x b/xls/modules/zstd/fse_lookup_dec.x index 85664be982..6c586c0713 100644 --- a/xls/modules/zstd/fse_lookup_dec.x +++ b/xls/modules/zstd/fse_lookup_dec.x @@ -22,7 +22,7 @@ import xls.modules.zstd.memory.axi_ram; import xls.modules.zstd.fse_table_creator; import xls.modules.zstd.refilling_shift_buffer; import xls.modules.zstd.fse_proba_freq_dec; -import xls.modules.shift_buffer.shift_buffer; +import xls.modules.zstd.shift_buffer; import xls.modules.zstd.comp_lookup_dec; import xls.modules.zstd.rle_lookup_dec; import xls.modules.zstd.refilling_shift_buffer_mux; diff --git a/xls/modules/zstd/fse_proba_freq_dec.x b/xls/modules/zstd/fse_proba_freq_dec.x index fa75571494..6a8665e096 100644 --- a/xls/modules/zstd/fse_proba_freq_dec.x +++ b/xls/modules/zstd/fse_proba_freq_dec.x @@ -19,7 +19,7 @@ import std; import xls.examples.ram; import xls.modules.zstd.common; -import xls.modules.shift_buffer.shift_buffer; +import xls.modules.zstd.shift_buffer; import xls.modules.zstd.refilling_shift_buffer; import xls.modules.zstd.ram_wr_handler as ram_wr; diff --git a/xls/modules/zstd/math.x b/xls/modules/zstd/math.x index 1b9a8dd1db..7e733dc94a 100644 --- a/xls/modules/zstd/math.x +++ b/xls/modules/zstd/math.x @@ -14,21 +14,10 @@ import std; -fn fast_if(cond: bool, arg1: uN[N], arg2: uN[N]) -> uN[N] { - let mask = if cond { !bits[N]:0 } else { bits[N]:0 }; - (arg1 & mask) | (arg2 & !mask) -} - -#[test] -fn fast_if_test() { - assert_eq(if true { u32:1 } else { u32:5 }, fast_if(true, u32:1, u32:5)); - assert_eq(if false { u32:1 } else { u32:5 }, fast_if(false, u32:1, u32:5)); -} - // Log-depth shift bits left pub fn logshiftl(n: bits[N], r: bits[R]) -> bits[N] { for (i, y) in u32:0..R { - fast_if(r[i+:u1], { y << (bits[R]:1 << i) }, { y }) + if r[i+:u1] { y << (bits[R]:1 << i) } else { y } }(n as bits[N]) } @@ -56,10 +45,11 @@ fn logshiftl_test() { assert_eq(logshiftl(bits[24]:0xc0ffee, u8:12), bits[24]:0xfee000); } + // Log-depth shift bits right pub fn logshiftr(n: bits[N], r: bits[R]) -> bits[N] { for (i, y) in u32:0..R { - fast_if(r[i+:u1], { y >> (bits[R]:1 << i) }, { y }) + if r[i+:u1] { y >> (bits[R]:1 << i) } else { y } }(n as bits[N]) } @@ -86,3 +76,19 @@ fn logshiftr_test() { assert_eq(logshiftr(max, u4:15), max >> u4:15); assert_eq(logshiftr(bits[24]:0xc0ffee, u8:12), bits[24]:0x000c0f); } + +// Return given value with m first bits masked +pub fn mask(n: bits[N], m: bits[M]) -> bits[N] { + n & (std::mask_bits() >> (N as bits[M] - m)) +} + +#[test] +fn mask_test() { + assert_eq(mask(u8:0b11111111, u4:0), u8:0b00000000); + assert_eq(mask(u8:0b11111111, u4:1), u8:0b00000001); + assert_eq(mask(u8:0b11111111, u4:2), u8:0b00000011); + assert_eq(mask(u8:0b11111111, u4:4), u8:0b00001111); + assert_eq(mask(u8:0b11111111, u4:8), u8:0b11111111); + assert_eq(mask(u8:0b11111111, u4:9), u8:0b00000000); // FIXME: sketchy result, I would expect + // 0b11111111 +} diff --git a/xls/modules/zstd/refilling_shift_buffer.x b/xls/modules/zstd/refilling_shift_buffer.x index ced9665439..39001d600f 100644 --- a/xls/modules/zstd/refilling_shift_buffer.x +++ b/xls/modules/zstd/refilling_shift_buffer.x @@ -18,7 +18,7 @@ import std; -import xls.modules.shift_buffer.shift_buffer; +import xls.modules.zstd.shift_buffer; import xls.modules.zstd.memory.mem_reader; diff --git a/xls/modules/zstd/sequence_dec.x b/xls/modules/zstd/sequence_dec.x index b14f088c07..e84dd54f57 100644 --- a/xls/modules/zstd/sequence_dec.x +++ b/xls/modules/zstd/sequence_dec.x @@ -26,7 +26,7 @@ import xls.modules.zstd.ram_demux; import xls.modules.zstd.ram_mux; import xls.modules.zstd.refilling_shift_buffer; import xls.modules.zstd.fse_dec; -import xls.modules.shift_buffer.shift_buffer; +import xls.modules.zstd.shift_buffer; import xls.modules.zstd.fse_table_creator; diff --git a/xls/modules/shift_buffer/shift_buffer.x b/xls/modules/zstd/shift_buffer.x similarity index 94% rename from xls/modules/shift_buffer/shift_buffer.x rename to xls/modules/zstd/shift_buffer.x index add66ffe3e..b392825292 100644 --- a/xls/modules/shift_buffer/shift_buffer.x +++ b/xls/modules/zstd/shift_buffer.x @@ -13,8 +13,7 @@ // limitations under the License. import std; -import xls.modules.shift_buffer.fixme; -import xls.modules.shift_buffer.math; +import xls.modules.zstd.math; pub enum ShiftBufferStatus : u1 { OK = 0, @@ -467,6 +466,59 @@ pub proc ShiftBuffer { next(state: ()) { } } +const INST_DATA_WIDTH = u32:64; +const INST_DATA_WIDTH_X2 = u32:128; +const INST_LENGTH_WIDTH = std::clog2(INST_DATA_WIDTH) + u32:1; + +proc ShiftBufferInst { + type Input = ShiftBufferPacket; + type Ctrl = ShiftBufferCtrl; + type Output = ShiftBufferOutput; + input_r: chan> in; + ctrl_r: chan> in; + output_s: chan> out; + + config(input_r: chan> in, + ctrl_r: chan> in, + output_s: chan> out) { + + spawn ShiftBuffer(ctrl_r, input_r, output_s); + + (input_r, ctrl_r, output_s) + } + + init { } + + next(state: ()) {} +} + +proc ShiftBufferAlignerInst { + type Input = ShiftBufferPacket; + type Inter = ShiftBufferPacket; + + config(input: chan in, inter: chan out) { + spawn ShiftBufferAligner(input, inter); + } + + init { } + + next(state: ()) { } +} + +proc ShiftBufferStorageInst { + type Ctrl = ShiftBufferCtrl; + type Inter = ShiftBufferPacket; + type Output = ShiftBufferOutput; + + config(ctrl: chan in, inter: chan in, output: chan out) { + spawn ShiftBufferStorage(ctrl, inter, output); + } + + init { } + + next(state: ()) { } +} + const TEST_DATA_WIDTH = u32:64; const TEST_LENGTH_WIDTH = std::clog2(TEST_DATA_WIDTH) + u32:1; // TODO: other places in the code use length_width(TEST_DATA_WIDTH) which is clog2(TEST_DATA_WIDTH + 1) instead, why clog2(TEST_DATA_WIDTH) + 1 here? From 72486435cfe9cfd64aeab5405ce9409533621286 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 26 Mar 2025 22:43:54 +0100 Subject: [PATCH 67/85] Adjust BUILD files to the recent changes in the toolchain Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 186 +++++------------- xls/modules/zstd/memory/BUILD | 342 ++++++---------------------------- 2 files changed, 101 insertions(+), 427 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index c1bc01988d..107f2d5e7e 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -33,7 +33,6 @@ package( ) CLOCK_PERIOD_PS = "750" -# Clock periods for modules that exceed the 750ps critical path in IR benchmark common_codegen_args = { "delay_model": "asap7", @@ -43,6 +42,8 @@ common_codegen_args = { "clock_period_ps": CLOCK_PERIOD_PS, "clock_margin_percent": "20", "multi_proc": "true", + "fifo_module": "", + "materialize_internal_fifos": "true", } xls_dslx_library( @@ -67,7 +68,6 @@ xls_dslx_library( xls_dslx_test( name = "buffer_dslx_test", - dslx_test_args = {"compare": "jit"}, library = ":buffer_dslx", tags = ["manual"], ) @@ -84,15 +84,12 @@ xls_dslx_library( xls_dslx_test( name = "window_buffer_dslx_test", - dslx_test_args = {"compare": "jit"}, library = ":window_buffer_dslx", tags = ["manual"], ) window_buffer_codegen_args = common_codegen_args | { "module_name": "WindowBuffer64", - "clock_period_ps": "0", - "pipeline_stages": "1", "worst_case_throughput": "2", } @@ -108,10 +105,7 @@ xls_dslx_verilog( xls_benchmark_ir( name = "window_buffer_opt_ir_benchmark", src = ":window_buffer_verilog.opt.ir", - benchmark_ir_args = window_buffer_codegen_args | { - "pipeline_stages": "10", - "top": "__window_buffer__WindowBuffer64__WindowBuffer_0__64_32_48_next", - }, + benchmark_ir_args = window_buffer_codegen_args, tags = ["manual"], ) @@ -167,20 +161,9 @@ xls_dslx_test( xls_dslx_verilog( name = "shift_buffer_aligner_verilog", - codegen_args = { - "module_name": "ShiftBufferAligner", - "generator": "pipeline", - "delay_model": "asap7", - "pipeline_stages": "2", - "reset": "rst", - "worst_case_throughput": "1", - "use_system_verilog": "false", - }, + codegen_args = common_codegen_args, dslx_top = "ShiftBufferAlignerInst", library = ":shift_buffer_dslx", - opt_ir_args = { - "top": "__xls_modules_shift_buffer_shift_buffer__ShiftBufferAlignerInst__ShiftBufferAligner_0__64_128_7_next", - }, verilog_file = "shift_buffer_aligner.v", tags = ["manual"], ) @@ -188,10 +171,7 @@ xls_dslx_verilog( xls_benchmark_ir( name = "shift_buffer_aligner_opt_ir_benchmark", src = ":shift_buffer_aligner_verilog.opt.ir", - benchmark_ir_args = { - "pipeline_stages": "2", - "delay_model": "asap7", - }, + benchmark_ir_args = common_codegen_args, tags = ["manual"], ) @@ -221,7 +201,7 @@ benchmark_synth( place_and_route( name = "shift_buffer_aligner_place_and_route", - clock_period = "650", + clock_period = CLOCK_PERIOD_PS, core_padding_microns = 2, min_pin_distance = "0.5", placement_density = "0.30", @@ -237,22 +217,16 @@ xls_benchmark_verilog( tags = ["manual"], ) +shift_buffer_storage_codegen_args = common_codegen_args | { + "clock_period_ps": "0", + "pipeline_stages": "2", +} + xls_dslx_verilog( name = "shift_buffer_storage_verilog", - codegen_args = { - "module_name": "ShiftBufferStorage", - "generator": "pipeline", - "delay_model": "asap7", - "pipeline_stages": "2", - "reset": "rst", - "worst_case_throughput": "1", - "use_system_verilog": "false", - }, + codegen_args = shift_buffer_storage_codegen_args, dslx_top = "ShiftBufferStorageInst", library = ":shift_buffer_dslx", - opt_ir_args = { - "top": "__xls_modules_shift_buffer_shift_buffer__ShiftBufferStorageInst__ShiftBufferStorage_0__64_7_next", - }, verilog_file = "shift_buffer_storage.v", tags = ["manual"], ) @@ -260,10 +234,7 @@ xls_dslx_verilog( xls_benchmark_ir( name = "shift_buffer_storage_opt_ir_benchmark", src = ":shift_buffer_storage_verilog.opt.ir", - benchmark_ir_args = { - "pipeline_stages": "2", - "delay_model": "asap7", - }, + benchmark_ir_args = shift_buffer_storage_codegen_args, tags = ["manual"], ) @@ -293,7 +264,7 @@ benchmark_synth( place_and_route( name = "shift_buffer_storage_place_and_route", - clock_period = "650", + clock_period = CLOCK_PERIOD_PS, core_padding_microns = 2, min_pin_distance = "0.5", placement_density = "0.30", @@ -309,23 +280,17 @@ xls_benchmark_verilog( tags = ["manual"], ) +# FIXME: Improve the proc to achieve CLOCK_PERIOD_PS +SHIFT_BUFFER_CLOCK_PERIOD_PS = "1000" +shift_buffer_codegen_args = common_codegen_args | { + "clock_period_ps": SHIFT_BUFFER_CLOCK_PERIOD_PS, +} + xls_dslx_verilog( name = "shift_buffer_verilog", - codegen_args = { - "module_name": "ShiftBuffer", - "generator": "pipeline", - "delay_model": "asap7", - "pipeline_stages": "2", - "reset": "rst", - "worst_case_throughput": "1", - "use_system_verilog": "false", - }, + codegen_args = shift_buffer_codegen_args, dslx_top = "ShiftBufferInst", library = ":shift_buffer_dslx", - opt_ir_args = { - "inline_procs": "true", - "top": "__xls_modules_shift_buffer_shift_buffer__ShiftBufferInst__ShiftBuffer_0__ShiftBufferStorage_0__64_7_next", - }, verilog_file = "shift_buffer.v", tags = ["manual"], ) @@ -333,10 +298,7 @@ xls_dslx_verilog( xls_benchmark_ir( name = "shift_buffer_opt_ir_benchmark", src = ":shift_buffer_verilog.opt.ir", - benchmark_ir_args = { - "pipeline_stages": "2", - "delay_model": "asap7", - }, + benchmark_ir_args = shift_buffer_codegen_args, tags = ["manual"], ) @@ -366,7 +328,7 @@ benchmark_synth( place_and_route( name = "shift_buffer_place_and_route", - clock_period = "650", + clock_period = CLOCK_PERIOD_PS, core_padding_microns = 2, min_pin_distance = "0.5", placement_density = "0.30", @@ -460,6 +422,7 @@ verilog_library( name = "frame_header_dec_verilog_lib", srcs = [ ":frame_header_dec.v", + ":xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -555,6 +518,7 @@ verilog_library( name = "block_header_dec_verilog_lib", srcs = [ ":block_header_dec.v", + ":xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -633,6 +597,7 @@ verilog_library( name = "raw_block_dec_verilog_lib", srcs = [ ":raw_block_dec.v", + ":xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -710,6 +675,7 @@ verilog_library( name = "rle_block_dec_verilog_lib", srcs = [ ":rle_block_dec.v", + ":xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -787,6 +753,7 @@ verilog_library( name = "dec_mux_verilog_lib", srcs = [ ":dec_mux.v", + ":xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -903,10 +870,6 @@ xls_dslx_verilog( codegen_args = sequence_executor_codegen_args, dslx_top = "SequenceExecutorZstd", library = ":sequence_executor_dslx", - opt_ir_args = { - "inline_procs": "true", - "top": "__sequence_executor__SequenceExecutorZstd__SequenceExecutor_0__16_64_64_0_0_0_13_8192_65536_next", - }, tags = ["manual"], verilog_file = "sequence_executor.v", ) @@ -930,6 +893,7 @@ verilog_library( name = "sequence_executor_lib", srcs = [ ":sequence_executor.v", + ":xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -1009,6 +973,7 @@ verilog_library( name = "axi_csr_accessor_verilog_lib", srcs = [ ":axi_csr_accessor.v", + ":xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -1085,6 +1050,7 @@ verilog_library( name = "csr_config_verilog_lib", srcs = [ ":csr_config.v", + ":xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -1163,6 +1129,7 @@ verilog_library( name = "ram_rw_handler_verilog_lib", srcs = [ ":ram_rw_handler.v", + ":xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -1233,10 +1200,6 @@ xls_dslx_verilog( }, dslx_top = "FseProbaFreqDecoderInst", library = ":fse_proba_freq_dec_dslx", - opt_ir_args = { - "inline_procs": "true", - "top": "__fse_proba_freq_dec__FseProbaFreqDecoderInst__FseProbaFreqDecoder_0__64_7_8_10_1_next", - }, verilog_file = "fse_proba_freq_dec.v", tags = ["manual"], ) @@ -1247,7 +1210,6 @@ xls_benchmark_ir( benchmark_ir_args = { "pipeline_stages": "10", "delay_model": "asap7", - "inline_procs": "true", "reset": "rst", # FIXME: update ram rewrite #"ram_configurations": "ram:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( @@ -1271,6 +1233,7 @@ verilog_library( name = "fse_proba_freq_dec_lib", srcs = [ ":fse_proba_freq_dec.v", + ":xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -1338,7 +1301,6 @@ xls_benchmark_ir( tags = ["manual"], ) - xls_dslx_library( name = "sequence_conf_dec_dslx", srcs = ["sequence_conf_dec.x"], @@ -1406,7 +1368,6 @@ xls_benchmark_ir( src = ":refilling_shift_buffer_internal_verilog.opt.ir", benchmark_ir_args = { "pipeline_stages": "10", - "inline_procs": "false", }, tags = ["manual"], ) @@ -1415,6 +1376,7 @@ verilog_library( name = "refilling_shift_buffer_internal_verilog_lib", srcs = [ ":refilling_shift_buffer_internal.v", + ":xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -1469,7 +1431,7 @@ xls_dslx_library( xls_dslx_test( name = "comp_block_dec_dslx_test", library = ":comp_block_dec_dslx", - size = "large", + size = "enormous", tags = ["manual"], ) @@ -1521,6 +1483,7 @@ xls_dslx_test( "data/comp_frame_fse_comp.x", "data/comp_frame_fse_repeated.x", ], + size = "large", tags = ["manual"], deps = zstd_dec_deps, ) @@ -1542,10 +1505,7 @@ xls_dslx_verilog( xls_benchmark_ir( name = "zstd_dec_internal_opt_ir_benchmark", src = ":zstd_dec_internal_verilog.opt.ir", - benchmark_ir_args = { - "top": "__zstd_dec__ZstdDecoderInternalInst__ZstdDecoderInternal_0__16_64_8_4_16_next", - "pipeline_stages": "10", - }, + benchmark_ir_args = zstd_dec_internal_codegen_args, tags = ["manual"], ) @@ -1750,10 +1710,6 @@ xls_dslx_verilog( }, dslx_top = "FseTableCreatorInst", library = ":fse_table_creator_dslx", - opt_ir_args = { - "inline_procs": "true", - "top": "__fse_table_creator__FseTableCreatorInst__FseTableCreator_0__8_16_1_9_32_1_9_8_1_8_16_1_next", - }, verilog_file = "fse_table_creator.v", tags = ["manual"], ) @@ -1930,10 +1886,6 @@ xls_dslx_verilog( }, dslx_top = "RamDemuxInst", library = ":ram_demux_dslx", - opt_ir_args = { - "inline_procs": "true", - "top": "__ram_demux__RamDemuxInst__RamDemux_0__5_8_0_0_8_5_next", - }, verilog_file = "ram_demux.v", tags = ["manual"], ) @@ -2108,7 +2060,6 @@ xls_benchmark_ir( benchmark_ir_args = { "delay_model": "asap7", "pipeline_stages": "3", - "inline_procs": "false" }, tags = ["manual"], ) @@ -2197,7 +2148,6 @@ xls_benchmark_ir( benchmark_ir_args = { "delay_model": "asap7", "pipeline_stages": "3", - "inline_procs": "false" }, tags = ["manual"], ) @@ -2206,7 +2156,6 @@ verilog_library( name = "ram_demux3_verilog_lib", srcs = [ ":ram_demux3.v", - ":xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -2310,8 +2259,6 @@ xls_benchmark_ir( benchmark_ir_args = { "delay_model": "asap7", "pipeline_stages": "6", - "inline_procs": "false", - "multi_proc": "true", }, tags = ["manual"], ) @@ -2344,10 +2291,6 @@ xls_dslx_verilog( }, dslx_top = "RleLiteralsDecoderInst", library = ":rle_literals_dec_dslx", - opt_ir_args = { - "inline_procs": "true", - "top": "__rle_literals_dec__RleLiteralsDecoderInst__RleLiteralsDecoder_0__64_next", - }, verilog_file = "rle_literals_dec.v", tags = ["manual"], ) @@ -2523,13 +2466,10 @@ xls_dslx_verilog( "reset": "rst", "worst_case_throughput": "1", "use_system_verilog": "false", + "multi_proc": "true", }, dslx_top = "LiteralsBufferInst", library = ":literals_buffer_dslx", - opt_ir_args = { - "inline_procs": "true", - "top": "__literals_buffer__LiteralsBufferInst__LiteralsBuffer_0__LiteralsBufferReader_0__64_0_0_0_13_8192_65536_next" - }, verilog_file = "literals_buffer.v", tags = ["manual"], ) @@ -2621,9 +2561,6 @@ literals_decoder_ctrl_codegen_args = common_codegen_args | { xls_dslx_verilog( name = "literals_decoder_ctrl_verilog", codegen_args = literals_decoder_ctrl_codegen_args, - opt_ir_args = { - "inline_procs": "false", - }, dslx_top = "LiteralsDecoderCtrlInst", library = ":literals_decoder_dslx", verilog_file = "literals_decoder_ctrl.v", @@ -2635,7 +2572,6 @@ xls_benchmark_ir( src = ":literals_decoder_ctrl_verilog.opt.ir", benchmark_ir_args = literals_decoder_ctrl_codegen_args | { "multi_proc": "true", - "inline_procs": "false", }, tags = ["manual"], ) @@ -2699,10 +2635,6 @@ xls_dslx_verilog( }, dslx_top = "LiteralsDecoderInst", library = ":literals_decoder_dslx", - opt_ir_args = { - "inline_procs": "true", - "top": "__xls_modules_zstd_literals_buffer__LiteralsDecoderInst__LiteralsDecoder_0__LiteralsBuffer_0__LiteralsBufferReader_0__64_0_0_0_13_8192_65536_next", - }, verilog_file = "literals_decoder.v", tags = ["manual"], ) @@ -2714,8 +2646,6 @@ xls_benchmark_ir( "pipeline_stages": "10", "worst_case_throughput": "2", "delay_model": "asap7", - "inline_procs": "true", - "top": "__xls_modules_zstd_literals_buffer__LiteralsDecoderInst__LiteralsDecoder_0__LiteralsBuffer_0__LiteralsBufferReader_0__64_0_0_0_13_8192_65536_next", }, tags = ["manual"], ) @@ -2829,7 +2759,6 @@ verilog_library( name = "huffman_prescan_verilog_lib", srcs = [ ":huffman_prescan.v", - "xls_fifo_wrapper.v" ], tags = ["manual"], ) @@ -2915,7 +2844,6 @@ verilog_library( name = "huffman_code_builder_verilog_lib", srcs = [ ":huffman_code_builder.v", - "xls_fifo_wrapper.v" ], tags = ["manual"], ) @@ -2966,7 +2894,6 @@ xls_dslx_test( huffman_axi_reader_codegen_args = common_codegen_args | { "module_name": "HuffmanAxiReader", - "pipeline_stages": "8", "clock_period_ps": "750", "worst_case_throughput": "4", } @@ -2976,10 +2903,6 @@ xls_dslx_verilog( codegen_args = huffman_axi_reader_codegen_args, dslx_top = "HuffmanAxiReaderInst", library = ":huffman_axi_reader_dslx", - opt_ir_args = { - "inline_procs": "true", - "top": "__huffman_axi_reader__HuffmanAxiReaderInst__HuffmanAxiReader_0__16_8_3_64_4_4_next", - }, tags = ["manual"], verilog_file = "huffman_axi_reader.v", ) @@ -3058,10 +2981,6 @@ xls_dslx_verilog( codegen_args = huffman_data_preprocessor_codegen_args, dslx_top = "HuffmanDataPreprocessor", library = ":huffman_data_preprocessor_dslx", - opt_ir_args = { - "inline_procs": "true", - "top": "", - }, tags = ["manual"], verilog_file = "huffman_data_preprocessor.v", ) @@ -3139,10 +3058,6 @@ xls_dslx_verilog( codegen_args = huffman_decoder_codegen_args, dslx_top = "HuffmanDecoder", library = ":huffman_decoder_dslx", - opt_ir_args = { - "inline_procs": "true", - "top": "", - }, tags = ["manual"], verilog_file = "huffman_decoder.v", ) @@ -3225,10 +3140,6 @@ xls_dslx_verilog( codegen_args = huffman_ctrl_codegen_args, dslx_top = "HuffmanControlAndSequenceInst", library = ":huffman_ctrl_dslx", - opt_ir_args = { - "inline_procs": "true", - "top": "__huffman_ctrl__HuffmanControlAndSequenceInst__HuffmanControlAndSequence_0__32_64_next", - }, tags = ["manual"], verilog_file = "huffman_ctrl.v", ) @@ -3298,6 +3209,7 @@ xls_dslx_test( name = "huffman_weights_dec_dslx_test", library = ":huffman_weights_dec_dslx", tags = ["manual"], + size = "large", ) huffman_weights_dec_codegen_args = common_codegen_args | { @@ -3305,6 +3217,7 @@ huffman_weights_dec_codegen_args = common_codegen_args | { "pipeline_stages": "25", "clock_period_ps": "750", "worst_case_throughput": "17", + "multi_proc": "true" } xls_dslx_verilog( @@ -3313,8 +3226,7 @@ xls_dslx_verilog( dslx_top = "HuffmanWeightsDecoderInst", library = ":huffman_weights_dec_dslx", opt_ir_args = { - "inline_procs": "true", - "top": "__huffman_weights_dec__HuffmanWeightsDecoderInst__HuffmanWeightsDecoder_0__32_64_6_32_8_next", + "top": "__huffman_weights_dec__HuffmanWeightsDecoderInst__HuffmanWeightsDecoder_0__32_64_8_8_16_1_8_32_4_9_8_1_8_16_1_6_32_8_next", }, tags = ["manual"], verilog_file = "huffman_weights_dec.v", @@ -3396,16 +3308,12 @@ huffman_literals_dec_codegen_args = common_codegen_args | { } xls_dslx_verilog( - name = "huffman_literals_dec_verilog", - codegen_args = huffman_literals_dec_codegen_args, - dslx_top = "HuffmanLiteralsDecoderInst", - library = ":huffman_literals_dec_dslx", - opt_ir_args = { - "inline_procs": "true", - "top": "__xls_modules_zstd_huffman_decoder__HuffmanLiteralsDecoderInst__HuffmanLiteralsDecoder_0__HuffmanDecoder_0_next", - }, - tags = ["manual"], - verilog_file = "huffman_literals_dec.v", + name = "huffman_literals_dec_verilog", + codegen_args = huffman_literals_dec_codegen_args, + dslx_top = "HuffmanLiteralsDecoderInst", + library = ":huffman_literals_dec_dslx", + tags = ["manual"], + verilog_file = "huffman_literals_dec.v", ) xls_benchmark_ir( diff --git a/xls/modules/zstd/memory/BUILD b/xls/modules/zstd/memory/BUILD index e9ab9c0119..f5553c9eee 100644 --- a/xls/modules/zstd/memory/BUILD +++ b/xls/modules/zstd/memory/BUILD @@ -55,11 +55,6 @@ xls_dslx_test( CLOCK_PERIOD_PS = "750" -# Clock periods for modules that exceed the 750ps critical path in IR benchmark -AXI_READER_CLOCK_PERIOD_PS = "1800" - -MEM_READER_CLOCK_PERIOD_PS = "2600" - common_codegen_args = { "delay_model": "asap7", "reset": "rst", @@ -68,6 +63,13 @@ common_codegen_args = { "clock_period_ps": CLOCK_PERIOD_PS, "clock_margin_percent": "20", "multi_proc": "true", + "streaming_channel_data_suffix": "_data", + "fifo_module": "", + "materialize_internal_fifos": "true", + + # TODO: This should be adjusted when per channel separation of IO options is enabled + "flop_inputs_kind": "skid", + "flop_outputs_kind": "skid", } xls_dslx_library( @@ -86,12 +88,10 @@ xls_dslx_test( tags = ["manual"], ) +# FIXME: Improve the proc to achieve CLOCK_PERIOD_PS +AXI_READER_CLOCK_PERIOD_PS = "1700" + axi_reader_codegen_args = common_codegen_args | { - "module_name": "axi_reader", - "pipeline_stages": "2", - "streaming_channel_data_suffix": "_data", - "flop_inputs_kind": "skid", - "flop_outputs_kind": "skid", "clock_period_ps": AXI_READER_CLOCK_PERIOD_PS, } @@ -107,10 +107,7 @@ xls_dslx_verilog( xls_benchmark_ir( name = "axi_reader_opt_ir_benchmark", src = ":axi_reader_verilog.opt.ir", - benchmark_ir_args = axi_reader_codegen_args | { - "pipeline_stages": "10", - "top": "__axi_reader__AxiReaderInst__AxiReader_0__16_32_4_4_4_3_2_14_next", - }, + benchmark_ir_args = axi_reader_codegen_args, tags = ["manual"], ) @@ -140,7 +137,7 @@ benchmark_synth( place_and_route( name = "axi_reader_place_and_route", - clock_period = CLOCK_PERIOD_PS, + clock_period = AXI_READER_CLOCK_PERIOD_PS, core_padding_microns = 2, min_pin_distance = "0.5", placement_density = "0.30", @@ -164,74 +161,9 @@ xls_dslx_test( tags = ["manual"], ) -axi_stream_remove_empty_internal_codegen_args = common_codegen_args | { - "module_name": "axi_stream_remove_empty_internal", - "pipeline_stages": "1", -} - -xls_dslx_verilog( - name = "axi_stream_remove_empty_internal_verilog", - codegen_args = axi_stream_remove_empty_internal_codegen_args, - dslx_top = "AxiStreamRemoveEmptyInternalInst", - library = ":axi_stream_remove_empty_dslx", - tags = ["manual"], - verilog_file = "axi_stream_remove_empty_internal.v", -) - -xls_benchmark_ir( - name = "axi_stream_remove_empty_internal_opt_ir_benchmark", - src = ":axi_stream_remove_empty_internal_verilog.opt.ir", - benchmark_ir_args = axi_stream_remove_empty_internal_codegen_args | { - "pipeline_stages": "10", - "top": "__axi_stream_remove_empty__AxiStreamRemoveEmptyInternalInst__AxiStreamRemoveEmptyInternal_0__32_4_6_32_32_next", - }, - tags = ["manual"], -) - -verilog_library( - name = "axi_stream_remove_empty_internal_verilog_lib", - srcs = [ - ":axi_stream_remove_empty_internal.v", - ], - tags = ["manual"], -) - -synthesize_rtl( - name = "axi_stream_remove_empty_internal_synth_asap7", - standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", - tags = ["manual"], - top_module = "axi_stream_remove_empty_internal", - deps = [ - ":axi_stream_remove_empty_internal_verilog_lib", - ], -) - -benchmark_synth( - name = "axi_stream_remove_empty_internal_benchmark_synth", - synth_target = ":axi_stream_remove_empty_internal_synth_asap7", - tags = ["manual"], -) - -place_and_route( - name = "axi_stream_remove_empty_internal_place_and_route", - clock_period = CLOCK_PERIOD_PS, - core_padding_microns = 2, - min_pin_distance = "0.5", - placement_density = "0.30", - stop_after_step = "global_routing", - synthesized_rtl = ":axi_stream_remove_empty_internal_synth_asap7", - tags = ["manual"], - target_die_utilization_percentage = "10", -) - -axi_stream_remove_empty_codegen_args = common_codegen_args | { - "module_name": "axi_stream_remove_empty", - "pipeline_stages": "2", -} - xls_dslx_verilog( name = "axi_stream_remove_empty_verilog", - codegen_args = axi_stream_remove_empty_codegen_args, + codegen_args = common_codegen_args, dslx_top = "AxiStreamRemoveEmptyInst", library = ":axi_stream_remove_empty_dslx", tags = ["manual"], @@ -242,7 +174,6 @@ verilog_library( name = "axi_stream_remove_empty_verilog_lib", srcs = [ ":axi_stream_remove_empty.v", - "//xls/modules/zstd:xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -275,14 +206,9 @@ place_and_route( target_die_utilization_percentage = "10", ) -remove_empty_bytes_codegen_args = common_codegen_args | { - "module_name": "remove_empty_bytes", - "pipeline_stages": "2", -} - xls_dslx_verilog( name = "remove_empty_bytes_verilog", - codegen_args = remove_empty_bytes_codegen_args, + codegen_args = common_codegen_args, dslx_top = "RemoveEmptyBytesInst", library = ":axi_stream_remove_empty_dslx", tags = ["manual"], @@ -292,10 +218,7 @@ xls_dslx_verilog( xls_benchmark_ir( name = "remove_empty_bytes_opt_ir_benchmark", src = ":remove_empty_bytes_verilog.opt.ir", - benchmark_ir_args = remove_empty_bytes_codegen_args | { - "top": "__axi_stream_remove_empty__RemoveEmptyBytesInst__RemoveEmptyBytes_0__32_4_6_32_9_32_next", - "pipeline_stages": "10", - }, + benchmark_ir_args = common_codegen_args, tags = ["manual"], ) @@ -349,14 +272,9 @@ xls_dslx_test( tags = ["manual"], ) -axi_stream_downscaler_codegen_args = common_codegen_args | { - "module_name": "axi_stream_downscaler", - "pipeline_stages": "2", -} - xls_dslx_verilog( name = "axi_stream_downscaler_verilog", - codegen_args = axi_stream_downscaler_codegen_args, + codegen_args = common_codegen_args, dslx_top = "AxiStreamDownscalerInst", library = ":axi_stream_downscaler_dslx", tags = ["manual"], @@ -366,10 +284,7 @@ xls_dslx_verilog( xls_benchmark_ir( name = "axi_stream_downscaler_opt_ir_benchmark", src = ":axi_stream_downscaler_verilog.opt.ir", - benchmark_ir_args = axi_stream_downscaler_codegen_args | { - "pipeline_stages": "10", - "top": "__axi_stream_downscaler__AxiStreamDownscalerInst__AxiStreamDownscaler_0__8_8_128_16_32_4_4_3_next", - }, + benchmark_ir_args = common_codegen_args, tags = ["manual"], ) @@ -424,29 +339,25 @@ xls_dslx_test( library = ":axi_ram_dslx", ) +# FIXME: Improve the proc to achieve CLOCK_PERIOD_PS +AXI_RAM_READER_CLOCK_PERIOD_PS = "850" +axi_ram_reader_codegen_args = common_codegen_args | { + "clock_period_ps": AXI_RAM_READER_CLOCK_PERIOD_PS, + "ram_configurations": "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 5, + ram_name = "ram", + rd_req = "axi_ram__rd_req_s", + rd_resp = "axi_ram__rd_resp_r", + wr_req = "axi_ram__wr_req_s", + wr_resp = "axi_ram__wr_resp_r", + ), +} + xls_dslx_verilog( name = "axi_ram_verilog", - codegen_args = { - "module_name": "AxiRam", - "delay_model": "asap7", - "ram_configurations": "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( - latency = 5, - ram_name = "ram", - rd_req = "axi_ram__rd_req_s", - rd_resp = "axi_ram__rd_resp_r", - wr_req = "axi_ram__wr_req_s", - wr_resp = "axi_ram__wr_resp_r", - ), - "pipeline_stages": "8", - "reset": "rst", - "use_system_verilog": "false", - }, + codegen_args = axi_ram_reader_codegen_args, dslx_top = "AxiRamReaderInstWithEmptyWrites", library = ":axi_ram_dslx", - opt_ir_args = { - "inline_procs": "true", - "top": "__axi_ram__AxiRamReaderInstWithEmptyWrites__AxiRamReader_0__AxiRamReaderResponder_0__32_32_4_5_6_8_8_32768_7_32_5_6_4_100_next", - }, tags = ["manual"], verilog_file = "axi_ram.v", ) @@ -462,10 +373,7 @@ verilog_library( xls_benchmark_ir( name = "axi_ram_opt_ir_benchmark", src = ":axi_ram_verilog.opt.ir", - benchmark_ir_args = { - "pipeline_stages": "4", - "delay_model": "asap7", - }, + benchmark_ir_args = axi_ram_reader_codegen_args, tags = ["manual"], ) @@ -515,74 +423,11 @@ xls_dslx_test( tags = ["manual"], ) -mem_reader_internal_codegen_args = common_codegen_args | { - "module_name": "mem_reader_internal", - "pipeline_stages": "10", -} - -xls_dslx_verilog( - name = "mem_reader_internal_verilog", - codegen_args = mem_reader_internal_codegen_args, - dslx_top = "MemReaderInternalInst", - library = ":mem_reader_dslx", - tags = ["manual"], - verilog_file = "mem_reader_internal.v", -) - -xls_benchmark_ir( - name = "mem_reader_internal_opt_ir_benchmark", - src = ":mem_reader_internal_verilog.opt.ir", - benchmark_ir_args = common_codegen_args | { - "pipeline_stages": "10", - "top": "__mem_reader__MemReaderInternalInst__MemReaderInternal_0__16_128_16_8_8_2_2_16_64_8_next", - }, - tags = ["manual"], -) - -verilog_library( - name = "mem_reader_internal_verilog_lib", - srcs = [ - ":mem_reader_internal.v", - ], - tags = ["manual"], -) - -synthesize_rtl( - name = "mem_reader_internal_synth_asap7", - standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", - tags = ["manual"], - top_module = "mem_reader_internal", - deps = [ - ":mem_reader_internal_verilog_lib", - ], -) - -benchmark_synth( - name = "mem_reader_internal_benchmark_synth", - synth_target = ":mem_reader_internal_synth_asap7", - tags = ["manual"], -) - -place_and_route( - name = "mem_reader_internal_place_and_route", - clock_period = CLOCK_PERIOD_PS, - core_padding_microns = 2, - min_pin_distance = "0.5", - placement_density = "0.30", - stop_after_step = "global_routing", - synthesized_rtl = ":mem_reader_internal_synth_asap7", - tags = ["manual"], - target_die_utilization_percentage = "10", -) +# FIXME: Improve the proc to achieve CLOCK_PERIOD_PS +MEM_READER_CLOCK_PERIOD_PS = "2600" mem_reader_codegen_args = common_codegen_args | { "clock_period_ps": MEM_READER_CLOCK_PERIOD_PS, - "module_name": "mem_reader", - "pipeline_stages": "4", - "streaming_channel_data_suffix": "_data", - "flop_inputs_kind": "skid", - "flop_outputs_kind": "skid", - "materialize_internal_fifos": "true", } xls_dslx_verilog( @@ -602,6 +447,13 @@ verilog_library( tags = ["manual"], ) +xls_benchmark_ir( + name = "mem_reader_opt_ir_benchmark", + src = ":mem_reader_verilog.opt.ir", + benchmark_ir_args = mem_reader_codegen_args, + tags = ["manual"], +) + synthesize_rtl( name = "mem_reader_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", @@ -630,25 +482,22 @@ place_and_route( target_die_utilization_percentage = "10", ) -mem_reader_adv_codegen_args = common_codegen_args | { - "clock_period_ps": MEM_READER_CLOCK_PERIOD_PS, - "module_name": "mem_reader_adv", - "pipeline_stages": "4", - "streaming_channel_data_suffix": "_data", - "flop_inputs_kind": "skid", - "flop_outputs_kind": "skid", - "materialize_internal_fifos": "true", -} - xls_dslx_verilog( name = "mem_reader_adv_verilog", - codegen_args = mem_reader_adv_codegen_args, + codegen_args = mem_reader_codegen_args, dslx_top = "MemReaderAdvInst", library = ":mem_reader_dslx", tags = ["manual"], verilog_file = "mem_reader_adv.v", ) +xls_benchmark_ir( + name = "mem_reader_adv_opt_ir_benchmark", + src = ":mem_reader_adv_verilog.opt.ir", + benchmark_ir_args = mem_reader_codegen_args, + tags = ["manual"], +) + verilog_library( name = "mem_reader_adv_verilog_lib", srcs = [ @@ -701,17 +550,9 @@ xls_dslx_test( tags = ["manual"], ) -axi_writer_codegen_args = common_codegen_args | { - "module_name": "axi_writer", - "pipeline_stages": "1", - "streaming_channel_data_suffix": "_data", - "flop_inputs_kind": "skid", - "flop_outputs_kind": "skid", -} - xls_dslx_verilog( name = "axi_writer_verilog", - codegen_args = axi_writer_codegen_args, + codegen_args = common_codegen_args, dslx_top = "AxiWriterInst", library = ":axi_writer_dslx", tags = ["manual"], @@ -721,10 +562,7 @@ xls_dslx_verilog( xls_benchmark_ir( name = "axi_writer_opt_ir_benchmark", src = ":axi_writer_verilog.opt.ir", - benchmark_ir_args = axi_writer_codegen_args | { - "pipeline_stages": "10", - "top": "__axi_writer__AxiWriterInst__AxiWriter_0__16_32_4_4_4_2_next", - }, + benchmark_ir_args = common_codegen_args, tags = ["manual"], ) @@ -798,10 +636,7 @@ xls_dslx_verilog( xls_benchmark_ir( name = "axi_stream_add_empty_opt_ir_benchmark", src = ":axi_stream_add_empty_verilog.opt.ir", - benchmark_ir_args = axi_stream_add_empty_codegen_args | { - "pipeline_stages": "10", - "top": "__axi_stream_add_empty__AxiStreamAddEmptyInst__AxiStreamAddEmpty_0__16_32_4_2_32_32_next", - }, + benchmark_ir_args = axi_stream_add_empty_codegen_args, tags = ["manual"], ) @@ -859,78 +694,9 @@ xls_dslx_test( library = ":mem_writer_dslx", ) -mem_writer_internal_codegen_args = common_codegen_args | { - "module_name": "mem_writer_internal", - "pipeline_stages": "2", -} - -xls_dslx_verilog( - name = "mem_writer_internal_verilog", - codegen_args = mem_writer_internal_codegen_args, - dslx_top = "MemWriterInternalInst", - library = ":mem_writer_dslx", - tags = ["manual"], - verilog_file = "mem_writer_internal.v", -) - -xls_benchmark_ir( - name = "mem_writer_internal_opt_ir_benchmark", - src = ":mem_writer_internal_verilog.opt.ir", - benchmark_ir_args = common_codegen_args | { - "pipeline_stages": "10", - "top": "__mem_writer__MemWriterInternalInst__MemWriterInternal_0__16_32_4_4_4_2_next", - }, - tags = ["manual"], -) - -verilog_library( - name = "mem_writer_internal_verilog_lib", - srcs = [ - ":mem_writer_internal.v", - ], - tags = ["manual"], -) - -synthesize_rtl( - name = "mem_writer_internal_synth_asap7", - standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", - tags = ["manual"], - top_module = "mem_writer_internal", - deps = [ - ":mem_writer_internal_verilog_lib", - ], -) - -benchmark_synth( - name = "mem_writer_internal_benchmark_synth", - synth_target = ":mem_writer_internal_synth_asap7", - tags = ["manual"], -) - -place_and_route( - name = "mem_writer_internal_place_and_route", - clock_period = CLOCK_PERIOD_PS, - core_padding_microns = 2, - min_pin_distance = "0.5", - placement_density = "0.30", - stop_after_step = "global_routing", - synthesized_rtl = ":mem_writer_internal_synth_asap7", - tags = ["manual"], - target_die_utilization_percentage = "10", -) - -mem_writer_codegen_args = common_codegen_args | { - "module_name": "mem_writer", - "pipeline_stages": "10", - "streaming_channel_data_suffix": "_data", - "flop_inputs_kind": "skid", - "flop_outputs_kind": "skid", - "materialize_internal_fifos": "true", -} - xls_dslx_verilog( name = "mem_writer_verilog", - codegen_args = mem_writer_codegen_args, + codegen_args = common_codegen_args, dslx_top = "MemWriterInst", library = ":mem_writer_dslx", tags = ["manual"], From b4c7a36d479ebd2e9fbf118222bad1c98d75f829 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 26 Mar 2025 22:44:15 +0100 Subject: [PATCH 68/85] Adjust design to the recent changes in the toolchain Signed-off-by: Robert Winkler --- xls/modules/zstd/comp_block_dec.x | 3 ++- xls/modules/zstd/huffman_axi_reader.x | 6 +++--- xls/modules/zstd/huffman_literals_dec.x | 10 +++++----- xls/modules/zstd/memory/axi_ram.x | 7 +++++-- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/xls/modules/zstd/comp_block_dec.x b/xls/modules/zstd/comp_block_dec.x index 0cf51ff320..3c8df70fe7 100644 --- a/xls/modules/zstd/comp_block_dec.x +++ b/xls/modules/zstd/comp_block_dec.x @@ -1588,7 +1588,8 @@ proc CompressBlockDecoderTest { }(tok); let tok = send(tok, ml_sel_test_s, u1:1); - let tok = unroll_for!(test_i, tok): (u32, token) in range(u32:0, array_size(COMP_BLOCK_DEC_TESTCASES)) { + // TODO: Enable more test cases when posssible. Currently their number is limited to lower the RAM consumption + let tok = unroll_for!(test_i, tok): (u32, token) in u32[2]:[u32:0, u32:4] { let (input_length, input, output_length, output) = COMP_BLOCK_DEC_TESTCASES[test_i]; trace_fmt!("Loading testcase {}", test_i); diff --git a/xls/modules/zstd/huffman_axi_reader.x b/xls/modules/zstd/huffman_axi_reader.x index 5166789228..5030d9f14f 100644 --- a/xls/modules/zstd/huffman_axi_reader.x +++ b/xls/modules/zstd/huffman_axi_reader.x @@ -63,10 +63,10 @@ pub proc HuffmanAxiReader out, data_s: chan out, ) { - let (mem_rd_req_s, mem_rd_req_r) = chan("mem_rd_req"); - let (mem_rd_resp_s, mem_rd_resp_r) = chan("mem_rd_resp"); + let (mem_rd_req_s, mem_rd_req_r) = chan("mem_rd_req"); + let (mem_rd_resp_s, mem_rd_resp_r) = chan("mem_rd_resp"); - spawn mem_reader::MemReader ( + spawn mem_reader::MemReader ( mem_rd_req_r, mem_rd_resp_s, axi_ar_s, diff --git a/xls/modules/zstd/huffman_literals_dec.x b/xls/modules/zstd/huffman_literals_dec.x index 825fbc05b6..171b05f994 100644 --- a/xls/modules/zstd/huffman_literals_dec.x +++ b/xls/modules/zstd/huffman_literals_dec.x @@ -204,27 +204,27 @@ pub proc HuffmanLiteralsDecoder< jump_table_mem_rd_resp_r, ); - spawn mem_reader::MemReader( + spawn mem_reader::MemReader( jump_table_mem_rd_req_r, jump_table_mem_rd_resp_s, jump_table_axi_ar_s, jump_table_axi_r_r ); - spawn mem_reader::MemReader( + spawn mem_reader::MemReader( weights_header_dec_mem_rd_req_r, weights_header_dec_mem_rd_resp_s, weights_header_dec_axi_ar_s, weights_header_dec_axi_r_r ); - spawn mem_reader::MemReader( + spawn mem_reader::MemReader( weights_raw_dec_mem_rd_req_r, weights_raw_dec_mem_rd_resp_s, weights_raw_dec_axi_ar_s, weights_raw_dec_axi_r_r ); - spawn mem_reader::MemReader( + spawn mem_reader::MemReader( weights_fse_lookup_dec_mem_rd_req_r, weights_fse_lookup_dec_mem_rd_resp_s, weights_fse_lookup_dec_axi_ar_s, weights_fse_lookup_dec_axi_r_r ); - spawn mem_reader::MemReader( + spawn mem_reader::MemReader( weights_fse_decoder_dec_mem_rd_req_r, weights_fse_decoder_dec_mem_rd_resp_s, weights_fse_decoder_dec_axi_ar_s, weights_fse_decoder_dec_axi_r_r ); diff --git a/xls/modules/zstd/memory/axi_ram.x b/xls/modules/zstd/memory/axi_ram.x index 4886028a9c..edea056195 100644 --- a/xls/modules/zstd/memory/axi_ram.x +++ b/xls/modules/zstd/memory/axi_ram.x @@ -108,7 +108,9 @@ proc AxiRamReaderRequester< //if ar_bundle_valid { // trace_fmt!("{:#x}", ar_bundle); //} else {}; - let tok = send_if(tok, sync_s, ar_bundle_valid && !ar_bundle_ok, Sync { + + let error = ar_bundle_valid && !ar_bundle_ok; + let tok = send_if(tok, sync_s, error, Sync { id: ar_bundle.id, resp: AxiReadResp::SLVERR, last: true, @@ -156,7 +158,7 @@ proc AxiRamReaderRequester< ) }; - let tok = send_if(tok, sync_s, state.status == Status::READ_BURST, Sync { + let tok = send_if(tok, sync_s, !error && state.status == Status::READ_BURST, Sync { do_recv_ram_resp: do_read_from_ram, read_data_size: read_data_size, read_data_offset: read_data_offset, @@ -707,6 +709,7 @@ proc AxiRamReaderTest { AxiAxBurst::WRAP => { (axi_ar_bundle.addr + j * (u32:1 << (axi_ar_bundle.size as u32))) % (TEST_RAM_SIZE * (TEST_RAM_DATA_W / u32:8)) }, + _ => fail!("invalid_burst_mode", TestAxiAddr:0), }; // create expected data using RAM data let (expected_data, addr_valid) = for (k, (expected_data, addr_valid)): (u32, (uN[TEST_AXI_DATA_W], bool)) in range(u32:0, TEST_AXI_DATA_W / u32:8) { From d008483884ac6b1e8da0d9719ad607446a6f9306 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Mon, 7 Apr 2025 16:50:39 +0200 Subject: [PATCH 69/85] xls/modules/zstd/BUILD: Format with buildifer Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 299 ++++++++++++++++++++--------------------- 1 file changed, 149 insertions(+), 150 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 107f2d5e7e..e2c9011ea6 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -164,8 +164,8 @@ xls_dslx_verilog( codegen_args = common_codegen_args, dslx_top = "ShiftBufferAlignerInst", library = ":shift_buffer_dslx", - verilog_file = "shift_buffer_aligner.v", tags = ["manual"], + verilog_file = "shift_buffer_aligner.v", ) xls_benchmark_ir( @@ -186,11 +186,11 @@ verilog_library( synthesize_rtl( name = "shift_buffer_aligner_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "ShiftBufferAligner", deps = [ ":shift_buffer_aligner_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -207,14 +207,14 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":shift_buffer_aligner_synth_asap7", - target_die_utilization_percentage = "5", tags = ["manual"], + target_die_utilization_percentage = "5", ) xls_benchmark_verilog( name = "shift_buffer_aligner_verilog_benchmark", - verilog_target = "shift_buffer_aligner_verilog", tags = ["manual"], + verilog_target = "shift_buffer_aligner_verilog", ) shift_buffer_storage_codegen_args = common_codegen_args | { @@ -227,8 +227,8 @@ xls_dslx_verilog( codegen_args = shift_buffer_storage_codegen_args, dslx_top = "ShiftBufferStorageInst", library = ":shift_buffer_dslx", - verilog_file = "shift_buffer_storage.v", tags = ["manual"], + verilog_file = "shift_buffer_storage.v", ) xls_benchmark_ir( @@ -249,11 +249,11 @@ verilog_library( synthesize_rtl( name = "shift_buffer_storage_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "ShiftBufferStorage", deps = [ ":shift_buffer_storage_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -270,18 +270,19 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":shift_buffer_storage_synth_asap7", - target_die_utilization_percentage = "5", tags = ["manual"], + target_die_utilization_percentage = "5", ) xls_benchmark_verilog( name = "shift_buffer_storage_verilog_benchmark", - verilog_target = "shift_buffer_storage_verilog", tags = ["manual"], + verilog_target = "shift_buffer_storage_verilog", ) # FIXME: Improve the proc to achieve CLOCK_PERIOD_PS SHIFT_BUFFER_CLOCK_PERIOD_PS = "1000" + shift_buffer_codegen_args = common_codegen_args | { "clock_period_ps": SHIFT_BUFFER_CLOCK_PERIOD_PS, } @@ -291,8 +292,8 @@ xls_dslx_verilog( codegen_args = shift_buffer_codegen_args, dslx_top = "ShiftBufferInst", library = ":shift_buffer_dslx", - verilog_file = "shift_buffer.v", tags = ["manual"], + verilog_file = "shift_buffer.v", ) xls_benchmark_ir( @@ -313,11 +314,11 @@ verilog_library( synthesize_rtl( name = "shift_buffer_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "ShiftBuffer", deps = [ ":shift_buffer_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -334,14 +335,14 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":shift_buffer_synth_asap7", - target_die_utilization_percentage = "5", tags = ["manual"], + target_die_utilization_percentage = "5", ) xls_benchmark_verilog( name = "shift_buffer_verilog_benchmark", - verilog_target = "shift_buffer_verilog", tags = ["manual"], + verilog_target = "shift_buffer_verilog", ) cc_library( @@ -351,6 +352,7 @@ cc_library( data = [ "@zstd//:decodecorpus", ], + tags = ["manual"], deps = [ "//xls/common:subprocess", "//xls/common/file:filesystem", @@ -362,7 +364,6 @@ cc_library( "@com_google_absl//absl/time", "@com_google_absl//absl/types:span", ], - tags = ["manual"], ) xls_dslx_library( @@ -823,8 +824,8 @@ xls_dslx_library( ], deps = [ ":common_dslx", - ":ram_printer_dslx", ":parallel_rams_dslx", + ":ram_printer_dslx", "//xls/examples:ram_dslx", "//xls/modules/zstd/memory:mem_writer_dslx", ], @@ -918,9 +919,9 @@ place_and_route( name = "sequence_executor_place_and_route", clock_period = CLOCK_PERIOD_PS, core_padding_microns = 2, - min_pin_distance = "0.4", die_height_microns = 120, die_width_microns = 120, + min_pin_distance = "0.4", placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":sequence_executor_asap7", @@ -1111,8 +1112,8 @@ xls_dslx_verilog( opt_ir_args = { "top": "__ram_wr_handler__RamWrRespHandlerInst__RamWrRespHandler_0__32_next", }, - verilog_file = "ram_rw_handler.v", tags = ["manual"], + verilog_file = "ram_rw_handler.v", ) xls_benchmark_ir( @@ -1137,11 +1138,11 @@ verilog_library( synthesize_rtl( name = "ram_rw_handler_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "RamWrRespHandler", deps = [ ":ram_rw_handler_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -1158,8 +1159,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":ram_rw_handler_synth_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -1200,8 +1201,8 @@ xls_dslx_verilog( }, dslx_top = "FseProbaFreqDecoderInst", library = ":fse_proba_freq_dec_dslx", - verilog_file = "fse_proba_freq_dec.v", tags = ["manual"], + verilog_file = "fse_proba_freq_dec.v", ) xls_benchmark_ir( @@ -1225,8 +1226,8 @@ xls_benchmark_ir( xls_benchmark_verilog( name = "fse_proba_freq_dec_verilog_benchmark", - verilog_target = "fse_proba_freq_dec_verilog", tags = ["manual"], + verilog_target = "fse_proba_freq_dec_verilog", ) verilog_library( @@ -1241,11 +1242,11 @@ verilog_library( synthesize_rtl( name = "fse_proba_freq_dec_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "FseProbaFreqDec", deps = [ ":fse_proba_freq_dec_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -1262,8 +1263,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "detailed_routing", synthesized_rtl = ":fse_proba_freq_dec_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -1281,7 +1282,6 @@ xls_dslx_test( tags = ["manual"], ) - xls_dslx_verilog( name = "literals_block_header_dec_verilog", codegen_args = window_buffer_codegen_args, @@ -1305,8 +1305,8 @@ xls_dslx_library( name = "sequence_conf_dec_dslx", srcs = ["sequence_conf_dec.x"], deps = [ - "//xls/modules/zstd/memory:mem_reader_dslx", ":common_dslx", + "//xls/modules/zstd/memory:mem_reader_dslx", ], ) @@ -1355,7 +1355,7 @@ xls_dslx_test( xls_dslx_verilog( name = "refilling_shift_buffer_internal_verilog", codegen_args = common_codegen_args | { - "module_name": "RefillingShiftBufferInternalInst" + "module_name": "RefillingShiftBufferInternalInst", }, dslx_top = "RefillingShiftBufferInternalInst", library = ":refilling_shift_buffer_dslx", @@ -1413,25 +1413,25 @@ xls_dslx_library( name = "comp_block_dec_dslx", srcs = ["comp_block_dec.x"], deps = [ - "//xls/examples:ram_dslx", - "//xls/modules/zstd/memory:mem_reader_dslx", - "//xls/modules/zstd/memory:axi_ram_dslx", - ":fse_proba_freq_dec_dslx", - ":literals_block_header_dec_dslx", + ":command_constructor_dslx", ":common_dslx", + ":fse_proba_freq_dec_dslx", ":huffman_literals_dec_dslx", - ":parallel_rams_dslx", + ":literals_block_header_dec_dslx", ":literals_buffer_dslx", - ":sequence_dec_dslx", ":literals_decoder_dslx", - ":command_constructor_dslx", + ":parallel_rams_dslx", + ":sequence_dec_dslx", + "//xls/examples:ram_dslx", + "//xls/modules/zstd/memory:axi_ram_dslx", + "//xls/modules/zstd/memory:mem_reader_dslx", ], ) xls_dslx_test( name = "comp_block_dec_dslx_test", - library = ":comp_block_dec_dslx", size = "enormous", + library = ":comp_block_dec_dslx", tags = ["manual"], ) @@ -1473,17 +1473,17 @@ xls_dslx_library( xls_dslx_test( name = "zstd_dec_dslx_test", + size = "large", srcs = [ - "zstd_dec.x", - "zstd_dec_test.x", - "zstd_frame_testcases.x", "data/comp_frame.x", - "data/comp_frame_huffman.x", - "data/comp_frame_huffman_fse.x", "data/comp_frame_fse_comp.x", "data/comp_frame_fse_repeated.x", + "data/comp_frame_huffman.x", + "data/comp_frame_huffman_fse.x", + "zstd_dec.x", + "zstd_dec_test.x", + "zstd_frame_testcases.x", ], - size = "large", tags = ["manual"], deps = zstd_dec_deps, ) @@ -1551,15 +1551,15 @@ xls_dslx_library( "comp_lookup_dec.x", ], deps = [ - ":shift_buffer_dslx", ":common_dslx", + ":fse_proba_freq_dec_dslx", ":fse_table_creator_dslx", ":refilling_shift_buffer_dslx", - ":fse_proba_freq_dec_dslx", + ":shift_buffer_dslx", "//xls/examples:ram_dslx", "//xls/modules/zstd/memory:axi_dslx", "//xls/modules/zstd/memory:axi_ram_dslx", - ] + ], ) xls_dslx_test( @@ -1575,10 +1575,10 @@ xls_dslx_library( ], deps = [ ":common_dslx", - ":refilling_shift_buffer_dslx", ":fse_table_creator_dslx", + ":refilling_shift_buffer_dslx", "//xls/examples:ram_dslx", - ] + ], ) xls_dslx_test( @@ -1594,11 +1594,11 @@ xls_dslx_library( ], deps = [ ":comp_lookup_dec_dslx", - ":rle_lookup_dec_dslx", ":ram_mux_dslx", ":refilling_shift_buffer_dslx", ":refilling_shift_buffer_mux_dslx", - ] + ":rle_lookup_dec_dslx", + ], ) xls_dslx_test( @@ -1632,8 +1632,8 @@ xls_dslx_verilog( }, dslx_top = "FseTableIterator", library = ":fse_table_iterator_dslx", - verilog_file = "fse_table_iterator.v", tags = ["manual"], + verilog_file = "fse_table_iterator.v", ) xls_benchmark_ir( @@ -1657,11 +1657,11 @@ verilog_library( synthesize_rtl( name = "fse_table_iterator_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "FseTableIterator", deps = [ ":fse_table_iterator_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -1678,8 +1678,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":fse_table_iterator_synth_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -1710,8 +1710,8 @@ xls_dslx_verilog( }, dslx_top = "FseTableCreatorInst", library = ":fse_table_creator_dslx", - verilog_file = "fse_table_creator.v", tags = ["manual"], + verilog_file = "fse_table_creator.v", ) xls_benchmark_ir( @@ -1726,8 +1726,8 @@ xls_benchmark_ir( xls_benchmark_verilog( name = "fse_table_creator_verilog_benchmark", - verilog_target = "fse_table_creator_verilog", tags = ["manual"], + verilog_target = "fse_table_creator_verilog", ) verilog_library( @@ -1741,11 +1741,11 @@ verilog_library( synthesize_rtl( name = "fse_table_creator_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "FseTableCreator", deps = [ ":fse_table_creator_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -1762,8 +1762,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":fse_table_creator_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -1792,8 +1792,8 @@ xls_dslx_verilog( }, dslx_top = "CommandConstructor", library = ":command_constructor_dslx", - verilog_file = "command_constructor.v", tags = ["manual"], + verilog_file = "command_constructor.v", ) xls_benchmark_ir( @@ -1808,8 +1808,8 @@ xls_benchmark_ir( xls_benchmark_verilog( name = "command_constructor_verilog_benchmark", - verilog_target = "command_constructor_verilog", tags = ["manual"], + verilog_target = "command_constructor_verilog", ) verilog_library( @@ -1823,11 +1823,11 @@ verilog_library( synthesize_rtl( name = "command_constructor_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "CommandConstructor", deps = [ ":command_constructor_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -1844,8 +1844,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":command_constructor_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -1886,8 +1886,8 @@ xls_dslx_verilog( }, dslx_top = "RamDemuxInst", library = ":ram_demux_dslx", - verilog_file = "ram_demux.v", tags = ["manual"], + verilog_file = "ram_demux.v", ) xls_benchmark_ir( @@ -1913,11 +1913,11 @@ verilog_library( synthesize_rtl( name = "ram_demux_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "RamDemux", deps = [ ":ram_demux_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -1934,8 +1934,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":ram_demux_synth_asap7", - target_die_utilization_percentage = "5", tags = ["manual"], + target_die_utilization_percentage = "5", ) xls_dslx_verilog( @@ -1964,8 +1964,8 @@ xls_dslx_verilog( opt_ir_args = { "top": "__ram_demux__RamDemuxNaiveInst__RamDemuxNaive_0__5_8_0_8_next", }, - verilog_file = "ram_demux_naive.v", tags = ["manual"], + verilog_file = "ram_demux_naive.v", ) xls_benchmark_ir( @@ -1991,11 +1991,11 @@ verilog_library( synthesize_rtl( name = "ram_demux_naive_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "RamDemuxNaive", deps = [ ":ram_demux_naive_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -2012,8 +2012,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":ram_demux_naive_synth_asap7", - target_die_utilization_percentage = "5", tags = ["manual"], + target_die_utilization_percentage = "5", ) xls_dslx_library( @@ -2023,8 +2023,8 @@ xls_dslx_library( ], deps = [ ":common_dslx", - ":math_dslx", ":fse_table_creator_dslx", + ":math_dslx", ":refilling_shift_buffer_dslx", "//xls/examples:ram_dslx", ], @@ -2046,10 +2046,10 @@ xls_dslx_verilog( "use_system_verilog": "false", }, dslx_top = "FseDecoderInst", + library = ":fse_dec_dslx", opt_ir_args = { - "top": "__fse_dec__FseDecoderInst__FseDecoder_0__64_8_32_4_64_7_next" + "top": "__fse_dec__FseDecoderInst__FseDecoder_0__64_8_32_4_64_7_next", }, - library = ":fse_dec_dslx", tags = ["manual"], verilog_file = "fse_dec.v", ) @@ -2138,8 +2138,8 @@ xls_dslx_verilog( }, dslx_top = "RamDemux3Inst", library = ":ram_demux3_dslx", - verilog_file = "ram_demux3.v", tags = ["manual"], + verilog_file = "ram_demux3.v", ) xls_benchmark_ir( @@ -2193,7 +2193,7 @@ place_and_route( xls_dslx_library( name = "ram_mux_dslx", srcs = [ - "ram_mux.x" + "ram_mux.x", ], deps = [ "//xls/examples:ram_dslx", @@ -2209,21 +2209,21 @@ xls_dslx_test( xls_dslx_library( name = "sequence_dec_dslx", srcs = [ - "sequence_dec.x" + "sequence_dec.x", ], deps = [ - ":sequence_conf_dec_dslx", - ":comp_lookup_dec_dslx", - ":ram_demux3_dslx", ":common_dslx", + ":comp_lookup_dec_dslx", ":fse_dec_dslx", - ":ram_mux_dslx", - ":fse_table_creator_dslx", ":fse_lookup_dec_dslx", - ":shift_buffer_dslx", + ":fse_table_creator_dslx", + ":ram_demux3_dslx", + ":ram_mux_dslx", ":refilling_shift_buffer_dslx", - "//xls/modules/zstd/memory:mem_reader_dslx", + ":sequence_conf_dec_dslx", + ":shift_buffer_dslx", "//xls/modules/zstd/memory:axi_ram_dslx", + "//xls/modules/zstd/memory:mem_reader_dslx", ], ) @@ -2244,13 +2244,13 @@ xls_dslx_verilog( "use_system_verilog": "false", "multi_proc": "true", }, - opt_ir_args = { - "top": "__sequence_dec__FseLookupCtrlInst__FseLookupCtrl_0_next" - }, dslx_top = "FseLookupCtrlInst", library = ":sequence_dec_dslx", - verilog_file = "fse_lookup_ctrl.v", + opt_ir_args = { + "top": "__sequence_dec__FseLookupCtrlInst__FseLookupCtrl_0_next", + }, tags = ["manual"], + verilog_file = "fse_lookup_ctrl.v", ) xls_benchmark_ir( @@ -2291,8 +2291,8 @@ xls_dslx_verilog( }, dslx_top = "RleLiteralsDecoderInst", library = ":rle_literals_dec_dslx", - verilog_file = "rle_literals_dec.v", tags = ["manual"], + verilog_file = "rle_literals_dec.v", ) xls_benchmark_ir( @@ -2316,11 +2316,11 @@ verilog_library( synthesize_rtl( name = "rle_literals_dec_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "rle_literals_dec", deps = [ ":rle_literals_dec_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -2337,8 +2337,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":rle_literals_dec_synth_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -2365,13 +2365,13 @@ xls_dslx_verilog( "reset": "rst", "use_system_verilog": "false", }, - opt_ir_args = { - "top": "__raw_literals_dec__RawLiteralsDecoderInst__RawLiteralsDecoder_0__16_64_next" - }, dslx_top = "RawLiteralsDecoderInst", library = ":raw_literals_dec_dslx", - verilog_file = "raw_literals_dec.v", + opt_ir_args = { + "top": "__raw_literals_dec__RawLiteralsDecoderInst__RawLiteralsDecoder_0__16_64_next", + }, tags = ["manual"], + verilog_file = "raw_literals_dec.v", ) xls_benchmark_ir( @@ -2380,15 +2380,15 @@ xls_benchmark_ir( benchmark_ir_args = { "pipeline_stages": "10", "delay_model": "asap7", - "top": "__raw_literals_dec__RawLiteralsDecoderInst__RawLiteralsDecoder_0__16_64_next" + "top": "__raw_literals_dec__RawLiteralsDecoderInst__RawLiteralsDecoder_0__16_64_next", }, tags = ["manual"], ) xls_benchmark_verilog( name = "raw_literals_dec_verilog_benchmark", - verilog_target = "raw_literals_dec_verilog", tags = ["manual"], + verilog_target = "raw_literals_dec_verilog", ) verilog_library( @@ -2402,11 +2402,11 @@ verilog_library( synthesize_rtl( name = "raw_literals_dec_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "RawLiteralsDecoder", deps = [ ":raw_literals_dec_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -2423,8 +2423,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":raw_literals_dec_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -2434,8 +2434,8 @@ xls_dslx_library( ], deps = [ ":common_dslx", - ":ram_printer_dslx", ":parallel_rams_dslx", + ":ram_printer_dslx", "//xls/examples:ram_dslx", ], ) @@ -2470,8 +2470,8 @@ xls_dslx_verilog( }, dslx_top = "LiteralsBufferInst", library = ":literals_buffer_dslx", - verilog_file = "literals_buffer.v", tags = ["manual"], + verilog_file = "literals_buffer.v", ) xls_benchmark_ir( @@ -2486,8 +2486,8 @@ xls_benchmark_ir( xls_benchmark_verilog( name = "literals_buffer_verilog_benchmark", - verilog_target = "literals_buffer_verilog", tags = ["manual"], + verilog_target = "literals_buffer_verilog", ) verilog_library( @@ -2501,11 +2501,11 @@ verilog_library( synthesize_rtl( name = "literals_buffer_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "LiteralsBuffer", deps = [ ":literals_buffer_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -2522,8 +2522,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":literals_buffer_synth_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -2532,18 +2532,18 @@ xls_dslx_library( "literals_decoder.x", ], deps = [ - "//xls/examples:ram_dslx", - "//xls/modules/zstd/memory:axi_dslx", - "//xls/modules/zstd/memory:axi_ram_dslx", - "//xls/modules/zstd/memory:mem_reader_dslx", ":common_dslx", - ":literals_buffer_dslx", + ":huffman_literals_dec_dslx", ":literals_block_header_dec_dslx", + ":literals_buffer_dslx", ":parallel_rams_dslx", ":ram_printer_dslx", ":raw_literals_dec_dslx", ":rle_literals_dec_dslx", - ":huffman_literals_dec_dslx", + "//xls/examples:ram_dslx", + "//xls/modules/zstd/memory:axi_dslx", + "//xls/modules/zstd/memory:axi_ram_dslx", + "//xls/modules/zstd/memory:mem_reader_dslx", ], ) @@ -2563,16 +2563,16 @@ xls_dslx_verilog( codegen_args = literals_decoder_ctrl_codegen_args, dslx_top = "LiteralsDecoderCtrlInst", library = ":literals_decoder_dslx", - verilog_file = "literals_decoder_ctrl.v", tags = ["manual"], + verilog_file = "literals_decoder_ctrl.v", ) xls_benchmark_ir( name = "literals_decoder_ctrl_opt_ir_benchmark", src = ":literals_decoder_ctrl_verilog.opt.ir", benchmark_ir_args = literals_decoder_ctrl_codegen_args | { - "multi_proc": "true", - }, + "multi_proc": "true", + }, tags = ["manual"], ) @@ -2587,11 +2587,11 @@ verilog_library( synthesize_rtl( name = "literals_decoder_ctrl_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "LiteralsDecoderCtrl", deps = [ ":literals_decoder_ctrl_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -2608,8 +2608,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":literals_decoder_ctrl_synth_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_verilog( @@ -2635,8 +2635,8 @@ xls_dslx_verilog( }, dslx_top = "LiteralsDecoderInst", library = ":literals_decoder_dslx", - verilog_file = "literals_decoder.v", tags = ["manual"], + verilog_file = "literals_decoder.v", ) xls_benchmark_ir( @@ -2652,8 +2652,8 @@ xls_benchmark_ir( xls_benchmark_verilog( name = "literals_decoder_verilog_benchmark", - verilog_target = "literals_decoder_verilog", tags = ["manual"], + verilog_target = "literals_decoder_verilog", ) verilog_library( @@ -2667,11 +2667,11 @@ verilog_library( synthesize_rtl( name = "literals_decoder_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "LiteralsDecoder", deps = [ ":literals_decoder_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -2688,8 +2688,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":literals_decoder_synth_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -2706,10 +2706,10 @@ xls_dslx_library( "huffman_prescan.x", ], deps = [ - "//xls/examples:ram_dslx", - "//xls/dslx/stdlib:acm_random_dslx", ":common_dslx", ":huffman_common_dslx", + "//xls/dslx/stdlib:acm_random_dslx", + "//xls/examples:ram_dslx", ], ) @@ -2724,13 +2724,12 @@ prescan_codegen_args = common_codegen_args | { "pipeline_stages": "16", "clock_period_ps": "750", "worst_case_throughput": "1", - "ram_configurations": - "InternalRam:1R1W:huffman_prescan__internal_read_req_s" + - ":huffman_prescan__internal_read_rsp_r:" + - "huffman_prescan__internal_write_req_s:" + - "huffman_prescan__internal_write_rsp_r:5", - "io_constraints" : "huffman_prescan__read_req_s:send:" + - "huffman_prescan__read_rsp_r:recv:5:5", + "ram_configurations": "InternalRam:1R1W:huffman_prescan__internal_read_req_s" + + ":huffman_prescan__internal_read_rsp_r:" + + "huffman_prescan__internal_write_req_s:" + + "huffman_prescan__internal_write_rsp_r:5", + "io_constraints": "huffman_prescan__read_req_s:send:" + + "huffman_prescan__read_rsp_r:recv:5:5", } xls_dslx_verilog( @@ -2738,8 +2737,8 @@ xls_dslx_verilog( codegen_args = prescan_codegen_args, dslx_top = "WeightPreScan", library = ":huffman_prescan_dslx", - verilog_file = "huffman_prescan.v", tags = ["manual"], + verilog_file = "huffman_prescan.v", ) xls_benchmark_ir( @@ -2751,8 +2750,8 @@ xls_benchmark_ir( xls_benchmark_verilog( name = "huffman_prescan_verilog_benchmark", - verilog_target = "huffman_prescan_verilog", tags = ["manual"], + verilog_target = "huffman_prescan_verilog", ) verilog_library( @@ -2766,11 +2765,11 @@ verilog_library( synthesize_rtl( name = "huffman_prescan_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "HuffmanPrescan", deps = [ ":huffman_prescan_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -2786,8 +2785,8 @@ place_and_route( min_pin_distance = "0.5", placement_density = "0.30", synthesized_rtl = ":huffman_prescan_synth_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -2796,10 +2795,10 @@ xls_dslx_library( "huffman_code_builder.x", ], deps = [ - "//xls/examples:ram_dslx", - "//xls/dslx/stdlib:acm_random_dslx", ":common_dslx", ":huffman_common_dslx", + "//xls/dslx/stdlib:acm_random_dslx", + "//xls/examples:ram_dslx", ], ) @@ -2814,8 +2813,8 @@ huffman_code_builder_codegen_args = common_codegen_args | { "pipeline_stages": "8", "clock_period_ps": "750", "worst_case_throughput": "1", - "io_constraints" : "huffman_code_builder__weight_r:recv:" + - "huffman_code_builder__codes_s:send:2:2", + "io_constraints": "huffman_code_builder__weight_r:recv:" + + "huffman_code_builder__codes_s:send:2:2", } xls_dslx_verilog( @@ -2823,8 +2822,8 @@ xls_dslx_verilog( codegen_args = huffman_code_builder_codegen_args, dslx_top = "WeightCodeBuilder", library = ":huffman_code_builder_dslx", - verilog_file = "huffman_code_builder.v", tags = ["manual"], + verilog_file = "huffman_code_builder.v", ) xls_benchmark_ir( @@ -2836,8 +2835,8 @@ xls_benchmark_ir( xls_benchmark_verilog( name = "huffman_code_builder_verilog_benchmark", - verilog_target = "huffman_code_builder_verilog", tags = ["manual"], + verilog_target = "huffman_code_builder_verilog", ) verilog_library( @@ -2851,11 +2850,11 @@ verilog_library( synthesize_rtl( name = "huffman_code_builder_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "HuffmanCodeBuilder", deps = [ ":huffman_code_builder_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -2871,8 +2870,8 @@ place_and_route( min_pin_distance = "0.5", placement_density = "0.30", synthesized_rtl = ":huffman_code_builder_synth_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -3193,23 +3192,23 @@ xls_dslx_library( "huffman_weights_dec.x", ], deps = [ - ":math_dslx", - ":huffman_prescan_dslx", - ":refilling_shift_buffer_dslx", ":comp_lookup_dec_dslx", ":fse_table_creator_dslx", + ":huffman_prescan_dslx", + ":math_dslx", ":ram_mux_dslx", - "//xls/modules/zstd/memory:mem_reader_dslx", - "//xls/modules/zstd/memory:axi_ram_dslx", + ":refilling_shift_buffer_dslx", "//xls/examples:ram_dslx", + "//xls/modules/zstd/memory:axi_ram_dslx", + "//xls/modules/zstd/memory:mem_reader_dslx", ], ) xls_dslx_test( name = "huffman_weights_dec_dslx_test", + size = "large", library = ":huffman_weights_dec_dslx", tags = ["manual"], - size = "large", ) huffman_weights_dec_codegen_args = common_codegen_args | { @@ -3217,7 +3216,7 @@ huffman_weights_dec_codegen_args = common_codegen_args | { "pipeline_stages": "25", "clock_period_ps": "750", "worst_case_throughput": "17", - "multi_proc": "true" + "multi_proc": "true", } xls_dslx_verilog( @@ -3281,7 +3280,6 @@ xls_dslx_library( "huffman_literals_dec.x", ], deps = [ - "//xls/modules/zstd/memory:axi_ram_dslx", ":common_dslx", ":huffman_axi_reader_dslx", ":huffman_code_builder_dslx", @@ -3290,6 +3288,7 @@ xls_dslx_library( ":huffman_data_preprocessor_dslx", ":huffman_decoder_dslx", ":huffman_prescan_dslx", + "//xls/modules/zstd/memory:axi_ram_dslx", ], ) @@ -3308,12 +3307,12 @@ huffman_literals_dec_codegen_args = common_codegen_args | { } xls_dslx_verilog( - name = "huffman_literals_dec_verilog", - codegen_args = huffman_literals_dec_codegen_args, - dslx_top = "HuffmanLiteralsDecoderInst", - library = ":huffman_literals_dec_dslx", - tags = ["manual"], - verilog_file = "huffman_literals_dec.v", + name = "huffman_literals_dec_verilog", + codegen_args = huffman_literals_dec_codegen_args, + dslx_top = "HuffmanLiteralsDecoderInst", + library = ":huffman_literals_dec_dslx", + tags = ["manual"], + verilog_file = "huffman_literals_dec.v", ) xls_benchmark_ir( @@ -3362,11 +3361,11 @@ place_and_route( xls_dslx_library( name = "refilling_shift_buffer_mux_dslx", srcs = [ - "refilling_shift_buffer_mux.x" + "refilling_shift_buffer_mux.x", ], deps = [ - ":refilling_shift_buffer_dslx" - ] + ":refilling_shift_buffer_dslx", + ], ) xls_dslx_test( From 5ab3fa9a96dfd8684ae9dc8a35c8f77ca1036767 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Mon, 7 Apr 2025 16:51:15 +0200 Subject: [PATCH 70/85] xls/modules/zstd/memory/BUILD: Format with buildifer Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/memory/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/xls/modules/zstd/memory/BUILD b/xls/modules/zstd/memory/BUILD index f5553c9eee..acfd13f700 100644 --- a/xls/modules/zstd/memory/BUILD +++ b/xls/modules/zstd/memory/BUILD @@ -341,6 +341,7 @@ xls_dslx_test( # FIXME: Improve the proc to achieve CLOCK_PERIOD_PS AXI_RAM_READER_CLOCK_PERIOD_PS = "850" + axi_ram_reader_codegen_args = common_codegen_args | { "clock_period_ps": AXI_RAM_READER_CLOCK_PERIOD_PS, "ram_configurations": "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( From ebcae1ef73c6c693f80e69c6f8a97ea1ffb763e6 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 8 Apr 2025 12:32:50 +0200 Subject: [PATCH 71/85] modules/zstd/memory: Add missing module names Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/memory/BUILD | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/xls/modules/zstd/memory/BUILD b/xls/modules/zstd/memory/BUILD index acfd13f700..39e2f52040 100644 --- a/xls/modules/zstd/memory/BUILD +++ b/xls/modules/zstd/memory/BUILD @@ -93,6 +93,7 @@ AXI_READER_CLOCK_PERIOD_PS = "1700" axi_reader_codegen_args = common_codegen_args | { "clock_period_ps": AXI_READER_CLOCK_PERIOD_PS, + "module_name": "axi_reader", } xls_dslx_verilog( @@ -163,7 +164,7 @@ xls_dslx_test( xls_dslx_verilog( name = "axi_stream_remove_empty_verilog", - codegen_args = common_codegen_args, + codegen_args = common_codegen_args | {"module_name": "axi_stream_remove_empty"}, dslx_top = "AxiStreamRemoveEmptyInst", library = ":axi_stream_remove_empty_dslx", tags = ["manual"], @@ -208,7 +209,7 @@ place_and_route( xls_dslx_verilog( name = "remove_empty_bytes_verilog", - codegen_args = common_codegen_args, + codegen_args = common_codegen_args | {"module_name": "remove_empty_bytes"}, dslx_top = "RemoveEmptyBytesInst", library = ":axi_stream_remove_empty_dslx", tags = ["manual"], @@ -274,7 +275,7 @@ xls_dslx_test( xls_dslx_verilog( name = "axi_stream_downscaler_verilog", - codegen_args = common_codegen_args, + codegen_args = common_codegen_args | {"module_name": "axi_stream_downscaler"}, dslx_top = "AxiStreamDownscalerInst", library = ":axi_stream_downscaler_dslx", tags = ["manual"], @@ -429,6 +430,7 @@ MEM_READER_CLOCK_PERIOD_PS = "2600" mem_reader_codegen_args = common_codegen_args | { "clock_period_ps": MEM_READER_CLOCK_PERIOD_PS, + "module_name": "mem_reader", } xls_dslx_verilog( @@ -485,7 +487,7 @@ place_and_route( xls_dslx_verilog( name = "mem_reader_adv_verilog", - codegen_args = mem_reader_codegen_args, + codegen_args = mem_reader_codegen_args | {"module_name": "mem_reader_adv"}, dslx_top = "MemReaderAdvInst", library = ":mem_reader_dslx", tags = ["manual"], @@ -553,7 +555,7 @@ xls_dslx_test( xls_dslx_verilog( name = "axi_writer_verilog", - codegen_args = common_codegen_args, + codegen_args = common_codegen_args | {"module_name": "axi_writer"}, dslx_top = "AxiWriterInst", library = ":axi_writer_dslx", tags = ["manual"], @@ -697,7 +699,7 @@ xls_dslx_test( xls_dslx_verilog( name = "mem_writer_verilog", - codegen_args = common_codegen_args, + codegen_args = common_codegen_args | {"module_name": "mem_writer"}, dslx_top = "MemWriterInst", library = ":mem_writer_dslx", tags = ["manual"], From 5a4323dcaf5d4dd8ffa193eb7fa2e08aab8a7bcc Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 8 Apr 2025 12:32:13 +0200 Subject: [PATCH 72/85] modules/zstd: Rename axi_ram->axi_ram_reader Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 14 +++--- xls/modules/zstd/comp_block_dec.x | 4 +- xls/modules/zstd/comp_lookup_dec.x | 4 +- xls/modules/zstd/fse_lookup_dec.x | 4 +- xls/modules/zstd/huffman_literals_dec.x | 14 +++--- xls/modules/zstd/huffman_weights_dec.x | 4 +- xls/modules/zstd/literals_decoder.x | 18 ++++---- xls/modules/zstd/memory/BUILD | 45 ++++++++++--------- .../memory/{axi_ram.x => axi_ram_reader.x} | 0 xls/modules/zstd/sequence_dec.x | 8 ++-- xls/modules/zstd/zstd_dec_test.x | 10 ++--- 11 files changed, 63 insertions(+), 62 deletions(-) rename xls/modules/zstd/memory/{axi_ram.x => axi_ram_reader.x} (100%) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index e2c9011ea6..f364c56bb4 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1423,7 +1423,7 @@ xls_dslx_library( ":parallel_rams_dslx", ":sequence_dec_dslx", "//xls/examples:ram_dslx", - "//xls/modules/zstd/memory:axi_ram_dslx", + "//xls/modules/zstd/memory:axi_ram_reader_dslx", "//xls/modules/zstd/memory:mem_reader_dslx", ], ) @@ -1460,7 +1460,7 @@ zstd_dec_deps = [ "//xls/examples:ram_dslx", "//xls/modules/zstd/memory:mem_reader_dslx", "//xls/modules/zstd/memory:mem_writer_dslx", - "//xls/modules/zstd/memory:axi_ram_dslx", + "//xls/modules/zstd/memory:axi_ram_reader_dslx", ] xls_dslx_library( @@ -1558,7 +1558,7 @@ xls_dslx_library( ":shift_buffer_dslx", "//xls/examples:ram_dslx", "//xls/modules/zstd/memory:axi_dslx", - "//xls/modules/zstd/memory:axi_ram_dslx", + "//xls/modules/zstd/memory:axi_ram_reader_dslx", ], ) @@ -2222,7 +2222,7 @@ xls_dslx_library( ":refilling_shift_buffer_dslx", ":sequence_conf_dec_dslx", ":shift_buffer_dslx", - "//xls/modules/zstd/memory:axi_ram_dslx", + "//xls/modules/zstd/memory:axi_ram_reader_dslx", "//xls/modules/zstd/memory:mem_reader_dslx", ], ) @@ -2542,7 +2542,7 @@ xls_dslx_library( ":rle_literals_dec_dslx", "//xls/examples:ram_dslx", "//xls/modules/zstd/memory:axi_dslx", - "//xls/modules/zstd/memory:axi_ram_dslx", + "//xls/modules/zstd/memory:axi_ram_reader_dslx", "//xls/modules/zstd/memory:mem_reader_dslx", ], ) @@ -3199,7 +3199,7 @@ xls_dslx_library( ":ram_mux_dslx", ":refilling_shift_buffer_dslx", "//xls/examples:ram_dslx", - "//xls/modules/zstd/memory:axi_ram_dslx", + "//xls/modules/zstd/memory:axi_ram_reader_dslx", "//xls/modules/zstd/memory:mem_reader_dslx", ], ) @@ -3288,7 +3288,7 @@ xls_dslx_library( ":huffman_data_preprocessor_dslx", ":huffman_decoder_dslx", ":huffman_prescan_dslx", - "//xls/modules/zstd/memory:axi_ram_dslx", + "//xls/modules/zstd/memory:axi_ram_reader_dslx", ], ) diff --git a/xls/modules/zstd/comp_block_dec.x b/xls/modules/zstd/comp_block_dec.x index 3c8df70fe7..a94658ba12 100644 --- a/xls/modules/zstd/comp_block_dec.x +++ b/xls/modules/zstd/comp_block_dec.x @@ -14,7 +14,7 @@ import std; import xls.examples.ram; -import xls.modules.zstd.memory.axi_ram; +import xls.modules.zstd.memory.axi_ram_reader; import xls.modules.zstd.common; import xls.modules.zstd.huffman_literals_dec; import xls.modules.zstd.parallel_rams; @@ -1326,7 +1326,7 @@ proc CompressBlockDecoderTest { >( axi_ram_rd_req_r[i], axi_ram_rd_resp_s[i], axi_ram_wr_req_r[i], axi_ram_wr_resp_s[i] ); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, TEST_CASE_RAM_SIZE, TEST_CASE_RAM_BASE_ADDR, TEST_CASE_RAM_DATA_W, TEST_CASE_RAM_ADDR_W >( diff --git a/xls/modules/zstd/comp_lookup_dec.x b/xls/modules/zstd/comp_lookup_dec.x index 5b32b1fc4e..b6414ea7c4 100644 --- a/xls/modules/zstd/comp_lookup_dec.x +++ b/xls/modules/zstd/comp_lookup_dec.x @@ -18,7 +18,7 @@ import xls.examples.ram; import xls.modules.zstd.common; import xls.modules.zstd.memory.axi; import xls.modules.zstd.memory.mem_reader; -import xls.modules.zstd.memory.axi_ram; +import xls.modules.zstd.memory.axi_ram_reader; import xls.modules.zstd.fse_table_creator; import xls.modules.zstd.refilling_shift_buffer; import xls.modules.zstd.fse_proba_freq_dec; @@ -1756,7 +1756,7 @@ proc CompLookupDecoderTest { let (testcase_axi_r_s, testcase_axi_r_r) = chan("testcase_axi_r"); let (testcase_axi_ar_s, testcase_axi_ar_r) = chan("testcase_axi_ar"); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_ADDR_WIDTH, TEST_AXI_DATA_WIDTH, TEST_AXI_DEST_WIDTH, TEST_AXI_ID_WIDTH, TEST_CASE_RAM_SIZE, TEST_CASE_RAM_BASE_ADDR, TEST_CASE_RAM_DATA_WIDTH, TEST_CASE_RAM_ADDR_WIDTH, TEST_CASE_RAM_NUM_PARTITIONS, diff --git a/xls/modules/zstd/fse_lookup_dec.x b/xls/modules/zstd/fse_lookup_dec.x index 6c586c0713..47eeaaab9a 100644 --- a/xls/modules/zstd/fse_lookup_dec.x +++ b/xls/modules/zstd/fse_lookup_dec.x @@ -18,7 +18,7 @@ import xls.examples.ram; import xls.modules.zstd.common; import xls.modules.zstd.memory.axi; import xls.modules.zstd.memory.mem_reader; -import xls.modules.zstd.memory.axi_ram; +import xls.modules.zstd.memory.axi_ram_reader; import xls.modules.zstd.fse_table_creator; import xls.modules.zstd.refilling_shift_buffer; import xls.modules.zstd.fse_proba_freq_dec; @@ -544,7 +544,7 @@ proc FseLookupDecoderTest { let (testcase_axi_r_s, testcase_axi_r_r) = chan("testcase_axi_r"); let (testcase_axi_ar_s, testcase_axi_ar_r) = chan("testcase_axi_ar"); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_ADDR_WIDTH, TEST_AXI_DATA_WIDTH, TEST_AXI_DEST_WIDTH, TEST_AXI_ID_WIDTH, TEST_CASE_RAM_SIZE, TEST_CASE_RAM_BASE_ADDR, TEST_CASE_RAM_DATA_WIDTH, TEST_CASE_RAM_ADDR_WIDTH, TEST_CASE_RAM_NUM_PARTITIONS, diff --git a/xls/modules/zstd/huffman_literals_dec.x b/xls/modules/zstd/huffman_literals_dec.x index 171b05f994..81819e9e75 100644 --- a/xls/modules/zstd/huffman_literals_dec.x +++ b/xls/modules/zstd/huffman_literals_dec.x @@ -25,7 +25,7 @@ import xls.modules.zstd.huffman_prescan as prescan; import xls.modules.zstd.huffman_ctrl as ctrl; import xls.modules.zstd.huffman_weights_dec as weights_dec; import xls.modules.zstd.memory.axi as axi; -import xls.modules.zstd.memory.axi_ram; +import xls.modules.zstd.memory.axi_ram_reader; import xls.modules.zstd.memory.mem_reader as mem_reader; import xls.examples.ram; @@ -907,7 +907,7 @@ proc HuffmanLiteralsDecoder_test { ram_rd_req_huffman_r, ram_rd_resp_huffman_s, ram_wr_req_huffman_r, ram_wr_resp_huffman_s ); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH > ( @@ -933,7 +933,7 @@ proc HuffmanLiteralsDecoder_test { ram_rd_req_jump_table_r, ram_rd_resp_jump_table_s, ram_wr_req_jump_table_r, ram_wr_resp_jump_table_s ); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH > ( @@ -959,7 +959,7 @@ proc HuffmanLiteralsDecoder_test { ram_rd_req_huffman_weights_header_r, ram_rd_resp_huffman_weights_header_s, ram_wr_req_huffman_weights_header_r, ram_wr_resp_huffman_weights_header_s ); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH > ( @@ -985,7 +985,7 @@ proc HuffmanLiteralsDecoder_test { ram_rd_req_huffman_weights_raw_r, ram_rd_resp_huffman_weights_raw_s, ram_wr_req_huffman_weights_raw_r, ram_wr_resp_huffman_weights_raw_s ); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH > ( @@ -1011,7 +1011,7 @@ proc HuffmanLiteralsDecoder_test { ram_rd_req_huffman_weights_fse_lookup_r, ram_rd_resp_huffman_weights_fse_lookup_s, ram_wr_req_huffman_weights_fse_lookup_r, ram_wr_resp_huffman_weights_fse_lookup_s ); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH > ( @@ -1036,7 +1036,7 @@ proc HuffmanLiteralsDecoder_test { ram_rd_req_huffman_weights_fse_decoder_r, ram_rd_resp_huffman_weights_fse_decoder_s, ram_wr_req_huffman_weights_fse_decoder_r, ram_wr_resp_huffman_weights_fse_decoder_s ); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH > ( diff --git a/xls/modules/zstd/huffman_weights_dec.x b/xls/modules/zstd/huffman_weights_dec.x index 287cf7ba17..3abea16e08 100644 --- a/xls/modules/zstd/huffman_weights_dec.x +++ b/xls/modules/zstd/huffman_weights_dec.x @@ -18,7 +18,7 @@ import xls.examples.ram; import xls.modules.zstd.common ; import xls.modules.zstd.huffman_prescan; import xls.modules.zstd.memory.axi; -import xls.modules.zstd.memory.axi_ram; +import xls.modules.zstd.memory.axi_ram_reader; import xls.modules.zstd.memory.mem_reader; import xls.modules.zstd.ram_mux; import xls.modules.zstd.refilling_shift_buffer; @@ -1990,7 +1990,7 @@ proc HuffmanWeightsDecoder_test { let (axi_r_s, axi_r_r) = chan[TEST_RAM_N]("axi_r"); unroll_for! (i, _) in range(u32:0, TEST_RAM_N) { - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, TEST_RAM_SIZE, diff --git a/xls/modules/zstd/literals_decoder.x b/xls/modules/zstd/literals_decoder.x index f242d9bd68..110757a7ce 100644 --- a/xls/modules/zstd/literals_decoder.x +++ b/xls/modules/zstd/literals_decoder.x @@ -21,7 +21,7 @@ import xls.modules.zstd.common as common; import xls.modules.zstd.literals_block_header_dec as literals_block_header_dec; import xls.modules.zstd.literals_buffer as literals_buffer; import xls.modules.zstd.memory.axi as axi; -import xls.modules.zstd.memory.axi_ram; +import xls.modules.zstd.memory.axi_ram_reader; import xls.modules.zstd.memory.mem_reader as mem_reader; import xls.modules.zstd.parallel_rams as parallel_rams; import xls.modules.zstd.ram_printer as ram_printer; @@ -1573,7 +1573,7 @@ proc LiteralsDecoder_test { ram_rd_req_header_r, ram_rd_resp_header_s, ram_wr_req_header_r, ram_wr_resp_header_s ); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH > ( @@ -1599,7 +1599,7 @@ proc LiteralsDecoder_test { ram_rd_req_raw_r, ram_rd_resp_raw_s, ram_wr_req_raw_r, ram_wr_resp_raw_s ); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH > ( @@ -1625,7 +1625,7 @@ proc LiteralsDecoder_test { ram_rd_req_huffman_r, ram_rd_resp_huffman_s, ram_wr_req_huffman_r, ram_wr_resp_huffman_s ); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH > ( @@ -1651,7 +1651,7 @@ proc LiteralsDecoder_test { ram_rd_req_huffman_jump_table_r, ram_rd_resp_huffman_jump_table_s, ram_wr_req_huffman_jump_table_r, ram_wr_resp_huffman_jump_table_s ); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH > ( @@ -1677,7 +1677,7 @@ proc LiteralsDecoder_test { ram_rd_req_huffman_weights_header_r, ram_rd_resp_huffman_weights_header_s, ram_wr_req_huffman_weights_header_r, ram_wr_resp_huffman_weights_header_s ); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH > ( @@ -1703,7 +1703,7 @@ proc LiteralsDecoder_test { ram_rd_req_huffman_weights_raw_r, ram_rd_resp_huffman_weights_raw_s, ram_wr_req_huffman_weights_raw_r, ram_wr_resp_huffman_weights_raw_s ); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH > ( @@ -1730,7 +1730,7 @@ proc LiteralsDecoder_test { ram_wr_req_huffman_weights_fse_lookup_dec_r, ram_wr_resp_huffman_weights_fse_lookup_dec_s ); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH > ( @@ -1757,7 +1757,7 @@ proc LiteralsDecoder_test { ram_wr_req_huffman_weights_fse_decoder_dec_r, ram_wr_resp_huffman_weights_fse_decoder_dec_s ); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_RAM_ADDR_W, TEST_AXI_RAM_DATA_W, TEST_AXI_RAM_DEST_W, TEST_AXI_RAM_ID_W, TEST_AXI_RAM_MODEL_SIZE, TEST_AXI_RAM_MODEL_BASE_ADDR, TEST_AXI_RAM_MODEL_DATA_WIDTH, TEST_AXI_RAM_MODEL_ADDR_WIDTH > ( diff --git a/xls/modules/zstd/memory/BUILD b/xls/modules/zstd/memory/BUILD index 39e2f52040..6830997678 100644 --- a/xls/modules/zstd/memory/BUILD +++ b/xls/modules/zstd/memory/BUILD @@ -326,8 +326,8 @@ place_and_route( ) xls_dslx_library( - name = "axi_ram_dslx", - srcs = ["axi_ram.x"], + name = "axi_ram_reader_dslx", + srcs = ["axi_ram_reader.x"], deps = [ ":axi_dslx", "//xls/examples:ram_dslx", @@ -336,73 +336,74 @@ xls_dslx_library( ) xls_dslx_test( - name = "axi_ram_dslx_test", - library = ":axi_ram_dslx", + name = "axi_ram_reader_dslx_test", + library = ":axi_ram_reader_dslx", ) # FIXME: Improve the proc to achieve CLOCK_PERIOD_PS AXI_RAM_READER_CLOCK_PERIOD_PS = "850" axi_ram_reader_codegen_args = common_codegen_args | { + "module_name": "axi_ram_reader", "clock_period_ps": AXI_RAM_READER_CLOCK_PERIOD_PS, "ram_configurations": "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( latency = 5, ram_name = "ram", - rd_req = "axi_ram__rd_req_s", - rd_resp = "axi_ram__rd_resp_r", - wr_req = "axi_ram__wr_req_s", - wr_resp = "axi_ram__wr_resp_r", + rd_req = "axi_ram_reader__rd_req_s", + rd_resp = "axi_ram_reader__rd_resp_r", + wr_req = "axi_ram_reader__wr_req_s", + wr_resp = "axi_ram_reader__wr_resp_r", ), } xls_dslx_verilog( - name = "axi_ram_verilog", + name = "axi_ram_reader_verilog", codegen_args = axi_ram_reader_codegen_args, dslx_top = "AxiRamReaderInstWithEmptyWrites", - library = ":axi_ram_dslx", + library = ":axi_ram_reader_dslx", tags = ["manual"], - verilog_file = "axi_ram.v", + verilog_file = "axi_ram_reader.v", ) verilog_library( - name = "axi_ram_verilog_lib", + name = "axi_ram_reader_verilog_lib", srcs = [ - ":axi_ram.v", + ":axi_ram_reader.v", ], tags = ["manual"], ) xls_benchmark_ir( - name = "axi_ram_opt_ir_benchmark", - src = ":axi_ram_verilog.opt.ir", + name = "axi_ram_reader_opt_ir_benchmark", + src = ":axi_ram_reader_verilog.opt.ir", benchmark_ir_args = axi_ram_reader_codegen_args, tags = ["manual"], ) synthesize_rtl( - name = "axi_ram_synth_asap7", + name = "axi_ram_reader_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", tags = ["manual"], - top_module = "AxiRam", + top_module = "axi_ram_reader", deps = [ - ":axi_ram_verilog_lib", + ":axi_ram_reader_verilog_lib", ], ) benchmark_synth( - name = "axi_ram_benchmark_synth", - synth_target = ":axi_ram_synth_asap7", + name = "axi_ram_reader_benchmark_synth", + synth_target = ":axi_ram_reader_synth_asap7", tags = ["manual"], ) place_and_route( - name = "axi_ram_place_and_route", + name = "axi_ram_reader_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 = ":axi_ram_synth_asap7", + synthesized_rtl = ":axi_ram_reader_synth_asap7", tags = ["manual"], target_die_utilization_percentage = "10", ) diff --git a/xls/modules/zstd/memory/axi_ram.x b/xls/modules/zstd/memory/axi_ram_reader.x similarity index 100% rename from xls/modules/zstd/memory/axi_ram.x rename to xls/modules/zstd/memory/axi_ram_reader.x diff --git a/xls/modules/zstd/sequence_dec.x b/xls/modules/zstd/sequence_dec.x index e84dd54f57..cbe89c439a 100644 --- a/xls/modules/zstd/sequence_dec.x +++ b/xls/modules/zstd/sequence_dec.x @@ -17,7 +17,7 @@ import std; import xls.examples.ram; import xls.modules.zstd.common; import xls.modules.zstd.memory.axi; -import xls.modules.zstd.memory.axi_ram; +import xls.modules.zstd.memory.axi_ram_reader; import xls.modules.zstd.memory.mem_reader; import xls.modules.zstd.sequence_conf_dec; import xls.modules.zstd.fse_lookup_dec; @@ -2256,7 +2256,7 @@ proc SequenceDecoderTest { let (ss_axi_ar_s, ss_axi_ar_r) = chan("ss_axi_ar"); let (ss_axi_r_s, ss_axi_r_r) = chan("ss_axi_r"); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, TEST_INPUT_RAM_SIZE >( @@ -2283,7 +2283,7 @@ proc SequenceDecoderTest { let (fl_axi_ar_s, fl_axi_ar_r) = chan("fl_axi_ar"); let (fl_axi_r_s, fl_axi_r_r) = chan("fl_axi_r"); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, TEST_INPUT_RAM_SIZE >( @@ -2311,7 +2311,7 @@ proc SequenceDecoderTest { let (fd_axi_ar_s, fd_axi_ar_r) = chan("fd_axi_ar"); let (fd_axi_r_s, fd_axi_r_r) = chan("fd_axi_r"); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, TEST_INPUT_RAM_SIZE >( diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x index c56032cc0b..423518c268 100644 --- a/xls/modules/zstd/zstd_dec_test.x +++ b/xls/modules/zstd/zstd_dec_test.x @@ -18,7 +18,7 @@ import xls.modules.zstd.common; import xls.modules.zstd.memory.axi; import xls.modules.zstd.csr_config; import xls.modules.zstd.sequence_executor; -import xls.modules.zstd.memory.axi_ram; +import xls.modules.zstd.memory.axi_ram_reader; import xls.modules.zstd.zstd_dec; import xls.modules.zstd.comp_block_dec; import xls.modules.zstd.sequence_dec; @@ -451,7 +451,7 @@ proc ZstdDecoderTest { TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED >(comp_ram_rd_req_r[i], comp_ram_rd_resp_s[i], comp_ram_wr_req_r[i], comp_ram_wr_resp_s[i]); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, TEST_RAM_SIZE, TEST_RAM_BASE_ADDR, TEST_RAM_DATA_W, TEST_RAM_ADDR_W >(comp_axi_ar_r[i], comp_axi_r_s[i], comp_ram_rd_req_s[i], comp_ram_rd_resp_r[i]); @@ -679,17 +679,17 @@ proc ZstdDecoderTest { TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, > (raw_ram_rd_req_r, raw_ram_rd_resp_s, raw_ram_wr_req_r, raw_ram_wr_resp_s); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, TEST_RAM_SIZE, TEST_RAM_BASE_ADDR, TEST_RAM_DATA_W, TEST_RAM_ADDR_W, >(fh_axi_ar_r, fh_axi_r_s, fh_ram_rd_req_s, fh_ram_rd_resp_r); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, TEST_RAM_SIZE, TEST_RAM_BASE_ADDR, TEST_RAM_DATA_W, TEST_RAM_ADDR_W, >(bh_axi_ar_r, bh_axi_r_s, bh_ram_rd_req_s, bh_ram_rd_resp_r); - spawn axi_ram::AxiRamReader< + spawn axi_ram_reader::AxiRamReader< TEST_AXI_ADDR_W, TEST_AXI_DATA_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, TEST_RAM_SIZE, TEST_RAM_BASE_ADDR, TEST_RAM_DATA_W, TEST_RAM_ADDR_W, >(raw_axi_ar_r, raw_axi_r_s, raw_ram_rd_req_s, raw_ram_rd_resp_r); From 4fc5ba11d9560c3e1e4422fc523bef703d9b8f93 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Mon, 12 May 2025 20:09:32 +0200 Subject: [PATCH 73/85] modules/zstd: Remove RESET CSR Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/README.md | 14 ++------ xls/modules/zstd/img/ZSTD_decoder_wrapper.png | Bin 123219 -> 113685 bytes xls/modules/zstd/zstd_dec.x | 32 +++++------------- xls/modules/zstd/zstd_dec_test.x | 20 ++--------- 4 files changed, 13 insertions(+), 53 deletions(-) diff --git a/xls/modules/zstd/README.md b/xls/modules/zstd/README.md index 0842d65c49..b60d630408 100644 --- a/xls/modules/zstd/README.md +++ b/xls/modules/zstd/README.md @@ -45,9 +45,8 @@ The registers are defined below: | ---- | ------- | ----------- | | Status | 0x0 | Keeps the code describing the current state of the ZSTD Decoder | | Start | 0x8 | Writing `1` when the decoder is in the `IDLE` state starts the decoding process | -| Reset | 0x10 | Writing `1` will reset the decoder to the `IDLE` state | -| Input Buffer | 0x18 | Keeps the base address for the input buffer that is used for storing the frame to decode | -| Output Buffer | 0x20 | Keeps the base address for the output buffer, ZSTD Decoder will write the decoded frame into memory starting from this address. | +| Input Buffer | 0x10 | Keeps the base address for the input buffer that is used for storing the frame to decode | +| Output Buffer | 0x18 | Keeps the base address for the output buffer, ZSTD Decoder will write the decoded frame into memory starting from this address. | ### Status codes @@ -68,15 +67,6 @@ The following is a list of all available status codes that can be written in the | RAW_BLOCK_ERROR | 10 | Failure in communication with the memory | | RLE_BLOCK_OK | 11 | Successfully decoded RLE data block | -### Reset handling - -The expected behavior of the `Reset` CSR cannot be achieved solely in the DSLX code. -As of [cb2829ab](https://github.com/google/xls/commit/cb2829ab809c58f21d957a47e400456a8c8f8db1), the XLS toolchain does not support resetting the proc network on the DSLX level. -As a workaround for this issue, the `ZstdDec` proc defines a `reset` output channel that sends a pulse when there is a write to the `Reset` CSR. -The Verilog code that integrates the decoder in a target system must connect this output back to the standard `rst` input of the decoder. -If any external reset signal exists and is intended to be used with the decoder, it should be OR-ed with the `reset` channel output before connecting to the decoder's `rst` input. -Please refer to the diagram of the Verilog wrapper in the [Testing Methodology](#testing-methodology) chapter for example reset connection. - ## Controlling the decoder from the software The configuration done by the software must be carried out when the decoder is in the `IDLE` state. diff --git a/xls/modules/zstd/img/ZSTD_decoder_wrapper.png b/xls/modules/zstd/img/ZSTD_decoder_wrapper.png index 293420234fbe5d0d33195a3e10a6fc4bf331271d..039f010ec1959a0a0b2048ef433acf495486498f 100644 GIT binary patch literal 113685 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ፋQ%g0N!J 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 literal 123219 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 U3>(c: Csr) -> uN[LOG2_REGS_N] { @@ -189,7 +188,6 @@ proc ZstdDecoderInternal< output_mem_wr_resp_r: chan in; notify_s: chan<()> out; - reset_s: chan<()> out; init { zero!() @@ -227,7 +225,6 @@ proc ZstdDecoderInternal< output_mem_wr_resp_r: chan in, notify_s: chan<()> out, - reset_s: chan<()> out, ) { ( csr_rd_req_s, csr_rd_resp_r, csr_wr_req_s, csr_wr_resp_r, csr_change_r, @@ -237,7 +234,7 @@ proc ZstdDecoderInternal< rle_req_s, rle_resp_r, comp_block_req_s, comp_block_resp_r, output_mem_wr_req_s, output_mem_wr_resp_r, - notify_s, reset_s, + notify_s, ) } @@ -254,12 +251,6 @@ proc ZstdDecoderInternal< let (tok1_0, csr_change, csr_change_valid) = recv_non_blocking(tok0, csr_change_r, zero!()); let is_start = (csr_change_valid && (csr_change.csr == csr(Csr::START))); - let is_reset = (csr_change_valid && (csr_change.csr == csr(Csr::RESET))); - let tok = send_if(tok0, reset_s, is_reset, ()); - if is_reset { - trace_fmt!("[[RESET]]"); - } else {}; - if csr_change_valid { trace_fmt!("[CSR CHANGE] {:#x}", csr_change); } else {}; @@ -721,7 +712,6 @@ proc ZstdDecoderInternalTest { output_mem_wr_resp_s: chan out; notify_r: chan<()> in; - reset_r: chan<()> in; init {} @@ -751,7 +741,6 @@ proc ZstdDecoderInternalTest { let (output_mem_wr_resp_s, output_mem_wr_resp_r) = chan("output_mem_wr_resp"); let (notify_s, notify_r) = chan<()>("notify"); - let (reset_s, reset_r) = chan<()>("reset"); spawn ZstdDecoderInternal( csr_rd_req_s, csr_rd_resp_r, csr_wr_req_s, csr_wr_resp_r, csr_change_r, @@ -761,7 +750,7 @@ proc ZstdDecoderInternalTest { rle_req_s, rle_resp_r, comp_block_req_s, comp_block_resp_r, output_mem_wr_req_s, output_mem_wr_resp_r, - notify_s, reset_s, + notify_s, ); ( @@ -773,7 +762,7 @@ proc ZstdDecoderInternalTest { rle_req_r, rle_resp_s, comp_block_req_r, comp_block_resp_s, output_mem_wr_req_r, output_mem_wr_resp_s, - notify_r, reset_r, + notify_r, ) } @@ -1228,7 +1217,6 @@ pub proc ZstdDecoder< ram_wr_resp_7_r: chan in, notify_s: chan<()> out, - reset_s: chan<()> out, ) { const CHANNEL_DEPTH = u32:1; @@ -1458,7 +1446,7 @@ pub proc ZstdDecoder< rle_req_s, rle_resp_r, comp_block_req_s, comp_block_resp_r, output_mem_wr_req_s, output_mem_wr_resp_r, - notify_s, reset_s, + notify_s, ); () @@ -1623,7 +1611,6 @@ proc ZstdDecoderInternalInst { // IRQ notify_s: chan<()> out, - reset_s: chan<()> out, ) { spawn ZstdDecoderInternal< INST_AXI_DATA_W, INST_AXI_ADDR_W, INST_REGS_N, @@ -1635,7 +1622,7 @@ proc ZstdDecoderInternalInst { rle_req_s, rle_resp_r, comp_req_s, comp_resp_r, output_mem_wr_req_s, output_mem_wr_resp_r, - notify_s, reset_s, + notify_s, ); } @@ -1834,7 +1821,6 @@ proc ZstdDecoderInst { ram_wr_resp_7_r: chan in, notify_s: chan<()> out, - reset_s: chan<()> out, ) { // FIXME: Remove inline Huffman Weights memory once HuffmanLiteralsDecoder's memory ports are able to be rewritten let (huffman_lit_weights_mem_rd_req_s, huffman_lit_weights_mem_rd_req_r) = chan("huffman_lit_weights_mem_rd_req"); @@ -1906,7 +1892,7 @@ proc ZstdDecoderInst { 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, - notify_s, reset_s, + notify_s, ); } diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x index 423518c268..151d3f15e3 100644 --- a/xls/modules/zstd/zstd_dec_test.x +++ b/xls/modules/zstd/zstd_dec_test.x @@ -311,7 +311,6 @@ proc ZstdDecoderTest { of_def_test_wr_resp_r: chan in; notify_r: chan<()> in; - reset_r: chan<()> in; init {} @@ -354,7 +353,6 @@ proc ZstdDecoderTest { let (hb_ram_wr_resp_s, hb_ram_wr_resp_r) = chan[8]("hb_ram_wr_resp"); let (notify_s, notify_r) = chan<()>("notify"); - let (reset_s, reset_r) = chan<()>("reset"); // Huffman weights memory let (huffman_lit_weights_rd_req_s, huffman_lit_weights_rd_req_r) = chan("huffman_lit_weights_rd_req"); @@ -652,7 +650,7 @@ proc ZstdDecoderTest { hb_ram_wr_resp_r[0], hb_ram_wr_resp_r[1], hb_ram_wr_resp_r[2], hb_ram_wr_resp_r[3], hb_ram_wr_resp_r[4], hb_ram_wr_resp_r[5], hb_ram_wr_resp_r[6], hb_ram_wr_resp_r[7], - notify_s, reset_s, + notify_s, ); unroll_for! (i, ()): (u32, ()) in range(u32:0, u32:8) { @@ -706,7 +704,7 @@ proc ZstdDecoderTest { ll_sel_test_s, ll_def_test_rd_req_s, ll_def_test_rd_resp_r, ll_def_test_wr_req_s, ll_def_test_wr_resp_r, ml_sel_test_s, ml_def_test_rd_req_s, ml_def_test_rd_resp_r, ml_def_test_wr_req_s, ml_def_test_wr_resp_r, of_sel_test_s, of_def_test_rd_req_s, of_def_test_rd_resp_r, of_def_test_wr_req_s, of_def_test_wr_resp_r, - notify_r, reset_r, + notify_r, ) } @@ -792,20 +790,6 @@ proc ZstdDecoderTest { last: u1:1, }; - // reset the decoder - trace_fmt!("Sending reset"); - let tok = send(tok, csr_axi_aw_s, axi::AxiAw { - addr: csr_addr(zstd_dec::Csr::RESET), - ..addr_req - }); - let tok = send(tok, csr_axi_w_s, axi::AxiW { - data: uN[TEST_AXI_DATA_W]:0x1, - ..data_req - }); - trace_fmt!("Sent reset"); - let (tok, _) = recv(tok, csr_axi_b_r); - // Wait for reset notification before issuing further CSR writes - let (tok, _) = recv(tok, reset_r); // configure input buffer address let tok = send(tok, csr_axi_aw_s, axi::AxiAw { addr: csr_addr(zstd_dec::Csr::INPUT_BUFFER), From e4ab1f731ab1c7a86757f84a8bc7c1b9b647be1d Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 8 Apr 2025 10:06:32 +0200 Subject: [PATCH 74/85] modules/zstd: fix bazel targets * Use materialize_internal_fifos when possible * Disable the above option for procs with loopback channels * Add missing module names * Add xls_fifo_wrapper verilog dependency to the synthesis of the procs without materialized internal fifos Signed-off-by: Pawel Czarnecki Co-authored-by: Wojciech Sipak --- xls/modules/zstd/BUILD | 54 +- xls/modules/zstd/rtl/BUILD | 47 ++ xls/modules/zstd/rtl/xls_fifo_wrapper.v | 67 +++ xls/modules/zstd/rtl/zstd_dec_wrapper.v | 743 ++++++++++++++++++++++++ 4 files changed, 896 insertions(+), 15 deletions(-) create mode 100644 xls/modules/zstd/rtl/BUILD create mode 100644 xls/modules/zstd/rtl/xls_fifo_wrapper.v create mode 100644 xls/modules/zstd/rtl/zstd_dec_wrapper.v diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index f364c56bb4..d80a9bb0af 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -42,7 +42,6 @@ common_codegen_args = { "clock_period_ps": CLOCK_PERIOD_PS, "clock_margin_percent": "20", "multi_proc": "true", - "fifo_module": "", "materialize_internal_fifos": "true", } @@ -161,7 +160,7 @@ xls_dslx_test( xls_dslx_verilog( name = "shift_buffer_aligner_verilog", - codegen_args = common_codegen_args, + codegen_args = common_codegen_args | {"module_name": "ShiftBufferAligner"}, dslx_top = "ShiftBufferAlignerInst", library = ":shift_buffer_dslx", tags = ["manual"], @@ -220,6 +219,7 @@ xls_benchmark_verilog( shift_buffer_storage_codegen_args = common_codegen_args | { "clock_period_ps": "0", "pipeline_stages": "2", + "module_name": "ShiftBufferStorage", } xls_dslx_verilog( @@ -285,6 +285,7 @@ SHIFT_BUFFER_CLOCK_PERIOD_PS = "1000" shift_buffer_codegen_args = common_codegen_args | { "clock_period_ps": SHIFT_BUFFER_CLOCK_PERIOD_PS, + "module_name": "ShiftBuffer", } xls_dslx_verilog( @@ -423,7 +424,7 @@ verilog_library( name = "frame_header_dec_verilog_lib", srcs = [ ":frame_header_dec.v", - ":xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -519,7 +520,7 @@ verilog_library( name = "block_header_dec_verilog_lib", srcs = [ ":block_header_dec.v", - ":xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -598,7 +599,7 @@ verilog_library( name = "raw_block_dec_verilog_lib", srcs = [ ":raw_block_dec.v", - ":xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -676,7 +677,7 @@ verilog_library( name = "rle_block_dec_verilog_lib", srcs = [ ":rle_block_dec.v", - ":xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -754,7 +755,7 @@ verilog_library( name = "dec_mux_verilog_lib", srcs = [ ":dec_mux.v", - ":xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -894,7 +895,7 @@ verilog_library( name = "sequence_executor_lib", srcs = [ ":sequence_executor.v", - ":xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -974,7 +975,7 @@ verilog_library( name = "axi_csr_accessor_verilog_lib", srcs = [ ":axi_csr_accessor.v", - ":xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -1051,7 +1052,7 @@ verilog_library( name = "csr_config_verilog_lib", srcs = [ ":csr_config.v", - ":xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -1106,6 +1107,7 @@ xls_dslx_verilog( "pipeline_stages": "1", "reset": "rst", "use_system_verilog": "false", + "materialize_internal_fifos": "true", }, dslx_top = "RamWrRespHandlerInst", library = ":ram_wr_handler_dslx", @@ -1130,7 +1132,7 @@ verilog_library( name = "ram_rw_handler_verilog_lib", srcs = [ ":ram_rw_handler.v", - ":xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -1198,6 +1200,7 @@ xls_dslx_verilog( "pipeline_stages": "6", "reset": "rst", "use_system_verilog": "false", + "materialize_internal_fifos": "true", }, dslx_top = "FseProbaFreqDecoderInst", library = ":fse_proba_freq_dec_dslx", @@ -1234,7 +1237,7 @@ verilog_library( name = "fse_proba_freq_dec_lib", srcs = [ ":fse_proba_freq_dec.v", - ":xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -1284,7 +1287,9 @@ xls_dslx_test( xls_dslx_verilog( name = "literals_block_header_dec_verilog", - codegen_args = window_buffer_codegen_args, + codegen_args = common_codegen_args | { + "module_name": "LiteralsBlockHeaderDecoder", + }, dslx_top = "LiteralsHeaderDecoderInst", library = ":literals_block_header_dec_dslx", tags = ["manual"], @@ -1319,7 +1324,9 @@ xls_dslx_test( xls_dslx_verilog( name = "sequence_conf_dec_verilog", - codegen_args = window_buffer_codegen_args, + codegen_args = common_codegen_args | { + "module_name": "SequenceConfDecoder", + }, dslx_top = "SequenceConfDecoderInst", library = ":sequence_conf_dec_dslx", tags = ["manual"], @@ -1376,7 +1383,7 @@ verilog_library( name = "refilling_shift_buffer_internal_verilog_lib", srcs = [ ":refilling_shift_buffer_internal.v", - ":xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", ], tags = ["manual"], ) @@ -1629,6 +1636,7 @@ xls_dslx_verilog( "pipeline_stages": "1", "reset": "rst", "use_system_verilog": "false", + "materialize_internal_fifos": "true", }, dslx_top = "FseTableIterator", library = ":fse_table_iterator_dslx", @@ -1707,6 +1715,7 @@ xls_dslx_verilog( "pipeline_stages": "4", "reset": "rst", "use_system_verilog": "false", + "materialize_internal_fifos": "true", }, dslx_top = "FseTableCreatorInst", library = ":fse_table_creator_dslx", @@ -1789,6 +1798,7 @@ xls_dslx_verilog( "pipeline_stages": "2", "reset": "rst", "use_system_verilog": "false", + "materialize_internal_fifos": "true", }, dslx_top = "CommandConstructor", library = ":command_constructor_dslx", @@ -1883,6 +1893,7 @@ xls_dslx_verilog( "pipeline_stages": "6", "reset": "rst", "use_system_verilog": "false", + "materialize_internal_fifos": "true", }, dslx_top = "RamDemuxInst", library = ":ram_demux_dslx", @@ -1958,6 +1969,7 @@ xls_dslx_verilog( "pipeline_stages": "6", "reset": "rst", "use_system_verilog": "false", + "materialize_internal_fifos": "true", }, dslx_top = "RamDemuxNaiveInst", library = ":ram_demux_dslx", @@ -2044,6 +2056,7 @@ xls_dslx_verilog( "pipeline_stages": "8", "reset": "rst", "use_system_verilog": "false", + "materialize_internal_fifos": "true", }, dslx_top = "FseDecoderInst", library = ":fse_dec_dslx", @@ -2135,6 +2148,7 @@ xls_dslx_verilog( "reset": "rst", "use_system_verilog": "false", "multi_proc": "true", + "materialize_internal_fifos": "true", }, dslx_top = "RamDemux3Inst", library = ":ram_demux3_dslx", @@ -2243,6 +2257,7 @@ xls_dslx_verilog( "reset": "rst", "use_system_verilog": "false", "multi_proc": "true", + "materialize_internal_fifos": "true", }, dslx_top = "FseLookupCtrlInst", library = ":sequence_dec_dslx", @@ -2288,6 +2303,7 @@ xls_dslx_verilog( "reset": "rst", "worst_case_throughput": "1", "use_system_verilog": "false", + "materialize_internal_fifos": "true", }, dslx_top = "RleLiteralsDecoderInst", library = ":rle_literals_dec_dslx", @@ -2364,6 +2380,7 @@ xls_dslx_verilog( "pipeline_stages": "1", "reset": "rst", "use_system_verilog": "false", + "materialize_internal_fifos": "true", }, dslx_top = "RawLiteralsDecoderInst", library = ":raw_literals_dec_dslx", @@ -2467,6 +2484,7 @@ xls_dslx_verilog( "worst_case_throughput": "1", "use_system_verilog": "false", "multi_proc": "true", + "materialize_internal_fifos": "true", }, dslx_top = "LiteralsBufferInst", library = ":literals_buffer_dslx", @@ -2632,6 +2650,7 @@ xls_dslx_verilog( "reset": "rst", "worst_case_throughput": "2", "use_system_verilog": "false", + "materialize_internal_fifos": "false", # TODO: set to true once this option works with loopback channels }, dslx_top = "LiteralsDecoderInst", library = ":literals_decoder_dslx", @@ -2671,6 +2690,7 @@ synthesize_rtl( top_module = "LiteralsDecoder", deps = [ ":literals_decoder_verilog_lib", + "//xls/modules/zstd/rtl:xls_fifo_wrapper_verilog_lib", ], ) @@ -2730,6 +2750,7 @@ prescan_codegen_args = common_codegen_args | { "huffman_prescan__internal_write_rsp_r:5", "io_constraints": "huffman_prescan__read_req_s:send:" + "huffman_prescan__read_rsp_r:recv:5:5", + "materialize_internal_fifos": "false", # TODO: remove once this option works with loopback channels } xls_dslx_verilog( @@ -2769,6 +2790,7 @@ synthesize_rtl( top_module = "HuffmanPrescan", deps = [ ":huffman_prescan_verilog_lib", + "//xls/modules/zstd/rtl:xls_fifo_wrapper_verilog_lib", ], ) @@ -3304,6 +3326,7 @@ huffman_literals_dec_codegen_args = common_codegen_args | { "clock_period_ps": "0", "worst_case_throughput": "0", "minimize_worst_case_throughput": "true", + "materialize_internal_fifos": "false", # TODO: remove once this option works with loopback channels } xls_dslx_verilog( @@ -3337,6 +3360,7 @@ synthesize_rtl( top_module = "HuffmanLiteralsDecoder", deps = [ ":huffman_literals_dec_verilog_lib", + "//xls/modules/zstd/rtl:xls_fifo_wrapper_verilog_lib", ], ) diff --git a/xls/modules/zstd/rtl/BUILD b/xls/modules/zstd/rtl/BUILD new file mode 100644 index 0000000000..9ca9c83ee3 --- /dev/null +++ b/xls/modules/zstd/rtl/BUILD @@ -0,0 +1,47 @@ +# 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. + +"""Collection of simulation sources + +This package exports verilog sources required by the ZSTD Decoder in the verilog +tests. + +The sources contain: + * Wrapper for the ZSTD Decoder used in the verilog tests + * Verilog implementation of the channel fifo + +""" + +load("@rules_hdl//verilog:providers.bzl", "verilog_library") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//xls:xls_users"], + licenses = ["notice"], +) + +exports_files( + [ + "zstd_dec_wrapper.v", + "xls_fifo_wrapper.v", + ], +) + +verilog_library( + name = "xls_fifo_wrapper_verilog_lib", + srcs = [ + ":xls_fifo_wrapper.v", + ], + tags = ["manual"], +) diff --git a/xls/modules/zstd/rtl/xls_fifo_wrapper.v b/xls/modules/zstd/rtl/xls_fifo_wrapper.v new file mode 100644 index 0000000000..ccb60535af --- /dev/null +++ b/xls/modules/zstd/rtl/xls_fifo_wrapper.v @@ -0,0 +1,67 @@ +// Copyright 2025 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. + +// simple fifo implementation +module xls_fifo_wrapper ( +clk, rst, +push_ready, push_data, push_valid, +pop_ready, pop_data, pop_valid); + parameter int Width = 32, + Depth = 32, + EnableBypass = 0, + RegisterPushOutputs = 1, + RegisterPopOutputs = 1; + localparam int AddrWidth = $clog2(Depth) + 1; + input wire clk; + input wire rst; + output wire push_ready; + input wire [Width-1:0] push_data; + input wire push_valid; + input wire pop_ready; + output wire [Width-1:0] pop_data; + output wire pop_valid; + + // Require depth be 1 and bypass disabled. + //initial begin + // if (EnableBypass || Depth != 1 || !RegisterPushOutputs || RegisterPopOutputs) begin + // // FIFO configuration not supported. + // $fatal(1); + // end + //end + + + reg [Width-1:0] mem; + reg full; + + assign push_ready = !full; + assign pop_valid = full; + assign pop_data = mem; + + always @(posedge clk) begin + if (rst == 1'b1) begin + full <= 1'b0; + end else begin + if (push_valid && push_ready) begin + mem <= push_data; + full <= 1'b1; + end else if (pop_valid && pop_ready) begin + mem <= mem; + full <= 1'b0; + end else begin + mem <= mem; + full <= full; + end + end + end +endmodule diff --git a/xls/modules/zstd/rtl/zstd_dec_wrapper.v b/xls/modules/zstd/rtl/zstd_dec_wrapper.v new file mode 100644 index 0000000000..8a616f3f23 --- /dev/null +++ b/xls/modules/zstd/rtl/zstd_dec_wrapper.v @@ -0,0 +1,743 @@ +// 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 module wraps the ZSTD Decoder verilog sources generated from DSLX to +// form a DUT with consistent IO. +// The wrapper also contains an AXI crossbar module for merging the traffic +// from multiple AXI Managers comming from the ZSTD Decoder into a single AXI +// master that could be e.g. routed to the memory. + +`default_nettype none + +module zstd_dec_wrapper #( + parameter int AXI_DATA_W = 64, + parameter int AXI_ADDR_W = 16, + parameter int S_AXI_ID_W = 4, + parameter int M_AXI_ID_W = 6, + parameter int AXI_STRB_W = 8, + parameter int AWUSER_WIDTH = 1, + parameter int WUSER_WIDTH = 1, + parameter int BUSER_WIDTH = 1, + parameter int ARUSER_WIDTH = 1, + parameter int RUSER_WIDTH = 1 +) ( + input wire clk, + input wire rst, + + // AXI Manager interface for the memory connection + output wire [M_AXI_ID_W-1:0] memory_axi_aw_awid, + output wire [AXI_ADDR_W-1:0] memory_axi_aw_awaddr, + output wire [7:0] memory_axi_aw_awlen, + output wire [2:0] memory_axi_aw_awsize, + output wire [1:0] memory_axi_aw_awburst, + output wire memory_axi_aw_awlock, + output wire [3:0] memory_axi_aw_awcache, + output wire [2:0] memory_axi_aw_awprot, + output wire [3:0] memory_axi_aw_awqos, + output wire [3:0] memory_axi_aw_awregion, + output wire [AWUSER_WIDTH-1:0] memory_axi_aw_awuser, + output wire memory_axi_aw_awvalid, + input wire memory_axi_aw_awready, + output wire [AXI_DATA_W-1:0] memory_axi_w_wdata, + output wire [AXI_STRB_W-1:0] memory_axi_w_wstrb, + output wire memory_axi_w_wlast, + output wire [WUSER_WIDTH-1:0] memory_axi_w_wuser, + output wire memory_axi_w_wvalid, + input wire memory_axi_w_wready, + input wire [M_AXI_ID_W-1:0] memory_axi_b_bid, + input wire [2:0] memory_axi_b_bresp, + input wire [BUSER_WIDTH-1:0] memory_axi_b_buser, + input wire memory_axi_b_bvalid, + output wire memory_axi_b_bready, + output wire [M_AXI_ID_W-1:0] memory_axi_ar_arid, + output wire [AXI_ADDR_W-1:0] memory_axi_ar_araddr, + output wire [7:0] memory_axi_ar_arlen, + output wire [2:0] memory_axi_ar_arsize, + output wire [1:0] memory_axi_ar_arburst, + output wire memory_axi_ar_arlock, + output wire [3:0] memory_axi_ar_arcache, + output wire [2:0] memory_axi_ar_arprot, + output wire [3:0] memory_axi_ar_arqos, + output wire [3:0] memory_axi_ar_arregion, + output wire [ARUSER_WIDTH-1:0] memory_axi_ar_aruser, + output wire memory_axi_ar_arvalid, + input wire memory_axi_ar_arready, + input wire [M_AXI_ID_W-1:0] memory_axi_r_rid, + input wire [AXI_DATA_W-1:0] memory_axi_r_rdata, + input wire [2:0] memory_axi_r_rresp, + input wire memory_axi_r_rlast, + input wire [RUSER_WIDTH-1:0] memory_axi_r_ruser, + input wire memory_axi_r_rvalid, + output wire memory_axi_r_rready, + + // AXI Subordinate interface for the CSR access + input wire [S_AXI_ID_W-1:0] csr_axi_aw_awid, + input wire [AXI_ADDR_W-1:0] csr_axi_aw_awaddr, + input wire [7:0] csr_axi_aw_awlen, + input wire [2:0] csr_axi_aw_awsize, + input wire [1:0] csr_axi_aw_awburst, + input wire csr_axi_aw_awlock, + input wire [3:0] csr_axi_aw_awcache, + input wire [2:0] csr_axi_aw_awprot, + input wire [3:0] csr_axi_aw_awqos, + input wire [3:0] csr_axi_aw_awregion, + input wire [AWUSER_WIDTH-1:0] csr_axi_aw_awuser, + input wire csr_axi_aw_awvalid, + output wire csr_axi_aw_awready, + input wire [AXI_DATA_W-1:0] csr_axi_w_wdata, + input wire [AXI_STRB_W-1:0] csr_axi_w_wstrb, + input wire csr_axi_w_wlast, + input wire [WUSER_WIDTH-1:0] csr_axi_w_wuser, + input wire csr_axi_w_wvalid, + output wire csr_axi_w_wready, + output wire [S_AXI_ID_W-1:0] csr_axi_b_bid, + output wire [2:0] csr_axi_b_bresp, + output wire [BUSER_WIDTH-1:0] csr_axi_b_buser, + output wire csr_axi_b_bvalid, + input wire csr_axi_b_bready, + input wire [S_AXI_ID_W-1:0] csr_axi_ar_arid, + input wire [AXI_ADDR_W-1:0] csr_axi_ar_araddr, + input wire [7:0] csr_axi_ar_arlen, + input wire [2:0] csr_axi_ar_arsize, + input wire [1:0] csr_axi_ar_arburst, + input wire csr_axi_ar_arlock, + input wire [3:0] csr_axi_ar_arcache, + input wire [2:0] csr_axi_ar_arprot, + input wire [3:0] csr_axi_ar_arqos, + input wire [3:0] csr_axi_ar_arregion, + input wire [ARUSER_WIDTH-1:0] csr_axi_ar_aruser, + input wire csr_axi_ar_arvalid, + output wire csr_axi_ar_arready, + output wire [S_AXI_ID_W-1:0] csr_axi_r_rid, + output wire [AXI_DATA_W-1:0] csr_axi_r_rdata, + output wire [2:0] csr_axi_r_rresp, + output wire csr_axi_r_rlast, + output wire [RUSER_WIDTH-1:0] csr_axi_r_ruser, + output wire csr_axi_r_rvalid, + input wire csr_axi_r_rready, + + output wire notify_data, + output wire notify_vld, + input wire notify_rdy +); + + /* + * MemReader AXI interfaces + */ + // RawBlockDecoder + wire raw_block_decoder_axi_ar_arvalid; + wire raw_block_decoder_axi_ar_arready; + wire [S_AXI_ID_W-1:0] raw_block_decoder_axi_ar_arid; + wire [AXI_ADDR_W-1:0] raw_block_decoder_axi_ar_araddr; + wire [ 3:0] raw_block_decoder_axi_ar_arregion; + wire [ 7:0] raw_block_decoder_axi_ar_arlen; + wire [ 2:0] raw_block_decoder_axi_ar_arsize; + wire [ 1:0] raw_block_decoder_axi_ar_arburst; + wire [ 3:0] raw_block_decoder_axi_ar_arcache; + wire [ 2:0] raw_block_decoder_axi_ar_arprot; + wire [ 3:0] raw_block_decoder_axi_ar_arqos; + + wire raw_block_decoder_axi_r_rvalid; + wire raw_block_decoder_axi_r_rready; + wire [S_AXI_ID_W-1:0] raw_block_decoder_axi_r_rid; + wire [AXI_DATA_W-1:0] raw_block_decoder_axi_r_rdata; + wire [ 2:0] raw_block_decoder_axi_r_rresp; + wire raw_block_decoder_axi_r_rlast; + + + // BlockHeaderDecoder + wire block_header_decoder_axi_ar_arvalid; + wire block_header_decoder_axi_ar_arready; + wire [S_AXI_ID_W-1:0] block_header_decoder_axi_ar_arid; + wire [AXI_ADDR_W-1:0] block_header_decoder_axi_ar_araddr; + wire [ 3:0] block_header_decoder_axi_ar_arregion; + wire [ 7:0] block_header_decoder_axi_ar_arlen; + wire [ 2:0] block_header_decoder_axi_ar_arsize; + wire [ 1:0] block_header_decoder_axi_ar_arburst; + wire [ 3:0] block_header_decoder_axi_ar_arcache; + wire [ 2:0] block_header_decoder_axi_ar_arprot; + wire [ 3:0] block_header_decoder_axi_ar_arqos; + + wire block_header_decoder_axi_r_rvalid; + wire block_header_decoder_axi_r_rready; + wire [S_AXI_ID_W-1:0] block_header_decoder_axi_r_rid; + wire [AXI_DATA_W-1:0] block_header_decoder_axi_r_rdata; + wire [ 2:0] block_header_decoder_axi_r_rresp; + wire block_header_decoder_axi_r_rlast; + + + // FrameHeaderDecoder + wire frame_header_decoder_axi_ar_arvalid; + wire frame_header_decoder_axi_ar_arready; + wire [S_AXI_ID_W-1:0] frame_header_decoder_axi_ar_arid; + wire [AXI_ADDR_W-1:0] frame_header_decoder_axi_ar_araddr; + wire [ 3:0] frame_header_decoder_axi_ar_arregion; + wire [ 7:0] frame_header_decoder_axi_ar_arlen; + wire [ 2:0] frame_header_decoder_axi_ar_arsize; + wire [ 1:0] frame_header_decoder_axi_ar_arburst; + wire [ 3:0] frame_header_decoder_axi_ar_arcache; + wire [ 2:0] frame_header_decoder_axi_ar_arprot; + wire [ 3:0] frame_header_decoder_axi_ar_arqos; + + wire frame_header_decoder_axi_r_rvalid; + wire frame_header_decoder_axi_r_rready; + wire [S_AXI_ID_W-1:0] frame_header_decoder_axi_r_rid; + wire [AXI_DATA_W-1:0] frame_header_decoder_axi_r_rdata; + wire [ 2:0] frame_header_decoder_axi_r_rresp; + wire frame_header_decoder_axi_r_rlast; + + + /* + * MemWriter AXI interfaces + */ + + // Output Writer + wire [S_AXI_ID_W-1:0] output_axi_aw_awid; + wire [AXI_ADDR_W-1:0] output_axi_aw_awaddr; + wire [ 2:0] output_axi_aw_awsize; + wire [ 7:0] output_axi_aw_awlen; + wire [ 1:0] output_axi_aw_awburst; + wire output_axi_aw_awvalid; + wire output_axi_aw_awready; + + wire [AXI_DATA_W-1:0] output_axi_w_wdata; + wire [AXI_STRB_W-1:0] output_axi_w_wstrb; + wire output_axi_w_wlast; + wire output_axi_w_wvalid; + wire output_axi_w_wready; + + wire [S_AXI_ID_W-1:0] output_axi_b_bid; + wire [ 2:0] output_axi_b_bresp; + wire output_axi_b_bvalid; + wire output_axi_b_bready; + + /* + * XLS Channels representing AXI interfaces + */ + + localparam int XlsAxiAwW = AXI_ADDR_W + S_AXI_ID_W + 3 + 2 + 8; + localparam int XlsAxiWW = AXI_DATA_W + AXI_STRB_W + 1; + localparam int XlsAxiBW = 3 + S_AXI_ID_W; + localparam int XlsAxiArW = S_AXI_ID_W + AXI_ADDR_W + 4 + 8 + 3 + 2 + 4 + 3 + 4; + localparam int XlsAxiRW = S_AXI_ID_W + AXI_DATA_W + 3 + 1; + // CSR + wire [XlsAxiAwW-1:0] zstd_dec__csr_axi_aw; + wire zstd_dec__csr_axi_aw_rdy; + wire zstd_dec__csr_axi_aw_vld; + wire [XlsAxiWW-1:0] zstd_dec__csr_axi_w; + wire zstd_dec__csr_axi_w_rdy; + wire zstd_dec__csr_axi_w_vld; + wire [ XlsAxiBW-1:0] zstd_dec__csr_axi_b; + wire zstd_dec__csr_axi_b_rdy; + wire zstd_dec__csr_axi_b_vld; + wire [XlsAxiArW-1:0] zstd_dec__csr_axi_ar; + wire zstd_dec__csr_axi_ar_rdy; + wire zstd_dec__csr_axi_ar_vld; + wire [ XlsAxiRW-1:0] zstd_dec__csr_axi_r; + wire zstd_dec__csr_axi_r_rdy; + wire zstd_dec__csr_axi_r_vld; + + // Frame Header Decoder + wire [XlsAxiArW-1:0] zstd_dec__fh_axi_ar; + wire zstd_dec__fh_axi_ar_rdy; + wire zstd_dec__fh_axi_ar_vld; + wire [ XlsAxiRW-1:0] zstd_dec__fh_axi_r; + wire zstd_dec__fh_axi_r_rdy; + wire zstd_dec__fh_axi_r_vld; + + // Block Header Decoder + wire [XlsAxiArW-1:0] zstd_dec__bh_axi_ar; + wire zstd_dec__bh_axi_ar_rdy; + wire zstd_dec__bh_axi_ar_vld; + wire [ XlsAxiRW-1:0] zstd_dec__bh_axi_r; + wire zstd_dec__bh_axi_r_rdy; + wire zstd_dec__bh_axi_r_vld; + + // Raw Block Decoder + wire [XlsAxiArW-1:0] zstd_dec__raw_axi_ar; + wire zstd_dec__raw_axi_ar_rdy; + wire zstd_dec__raw_axi_ar_vld; + wire [ XlsAxiRW-1:0] zstd_dec__raw_axi_r; + wire zstd_dec__raw_axi_r_rdy; + wire zstd_dec__raw_axi_r_vld; + + // Output Memory Interface + wire [XlsAxiAwW-1:0] zstd_dec__output_axi_aw; + wire zstd_dec__output_axi_aw_rdy; + wire zstd_dec__output_axi_aw_vld; + wire [XlsAxiWW-1:0] zstd_dec__output_axi_w; + wire zstd_dec__output_axi_w_rdy; + wire zstd_dec__output_axi_w_vld; + wire [XlsAxiBW-1:0] zstd_dec__output_axi_b; + wire zstd_dec__output_axi_b_rdy; + wire zstd_dec__output_axi_b_vld; + + /* + * Mapping XLS Channels to AXI channels fields + */ + + // CSR + assign zstd_dec__csr_axi_aw = { + csr_axi_aw_awid, + csr_axi_aw_awaddr, + csr_axi_aw_awsize, + csr_axi_aw_awlen, + csr_axi_aw_awburst + }; + assign zstd_dec__csr_axi_aw_vld = csr_axi_aw_awvalid; + assign csr_axi_aw_awready = zstd_dec__csr_axi_aw_rdy; + assign zstd_dec__csr_axi_w = { + csr_axi_w_wdata, + csr_axi_w_wstrb, + csr_axi_w_wlast + }; + assign zstd_dec__csr_axi_w_vld = csr_axi_w_wvalid; + assign csr_axi_w_wready = zstd_dec__csr_axi_w_rdy; + assign { + csr_axi_b_bresp, + csr_axi_b_bid + } = zstd_dec__csr_axi_b; + assign csr_axi_b_bvalid = zstd_dec__csr_axi_b_vld; + assign zstd_dec__csr_axi_b_rdy = csr_axi_b_bready; + assign zstd_dec__csr_axi_ar = { + csr_axi_ar_arid, + csr_axi_ar_araddr, + csr_axi_ar_arregion, + csr_axi_ar_arlen, + csr_axi_ar_arsize, + csr_axi_ar_arburst, + csr_axi_ar_arcache, + csr_axi_ar_arprot, + csr_axi_ar_arqos + }; + assign zstd_dec__csr_axi_ar_vld = csr_axi_ar_arvalid; + assign csr_axi_ar_arready = zstd_dec__csr_axi_ar_rdy; + assign { + csr_axi_r_rid, + csr_axi_r_rdata, + csr_axi_r_rresp, + csr_axi_r_rlast + } = zstd_dec__csr_axi_r; + assign csr_axi_r_rvalid = zstd_dec__csr_axi_r_vld; + assign zstd_dec__csr_axi_r_rdy = csr_axi_r_rready; + + // Frame Header Decoder + assign { + frame_header_decoder_axi_ar_arid, + frame_header_decoder_axi_ar_araddr, + frame_header_decoder_axi_ar_arregion, + frame_header_decoder_axi_ar_arlen, + frame_header_decoder_axi_ar_arsize, + frame_header_decoder_axi_ar_arburst, + frame_header_decoder_axi_ar_arcache, + frame_header_decoder_axi_ar_arprot, + frame_header_decoder_axi_ar_arqos + } = zstd_dec__fh_axi_ar; + assign frame_header_decoder_axi_ar_arvalid = zstd_dec__fh_axi_ar_vld; + assign zstd_dec__fh_axi_ar_rdy = frame_header_decoder_axi_ar_arready; + assign zstd_dec__fh_axi_r = { + frame_header_decoder_axi_r_rid, + frame_header_decoder_axi_r_rdata, + frame_header_decoder_axi_r_rresp, + frame_header_decoder_axi_r_rlast}; + assign zstd_dec__fh_axi_r_vld = frame_header_decoder_axi_r_rvalid; + assign frame_header_decoder_axi_r_rready = zstd_dec__fh_axi_r_rdy; + + // Block Header Decoder + assign { + block_header_decoder_axi_ar_arid, + block_header_decoder_axi_ar_araddr, + block_header_decoder_axi_ar_arregion, + block_header_decoder_axi_ar_arlen, + block_header_decoder_axi_ar_arsize, + block_header_decoder_axi_ar_arburst, + block_header_decoder_axi_ar_arcache, + block_header_decoder_axi_ar_arprot, + block_header_decoder_axi_ar_arqos + } = zstd_dec__bh_axi_ar; + assign block_header_decoder_axi_ar_arvalid = zstd_dec__bh_axi_ar_vld; + assign zstd_dec__bh_axi_ar_rdy = block_header_decoder_axi_ar_arready; + assign zstd_dec__bh_axi_r = { + block_header_decoder_axi_r_rid, + block_header_decoder_axi_r_rdata, + block_header_decoder_axi_r_rresp, + block_header_decoder_axi_r_rlast}; + assign zstd_dec__bh_axi_r_vld = block_header_decoder_axi_r_rvalid; + assign block_header_decoder_axi_r_rready = zstd_dec__bh_axi_r_rdy; + + // Raw Block Decoder + assign { + raw_block_decoder_axi_ar_arid, + raw_block_decoder_axi_ar_araddr, + raw_block_decoder_axi_ar_arregion, + raw_block_decoder_axi_ar_arlen, + raw_block_decoder_axi_ar_arsize, + raw_block_decoder_axi_ar_arburst, + raw_block_decoder_axi_ar_arcache, + raw_block_decoder_axi_ar_arprot, + raw_block_decoder_axi_ar_arqos + } = zstd_dec__raw_axi_ar; + assign raw_block_decoder_axi_ar_arvalid = zstd_dec__raw_axi_ar_vld; + assign zstd_dec__raw_axi_ar_rdy = raw_block_decoder_axi_ar_arready; + assign zstd_dec__raw_axi_r = { + raw_block_decoder_axi_r_rid, + raw_block_decoder_axi_r_rdata, + raw_block_decoder_axi_r_rresp, + raw_block_decoder_axi_r_rlast}; + assign zstd_dec__raw_axi_r_vld = raw_block_decoder_axi_r_rvalid; + assign raw_block_decoder_axi_r_rready = zstd_dec__raw_axi_r_rdy; + + // Output Writer + assign { + output_axi_aw_awid, + output_axi_aw_awaddr, + output_axi_aw_awsize, + output_axi_aw_awlen, + output_axi_aw_awburst + } = zstd_dec__output_axi_aw; + assign output_axi_aw_awvalid = zstd_dec__output_axi_aw_vld; + assign zstd_dec__output_axi_aw_rdy = output_axi_aw_awready; + assign { + output_axi_w_wdata, + output_axi_w_wstrb, + output_axi_w_wlast + } = zstd_dec__output_axi_w; + assign output_axi_w_wvalid = zstd_dec__output_axi_w_vld; + assign zstd_dec__output_axi_w_rdy = output_axi_w_wready; + assign zstd_dec__output_axi_b = { + output_axi_b_bresp, + output_axi_b_bid + }; + assign zstd_dec__output_axi_b_vld = output_axi_b_bvalid; + assign output_axi_b_bready = zstd_dec__output_axi_b_rdy; + + assign csr_axi_b_buser = 1'b0; + assign csr_axi_r_ruser = 1'b0; + assign notify_data = notify_vld; + + /* + * ZSTD Decoder instance + */ + ZstdDecoder ZstdDecoder ( + .clk(clk), + .rst(rst), + + // CSR Interface + .zstd_dec__csr_axi_aw_r(zstd_dec__csr_axi_aw), + .zstd_dec__csr_axi_aw_r_vld(zstd_dec__csr_axi_aw_vld), + .zstd_dec__csr_axi_aw_r_rdy(zstd_dec__csr_axi_aw_rdy), + .zstd_dec__csr_axi_w_r(zstd_dec__csr_axi_w), + .zstd_dec__csr_axi_w_r_vld(zstd_dec__csr_axi_w_vld), + .zstd_dec__csr_axi_w_r_rdy(zstd_dec__csr_axi_w_rdy), + .zstd_dec__csr_axi_b_s(zstd_dec__csr_axi_b), + .zstd_dec__csr_axi_b_s_vld(zstd_dec__csr_axi_b_vld), + .zstd_dec__csr_axi_b_s_rdy(zstd_dec__csr_axi_b_rdy), + .zstd_dec__csr_axi_ar_r(zstd_dec__csr_axi_ar), + .zstd_dec__csr_axi_ar_r_vld(zstd_dec__csr_axi_ar_vld), + .zstd_dec__csr_axi_ar_r_rdy(zstd_dec__csr_axi_ar_rdy), + .zstd_dec__csr_axi_r_s(zstd_dec__csr_axi_r), + .zstd_dec__csr_axi_r_s_vld(zstd_dec__csr_axi_r_vld), + .zstd_dec__csr_axi_r_s_rdy(zstd_dec__csr_axi_r_rdy), + + // FrameHeaderDecoder + .zstd_dec__fh_axi_ar_s(zstd_dec__fh_axi_ar), + .zstd_dec__fh_axi_ar_s_vld(zstd_dec__fh_axi_ar_vld), + .zstd_dec__fh_axi_ar_s_rdy(zstd_dec__fh_axi_ar_rdy), + .zstd_dec__fh_axi_r_r(zstd_dec__fh_axi_r), + .zstd_dec__fh_axi_r_r_vld(zstd_dec__fh_axi_r_vld), + .zstd_dec__fh_axi_r_r_rdy(zstd_dec__fh_axi_r_rdy), + + // BlockHeaderDecoder + .zstd_dec__bh_axi_ar_s(zstd_dec__bh_axi_ar), + .zstd_dec__bh_axi_ar_s_vld(zstd_dec__bh_axi_ar_vld), + .zstd_dec__bh_axi_ar_s_rdy(zstd_dec__bh_axi_ar_rdy), + .zstd_dec__bh_axi_r_r(zstd_dec__bh_axi_r), + .zstd_dec__bh_axi_r_r_vld(zstd_dec__bh_axi_r_vld), + .zstd_dec__bh_axi_r_r_rdy(zstd_dec__bh_axi_r_rdy), + + // RawBlockDecoder + .zstd_dec__raw_axi_ar_s(zstd_dec__raw_axi_ar), + .zstd_dec__raw_axi_ar_s_vld(zstd_dec__raw_axi_ar_vld), + .zstd_dec__raw_axi_ar_s_rdy(zstd_dec__raw_axi_ar_rdy), + .zstd_dec__raw_axi_r_r(zstd_dec__raw_axi_r), + .zstd_dec__raw_axi_r_r_vld(zstd_dec__raw_axi_r_vld), + .zstd_dec__raw_axi_r_r_rdy(zstd_dec__raw_axi_r_rdy), + + // Output Writer + .zstd_dec__output_axi_aw_s(zstd_dec__output_axi_aw), + .zstd_dec__output_axi_aw_s_vld(zstd_dec__output_axi_aw_vld), + .zstd_dec__output_axi_aw_s_rdy(zstd_dec__output_axi_aw_rdy), + .zstd_dec__output_axi_w_s(zstd_dec__output_axi_w), + .zstd_dec__output_axi_w_s_vld(zstd_dec__output_axi_w_vld), + .zstd_dec__output_axi_w_s_rdy(zstd_dec__output_axi_w_rdy), + .zstd_dec__output_axi_b_r(zstd_dec__output_axi_b), + .zstd_dec__output_axi_b_r_vld(zstd_dec__output_axi_b_vld), + .zstd_dec__output_axi_b_r_rdy(zstd_dec__output_axi_b_rdy), + + // Other ports + .zstd_dec__notify_s_vld(notify_vld), + .zstd_dec__notify_s_rdy(notify_rdy) + ); + + assign frame_header_decoder_axi_r_rresp[2] = '0; + assign block_header_decoder_axi_r_rresp[2] = '0; + assign raw_block_decoder_axi_r_rresp[2] = '0; + assign output_axi_b_bresp[2] = '0; + assign memory_axi_b_bresp[2] = '0; + assign memory_axi_r_rresp[2] = '0; + /* + * AXI Interconnect + */ + axi_crossbar_wrapper #( + .DATA_WIDTH(AXI_DATA_W), + .ADDR_WIDTH(AXI_ADDR_W), + .M00_ADDR_WIDTH(AXI_ADDR_W), + .M00_BASE_ADDR(32'd0), + .STRB_WIDTH(AXI_STRB_W), + .S_ID_WIDTH(S_AXI_ID_W), + .M_ID_WIDTH(M_AXI_ID_W) + ) axi_memory_interconnect ( + .clk(clk), + .rst(rst), + + /* + * AXI Subordinate interfaces + */ + // FrameHeaderDecoder + .s00_axi_awid('0), + .s00_axi_awaddr('0), + .s00_axi_awlen('0), + .s00_axi_awsize('0), + .s00_axi_awburst('0), + .s00_axi_awlock('0), + .s00_axi_awcache('0), + .s00_axi_awprot('0), + .s00_axi_awqos('0), + .s00_axi_awuser('0), + .s00_axi_awvalid('0), + .s00_axi_awready(), + .s00_axi_wdata('0), + .s00_axi_wstrb('0), + .s00_axi_wlast('0), + .s00_axi_wuser('0), + .s00_axi_wvalid(), + .s00_axi_wready(), + .s00_axi_bid(), + .s00_axi_bresp(), + .s00_axi_buser(), + .s00_axi_bvalid(), + .s00_axi_bready('0), + .s00_axi_arid(frame_header_decoder_axi_ar_arid), + .s00_axi_araddr(frame_header_decoder_axi_ar_araddr), + .s00_axi_arlen(frame_header_decoder_axi_ar_arlen), + .s00_axi_arsize(frame_header_decoder_axi_ar_arsize), + .s00_axi_arburst(frame_header_decoder_axi_ar_arburst), + .s00_axi_arlock('0), + .s00_axi_arcache(frame_header_decoder_axi_ar_arcache), + .s00_axi_arprot(frame_header_decoder_axi_ar_arprot), + .s00_axi_arqos(frame_header_decoder_axi_ar_arqos), + .s00_axi_aruser('0), + .s00_axi_arvalid(frame_header_decoder_axi_ar_arvalid), + .s00_axi_arready(frame_header_decoder_axi_ar_arready), + .s00_axi_rid(frame_header_decoder_axi_r_rid), + .s00_axi_rdata(frame_header_decoder_axi_r_rdata), + .s00_axi_rresp(frame_header_decoder_axi_r_rresp[1:0]), + .s00_axi_rlast(frame_header_decoder_axi_r_rlast), + .s00_axi_ruser(), + .s00_axi_rvalid(frame_header_decoder_axi_r_rvalid), + .s00_axi_rready(frame_header_decoder_axi_r_rready), + + // BlockHeaderDecoder + .s01_axi_awid('0), + .s01_axi_awaddr('0), + .s01_axi_awlen('0), + .s01_axi_awsize('0), + .s01_axi_awburst('0), + .s01_axi_awlock('0), + .s01_axi_awcache('0), + .s01_axi_awprot('0), + .s01_axi_awqos('0), + .s01_axi_awuser('0), + .s01_axi_awvalid('0), + .s01_axi_awready(), + .s01_axi_wdata('0), + .s01_axi_wstrb('0), + .s01_axi_wlast('0), + .s01_axi_wuser('0), + .s01_axi_wvalid(), + .s01_axi_wready(), + .s01_axi_bid(), + .s01_axi_bresp(), + .s01_axi_buser(), + .s01_axi_bvalid(), + .s01_axi_bready('0), + .s01_axi_arid(block_header_decoder_axi_ar_arid), + .s01_axi_araddr(block_header_decoder_axi_ar_araddr), + .s01_axi_arlen(block_header_decoder_axi_ar_arlen), + .s01_axi_arsize(block_header_decoder_axi_ar_arsize), + .s01_axi_arburst(block_header_decoder_axi_ar_arburst), + .s01_axi_arlock('0), + .s01_axi_arcache(block_header_decoder_axi_ar_arcache), + .s01_axi_arprot(block_header_decoder_axi_ar_arprot), + .s01_axi_arqos(block_header_decoder_axi_ar_arqos), + .s01_axi_aruser('0), + .s01_axi_arvalid(block_header_decoder_axi_ar_arvalid), + .s01_axi_arready(block_header_decoder_axi_ar_arready), + .s01_axi_rid(block_header_decoder_axi_r_rid), + .s01_axi_rdata(block_header_decoder_axi_r_rdata), + .s01_axi_rresp(block_header_decoder_axi_r_rresp[1:0]), + .s01_axi_rlast(block_header_decoder_axi_r_rlast), + .s01_axi_ruser(), + .s01_axi_rvalid(block_header_decoder_axi_r_rvalid), + .s01_axi_rready(block_header_decoder_axi_r_rready), + + // RawBlockDecoder + .s02_axi_awid('0), + .s02_axi_awaddr('0), + .s02_axi_awlen('0), + .s02_axi_awsize('0), + .s02_axi_awburst('0), + .s02_axi_awlock('0), + .s02_axi_awcache('0), + .s02_axi_awprot('0), + .s02_axi_awqos('0), + .s02_axi_awuser('0), + .s02_axi_awvalid('0), + .s02_axi_awready(), + .s02_axi_wdata('0), + .s02_axi_wstrb('0), + .s02_axi_wlast('0), + .s02_axi_wuser('0), + .s02_axi_wvalid(), + .s02_axi_wready(), + .s02_axi_bid(), + .s02_axi_bresp(), + .s02_axi_buser(), + .s02_axi_bvalid(), + .s02_axi_bready('0), + .s02_axi_arid(raw_block_decoder_axi_ar_arid), + .s02_axi_araddr(raw_block_decoder_axi_ar_araddr), + .s02_axi_arlen(raw_block_decoder_axi_ar_arlen), + .s02_axi_arsize(raw_block_decoder_axi_ar_arsize), + .s02_axi_arburst(raw_block_decoder_axi_ar_arburst), + .s02_axi_arlock('0), + .s02_axi_arcache(raw_block_decoder_axi_ar_arcache), + .s02_axi_arprot(raw_block_decoder_axi_ar_arprot), + .s02_axi_arqos(raw_block_decoder_axi_ar_arqos), + .s02_axi_aruser('0), + .s02_axi_arvalid(raw_block_decoder_axi_ar_arvalid), + .s02_axi_arready(raw_block_decoder_axi_ar_arready), + .s02_axi_rid(raw_block_decoder_axi_r_rid), + .s02_axi_rdata(raw_block_decoder_axi_r_rdata), + .s02_axi_rresp(raw_block_decoder_axi_r_rresp[1:0]), + .s02_axi_rlast(raw_block_decoder_axi_r_rlast), + .s02_axi_ruser(), + .s02_axi_rvalid(raw_block_decoder_axi_r_rvalid), + .s02_axi_rready(raw_block_decoder_axi_r_rready), + + // SequenceExecutor + .s03_axi_awid(output_axi_aw_awid), + .s03_axi_awaddr(output_axi_aw_awaddr), + .s03_axi_awlen(output_axi_aw_awlen), + .s03_axi_awsize(output_axi_aw_awsize), + .s03_axi_awburst(output_axi_aw_awburst), + .s03_axi_awlock('0), + .s03_axi_awcache('0), + .s03_axi_awprot('0), + .s03_axi_awqos('0), + .s03_axi_awuser('0), + .s03_axi_awvalid(output_axi_aw_awvalid), + .s03_axi_awready(output_axi_aw_awready), + .s03_axi_wdata(output_axi_w_wdata), + .s03_axi_wstrb(output_axi_w_wstrb), + .s03_axi_wlast(output_axi_w_wlast), + .s03_axi_wuser('0), + .s03_axi_wvalid(output_axi_w_wvalid), + .s03_axi_wready(output_axi_w_wready), + .s03_axi_bid(output_axi_b_bid), + .s03_axi_bresp(output_axi_b_bresp), + .s03_axi_buser(), + .s03_axi_bvalid(output_axi_b_bvalid), + .s03_axi_bready(output_axi_b_bready), + .s03_axi_arid('0), + .s03_axi_araddr('0), + .s03_axi_arlen('0), + .s03_axi_arsize('0), + .s03_axi_arburst('0), + .s03_axi_arlock('0), + .s03_axi_arcache('0), + .s03_axi_arprot('0), + .s03_axi_arqos('0), + .s03_axi_aruser('0), + .s03_axi_arvalid('0), + .s03_axi_arready(), + .s03_axi_rid(), + .s03_axi_rdata(), + .s03_axi_rresp(), + .s03_axi_rlast(), + .s03_axi_ruser(), + .s03_axi_rvalid(), + .s03_axi_rready('0), + + /* + * AXI Manager interface + */ + // Outside-facing AXI interface of the ZSTD Decoder + .m00_axi_awid(memory_axi_aw_awid), + .m00_axi_awaddr(memory_axi_aw_awaddr), + .m00_axi_awlen(memory_axi_aw_awlen), + .m00_axi_awsize(memory_axi_aw_awsize), + .m00_axi_awburst(memory_axi_aw_awburst), + .m00_axi_awlock(memory_axi_aw_awlock), + .m00_axi_awcache(memory_axi_aw_awcache), + .m00_axi_awprot(memory_axi_aw_awprot), + .m00_axi_awqos(memory_axi_aw_awqos), + .m00_axi_awregion(memory_axi_aw_awregion), + .m00_axi_awuser(memory_axi_aw_awuser), + .m00_axi_awvalid(memory_axi_aw_awvalid), + .m00_axi_awready(memory_axi_aw_awready), + .m00_axi_wdata(memory_axi_w_wdata), + .m00_axi_wstrb(memory_axi_w_wstrb), + .m00_axi_wlast(memory_axi_w_wlast), + .m00_axi_wuser(memory_axi_w_wuser), + .m00_axi_wvalid(memory_axi_w_wvalid), + .m00_axi_wready(memory_axi_w_wready), + .m00_axi_bid(memory_axi_b_bid), + .m00_axi_bresp(memory_axi_b_bresp[1:0]), + .m00_axi_buser(memory_axi_b_buser), + .m00_axi_bvalid(memory_axi_b_bvalid), + .m00_axi_bready(memory_axi_b_bready), + .m00_axi_arid(memory_axi_ar_arid), + .m00_axi_araddr(memory_axi_ar_araddr), + .m00_axi_arlen(memory_axi_ar_arlen), + .m00_axi_arsize(memory_axi_ar_arsize), + .m00_axi_arburst(memory_axi_ar_arburst), + .m00_axi_arlock(memory_axi_ar_arlock), + .m00_axi_arcache(memory_axi_ar_arcache), + .m00_axi_arprot(memory_axi_ar_arprot), + .m00_axi_arqos(memory_axi_ar_arqos), + .m00_axi_arregion(memory_axi_ar_arregion), + .m00_axi_aruser(memory_axi_ar_aruser), + .m00_axi_arvalid(memory_axi_ar_arvalid), + .m00_axi_arready(memory_axi_ar_arready), + .m00_axi_rid(memory_axi_r_rid), + .m00_axi_rdata(memory_axi_r_rdata), + .m00_axi_rresp(memory_axi_r_rresp[1:0]), + .m00_axi_rlast(memory_axi_r_rlast), + .m00_axi_ruser(memory_axi_r_ruser), + .m00_axi_rvalid(memory_axi_r_rvalid), + .m00_axi_rready(memory_axi_r_rready) + ); + +endmodule : zstd_dec_wrapper From bc0808e8a90e107ace1c4f721258d9ece07539e9 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Wed, 9 Apr 2025 16:15:03 +0200 Subject: [PATCH 75/85] CI/modules-zstd: Remove step for running CC tests No longer applicable - there are no CC tests in ZSTD module Signed-off-by: Pawel Czarnecki --- .github/workflows/modules-zstd.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/modules-zstd.yml b/.github/workflows/modules-zstd.yml index 4cd6162fa7..f82b5cd010 100644 --- a/.github/workflows/modules-zstd.yml +++ b/.github/workflows/modules-zstd.yml @@ -47,11 +47,6 @@ jobs: run: | 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() }} run: | From 4f2e1891eb14b2153afa9185933119e45d8f501e Mon Sep 17 00:00:00 2001 From: Wojciech Sipak Date: Tue, 13 May 2025 18:59:46 +0200 Subject: [PATCH 76/85] modules/zstd: add license Signed-off-by: Wojciech Sipak --- xls/modules/zstd/data/comp_frame.x | 14 ++++++++++++++ xls/modules/zstd/data/comp_frame_fse_comp.x | 14 ++++++++++++++ xls/modules/zstd/data/comp_frame_fse_repeated.x | 14 ++++++++++++++ xls/modules/zstd/data/comp_frame_huffman.x | 14 ++++++++++++++ xls/modules/zstd/data/comp_frame_huffman_fse.x | 14 ++++++++++++++ 5 files changed, 70 insertions(+) diff --git a/xls/modules/zstd/data/comp_frame.x b/xls/modules/zstd/data/comp_frame.x index 8376d6bf1d..399c7258f2 100644 --- a/xls/modules/zstd/data/comp_frame.x +++ b/xls/modules/zstd/data/comp_frame.x @@ -1,3 +1,17 @@ +// Copyright 2025 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + pub struct DataArray{ data: uN[BITS_PER_WORD][LENGTH], length: u32, diff --git a/xls/modules/zstd/data/comp_frame_fse_comp.x b/xls/modules/zstd/data/comp_frame_fse_comp.x index 1eb07c06e7..696bbadff4 100644 --- a/xls/modules/zstd/data/comp_frame_fse_comp.x +++ b/xls/modules/zstd/data/comp_frame_fse_comp.x @@ -1,3 +1,17 @@ +// Copyright 2025 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + pub struct DataArray{ data: uN[BITS_PER_WORD][LENGTH], length: u32, diff --git a/xls/modules/zstd/data/comp_frame_fse_repeated.x b/xls/modules/zstd/data/comp_frame_fse_repeated.x index 37de3b7ddf..f9b84897e9 100644 --- a/xls/modules/zstd/data/comp_frame_fse_repeated.x +++ b/xls/modules/zstd/data/comp_frame_fse_repeated.x @@ -1,3 +1,17 @@ +// Copyright 2025 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + pub struct DataArray{ data: uN[BITS_PER_WORD][LENGTH], length: u32, diff --git a/xls/modules/zstd/data/comp_frame_huffman.x b/xls/modules/zstd/data/comp_frame_huffman.x index a83c42577d..e1b524265b 100644 --- a/xls/modules/zstd/data/comp_frame_huffman.x +++ b/xls/modules/zstd/data/comp_frame_huffman.x @@ -1,3 +1,17 @@ +// Copyright 2025 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + pub struct DataArray{ data: uN[BITS_PER_WORD][LENGTH], length: u32, diff --git a/xls/modules/zstd/data/comp_frame_huffman_fse.x b/xls/modules/zstd/data/comp_frame_huffman_fse.x index 7732b115cd..13ff0f45f8 100644 --- a/xls/modules/zstd/data/comp_frame_huffman_fse.x +++ b/xls/modules/zstd/data/comp_frame_huffman_fse.x @@ -1,3 +1,17 @@ +// Copyright 2025 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + pub struct DataArray{ data: uN[BITS_PER_WORD][LENGTH], length: u32, From d9f692f84d5fb2c7d87ec707694dfe7697f95608 Mon Sep 17 00:00:00 2001 From: Wojciech Sipak Date: Tue, 20 May 2025 16:21:24 +0200 Subject: [PATCH 77/85] Style changes Improve formatting, wording and fix lint issues Co-authored-by: Dominik Lau Co-authored-by: Szymon Gizler Signed-off-by: Wojciech Sipak --- xls/modules/zstd/README.md | 493 +++++++++++++++-------- xls/modules/zstd/comp_block_dec.x | 1 - xls/modules/zstd/huffman_literals_dec.x | 1 - xls/modules/zstd/memory/README.md | 16 +- xls/modules/zstd/memory/axi_ram_reader.x | 2 - xls/modules/zstd/ram_mux.x | 1 - xls/modules/zstd/zstd_dec_test.x | 1 - 7 files changed, 326 insertions(+), 189 deletions(-) diff --git a/xls/modules/zstd/README.md b/xls/modules/zstd/README.md index b60d630408..fb079592e3 100644 --- a/xls/modules/zstd/README.md +++ b/xls/modules/zstd/README.md @@ -1,9 +1,10 @@ # 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. -An overview of the decoder architecture is presented in the diagram below. -The decoder comprises: +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. An overview of the decoder architecture is presented in +the diagram below. The decoder comprises: + * Memory Readers * Memory Writer, * Control and Status Registers, @@ -13,33 +14,39 @@ The decoder comprises: * Command Aggregator, The Decoder interacts with the environment through a set of ports: + * Memory Interface (AXI) * CSR Interface (AXI) * Notify line -The software controls the core through registers accessible through the `CSR Interface`. -The CSRs are used to configure the decoder and to start the decoding process. +The software controls the core through registers accessible through the `CSR +Interface`. The CSRs are used to configure the decoder and to start the decoding +process. ZSTD frames to decode are placed in a memory that should be connected to decoder's `Memory Interface`. Once the decoding process is started, the decoder: -1. Reads the configuration from the CSRs, -2. Decodes the Frame Header, -3. Decodes the Block Header, -4. Decodes the Block Data with the correct processing unit picked based on the Block Type from the Block Header, -4. Aggregates the processing unit results in the correct order into a stream and routes it to the history buffer, -5. Assembles the data block outputs based on the history buffer contents and updates the history, -6. Prepares the final output of the decoder and writes it to the memory, -7. (Optional) Calculates checksum and compares it against the checksum read from the frame.[^2] - -![](img/ZSTD_decoder.png) +1. Reads the configuration from the CSRs, +2. Decodes the Frame Header, +3. Decodes the Block Header, +4. Decodes the Block Data with the correct processing unit picked based on the + Block Type from the Block Header, +5. Aggregates the processing unit results in the correct order into a stream + and routes it to the history buffer, +6. Assembles the data block outputs based on the history buffer contents and + updates the history, +7. Prepares the final output of the decoder and writes it to the memory, +8. (Optional) Calculates checksum and compares it against the checksum read + from the frame.[^2] + +![brief data flow diagram of ZstdDecoder](img/ZSTD_decoder.png) ## Registers description -The ZSTD Decoder operation is based on the values stored in a set of CSRs accessible to the user through the AXI bus. -The registers are defined below: +The ZSTD Decoder operation is based on the values stored in a set of CSRs +accessible to the user through the AXI bus. The registers are defined below: | Name | Address | Description | | ---- | ------- | ----------- | @@ -50,7 +57,8 @@ The registers are defined below: ### Status codes -The following is a list of all available status codes that can be written in the `Status` register. +The following is a list of all available status codes that can be written in the +`Status` register. | Name | Value | Description | | ---- | ------- | ----------- | @@ -69,34 +77,44 @@ The following is a list of all available status codes that can be written in the ## Controlling the decoder from the software -The configuration done by the software must be carried out when the decoder is in the `IDLE` state. -It is the only time when the decoder will be able to take the configuration values from the CSRs and use those in the decoding process. +The configuration done by the software must be carried out when the decoder is +in the `IDLE` state. It is the only time when the decoder will be able to take +the configuration values from the CSRs and use those in the decoding process. -The software should first read the `Status` register to confirm that the decoder is in the `IDLE` state. -In case it is not in the `IDLE` state, it is possible to reset the decoder by writing `1` to the `Reset` register. -Please note that this will stop ongoing decoding and all progress will be lost. +The software should first read the `Status` register to confirm that the decoder +is in the `IDLE` state. -Then, the software has to reserve the memory for the input buffer and write the frame to decode there. -The address of the buffer should be written into `Input Buffer` register so that the decoder will know where to look for the frame to decode. +Then, the software has to reserve the memory for the input buffer and write the +frame to decode there. The address of the buffer should be written into `Input +Buffer` register so that the decoder will know where to look for the frame to +decode. -The next step is to reserve the memory space for the decoded frame where the Decoder will write the decompressed data. -The address to that buffer should be written to the `Output Buffer` register. +The next step is to reserve the memory space for the decoded frame where the +Decoder will write the decompressed data. The address to that buffer should be +written to the `Output Buffer` register. -Finally, it is possible to start the decoding process by writing `1` to the `Start` register. -This orders the Decoder to read the configuration CSRs and start reading and decoding data stored in the input buffer. -The Decoder transitions to the `RUNNING` state and then to other states that describe the status of the last operation finished in the decoder (see #status-codes for other possible status codes) which will be visible in the `Status` register. +Finally, it is possible to start the decoding process by writing `1` to the +`Start` register. This orders the Decoder to read the configuration CSRs and +start reading and decoding data stored in the input buffer. The Decoder +transitions to the `RUNNING` state and then to other states that describe the +status of the last operation finished in the decoder (see #status-codes for +other possible status codes) which will be visible in the `Status` register. -When the decoding process is finished the Decoder transitions back to the `IDLE` state and signals this on the `Notify` IRQ line. -The decoded data is stored under the address configured previously in the `Output Buffer` register. +When the decoding process is finished the Decoder transitions back to the `IDLE` +state and signals this on the `Notify` IRQ line. The decoded data is stored +under the address configured previously in the `Output Buffer` register. -In case an error occurs during the decoding process it is also signaled on the `Notify` IRQ line and the error code is written to the `Status` CSR. +In case an error occurs during the decoding process it is also signaled on the +`Notify` IRQ line and the error code is written to the `Status` CSR. ## ZSTD decoder architecture ### Top level Proc -This state machine is responsible for controlling the operation of the whole decoder. -It uses the configuration data from the CSRs, connects all underlying modules and sends processing requests to those based on the state of the machine. -The states defined for the processing of the ZSTD frame are as follows: + +This state machine is responsible for controlling the operation of the whole +decoder. It uses the configuration data from the CSRs, connects all underlying +modules and sends processing requests to those based on the state of the +machine. The states defined for the processing of the ZSTD frame are as follows: ```mermaid stateDiagram @@ -143,185 +161,277 @@ stateDiagram ERROR --> IDLE ``` -After going through the initial stage of reading the configuration from the CSRs, the decoder sends the processing requests to the underlying parts of the decoder. -The processing requests contain the addresses in the memory where particular parts of the encoded ZSTD frames reside. -The decoder, based on responses from consecutive internal modules, calculates offsets from the base address that was written to `Input Buffer` CSR and forms the requests for the next internal modules, e.g.: for `BlockHeaderDecoder` or any of the processing units (`RawBlockDecoder`, `RleBlockDecoder`, `CompressedBlockDecoder`). - -Each of the internal modules waits for the processing request. -Once received, the module fetches the data from the memory starting from the address received in the processing request. -`MemReader` procs are used by those modules to communicate with the external memory through the AXI interface. -Internal modules decode the acquired parts of the frame and return responses with the results back to the top level proc. - -The processing units also output the decoded blocks of data through a stream-based interface to the `SequenceExecutor` proc. -This proc performs the last step of the decoding before the final output is sent out back to the memory under the address stored in the `Output Buffer` CSR by the `MemWriter` proc. -Once the decoding process is completed and the decoded frame is written back to the memory, the decoder sends the `Notify` signal and transitions back to the `IDLE` state. +After going through the initial stage of reading the configuration from the +CSRs, the decoder sends the processing requests to the underlying parts of the +decoder. The processing requests contain the addresses in the memory where +particular parts of the encoded ZSTD frames reside. The decoder, based on +responses from consecutive internal modules, calculates offsets from the base +address that was written to `Input Buffer` CSR and forms the requests for the +next internal modules, e.g.: for `BlockHeaderDecoder` or any of the processing +units (`RawBlockDecoder`, `RleBlockDecoder`, `CompressedBlockDecoder`). + +Each of the internal modules waits for the processing request. Once received, +the module fetches the data from the memory starting from the address received +in the processing request. `MemReader` procs are used by those modules to +communicate with the external memory through the AXI interface. Internal modules +decode the acquired parts of the frame and return responses with the results +back to the top level proc. + +The processing units also output the decoded blocks of data through a +stream-based interface to the `SequenceExecutor` proc. This proc performs the +last step of the decoding before the final output is sent out back to the memory +under the address stored in the `Output Buffer` CSR by the `MemWriter` proc. +Once the decoding process is completed and the decoded frame is written back to +the memory, the decoder sends the `Notify` signal and transitions back to the +`IDLE` state. ### Internal modules #### FrameHeaderDecoder + This proc receives requests with the address of the beginning of the ZSTD frame. -It then reads the frame data from the memory and starts parsing the frame header. -If the magic number is not detected or the frame header is invalid, the proc will send a response with an error code. -Otherwise, it will put the frame header into internal DSLX representation, calculate the length of the header and send those as a response with `OKAY` status. +It then reads the frame data from the memory and starts parsing the frame +header. If the magic number is not detected or the frame header is invalid, the +proc will send a response with an error code. Otherwise, it will put the frame +header into internal DSLX representation, calculate the length of the header and +send those as a response with `OKAY` status. #### BlockHeaderDecoder -ZSTD block header size is always 3 bytes. -BlockHeaderDecoder always reads 4 bytes of data. -It extracts the information on block type, size and whether the block is the last one in the ZSTD frame and puts that data in the response. -The additional byte is also placed in the response as an optimization for the RleBlockDecoder. + +ZSTD block header size is always 3 bytes. BlockHeaderDecoder always reads 4 +bytes of data. It extracts the information on block type, size and whether the +block is the last one in the ZSTD frame and puts that data in the response. The +additional byte is also placed in the response as an optimization for the +RleBlockDecoder. #### RawBlockDecoder + This proc passes the data read from the memory 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. +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. #### RleBlockDecoder -This proc receives a tuple (s, N), where s is an 8-bit symbol and N is an accompanying `symbol_count`. -It does not have to read the 8-bit symbol from the memory because `BlockHeaderDecoder` did that before and passed the symbol in the processing request to the `RleBlockDecoder`. -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. + +This proc receives a tuple (s, N), where s is an 8-bit symbol and N is an +accompanying `symbol_count`. It does not have to read the 8-bit symbol from the +memory because `BlockHeaderDecoder` did that before and passed the symbol in the +processing request to the `RleBlockDecoder`. 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. #### CompressedBlockDecoder[^1] + This part of the design is responsible for decoding the compressed data blocks. -It ingests the bytes stream, and 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. +It ingests the bytes stream, and 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-architecture1) paragraph of this doc. #### Commands aggregator (DecMux) -This stage takes the output from either RAW, RLE or CompressedBlockDecoder and sends it to the History buffer and command execution stage. -This stage orders streams based on the ID value assigned by the top level proc. -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. +This stage takes the output from either RAW, RLE or CompressedBlockDecoder and +sends it to the History buffer and command execution stage. This stage orders +streams based on the ID value assigned by the top level proc. 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`. +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 (SequenceExecutor) -This stage receives data which is tagged either `literals` or `copy`. -This stage will show the following behavior, depending on the tag: + +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, + * 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. + * 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[^1] {#compressed-block-decoder-architecture1} -### Compressed block decoder architecture[^1] -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 the expected data output. -An overview of the architecture is provided in the diagram below. -The architecture is split into 2 paths: the literals path and the sequence path. -Architecture is split into 3 paths: literals path, FSE encoded Huffman trees and sequence path. -Literals path uses Huffman trees to decode some types of compressed blocks: Compressed and Treeless blocks. +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 the expected data output. An overview of the +architecture is provided in the diagram below. The architecture is split into 2 +paths: the literals path and the sequence path. Architecture is split into 3 +paths: literals path, FSE encoded Huffman trees and sequence path. Literals path +uses Huffman trees to decode some types of compressed blocks: Compressed and +Treeless blocks. -![](img/ZSTD_compressed_block_decoder.png) +![data flow diagram of compressed block decoder](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. +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). + +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) +![data flow diagram of literals decoder](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. +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. + +This stage works similarly to the [RLE stage](#rleblockdecoder) 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. + +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. + +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. +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) +![data flow diagram of weight decoders](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. + +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. + +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. + +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. + +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. + +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) +![data flow diagram of sequence decoder](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. + +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. + +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. + +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. + +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. ## Testing methodology @@ -330,22 +440,30 @@ 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 positive 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. +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 positive +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 carried out on DSLX and Verilog levels. 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). - -ZstdDecoder's main communication interfaces are the AXI buses. -Due to the way XLS handles the codegen of DSLX channels that model the AXI channels, the particular ports of the AXI channels are not represented correctly. -This enforces the introduction of a Verilog wrapper that maps the ports generated by XLS into proper AXI ports (see AXI peripherals [README](memory/README.md) for more information). -Additionally, the wrapper is used to mux multiple AXI interfaces from `Memory Readers` and `Memory Writer` into a single outside-facing AXI interface (`Memory Interface`) that can be connected to the external memory. -The mux is implemented by a third-party [AXI Crossbar](https://github.com/alexforencich/verilog-axi). - -![](img/ZSTD_decoder_wrapper.png) +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). + +ZstdDecoder's main communication interfaces are the AXI buses. Due to the way +XLS handles the codegen of DSLX channels that model the AXI channels, the +particular ports of the AXI channels are not represented correctly. This +enforces the introduction of a Verilog wrapper that maps the ports generated by +XLS into proper AXI ports (see AXI peripherals [README](memory/README.md) for +more information). Additionally, the wrapper is used to mux multiple AXI +interfaces from `Memory Readers` and `Memory Writer` into a single +outside-facing AXI interface (`Memory Interface`) that can be connected to the +external memory. The mux is implemented by a third-party [AXI +Crossbar](https://github.com/alexforencich/verilog-axi). + +![diagram of interfaces of decoder and its wrapper](img/ZSTD_decoder_wrapper.png) ### Failure points @@ -356,42 +474,65 @@ 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 + * 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 Currently, all mentioned conditions lead to an eventual test failure. #### 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. +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. Upon entering the `ERROR` state, the decoder +will write a specific error code to the `Status` CSR and send a `Notify` signal +to the output. The interacting software can then read the code from the register +and properly handle the error. -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. -Upon entering the `ERROR` state, the decoder will write a specific error code to the `Status` CSR and send a `Notify` signal to the output. -The interacting software can then read the code from the register and properly handle the error. +The following enumeration will describe how to trigger each possible ZSTD +Decoder error. -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 Verilog tests but also in DSLX tests for the specific components: -The `ERROR` state can be encountered under the following conditions when running Top Level Proc Verilog tests but also in DSLX tests for the specific components: * Corrupted data on the frame header decoding stage - * Provide data for the decoding with the first 4 bytes not being the valid `Magic Number` (0xFD2FB528) + * Provide data for the decoding with the first 4 bytes not being the valid +`Magic Number` (0xFD2FB528) * Set the `Reserved bit` in the frame header descriptor - * 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) + * 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: -* 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` + +* 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 `fail!()`. -Those are mostly enforced by the type checker for the `match` expressions to cover unreachable cases. +Those are mostly enforced by the type checker for the `match` expressions to +cover unreachable cases. This is done for example in: * Frame header decoder diff --git a/xls/modules/zstd/comp_block_dec.x b/xls/modules/zstd/comp_block_dec.x index a94658ba12..f40cfad3cb 100644 --- a/xls/modules/zstd/comp_block_dec.x +++ b/xls/modules/zstd/comp_block_dec.x @@ -1636,4 +1636,3 @@ proc CompressBlockDecoderTest { send(tok, terminator, true); } } - diff --git a/xls/modules/zstd/huffman_literals_dec.x b/xls/modules/zstd/huffman_literals_dec.x index 81819e9e75..b60cadedad 100644 --- a/xls/modules/zstd/huffman_literals_dec.x +++ b/xls/modules/zstd/huffman_literals_dec.x @@ -1233,4 +1233,3 @@ proc HuffmanLiteralsDecoder_test { // literals_last: true, // }, //]; - diff --git a/xls/modules/zstd/memory/README.md b/xls/modules/zstd/memory/README.md index 6a0e4aedfb..37c20c45a3 100644 --- a/xls/modules/zstd/memory/README.md +++ b/xls/modules/zstd/memory/README.md @@ -16,7 +16,7 @@ structures will not match the expected AXI bus signature. To interface with other AXI peripherals, additional Verilog wrappers may be needed to split the flattened bit vector into individual signals. -# Data Structures +## Data Structures As noted, the procs in this directory use channels with dedicated structures to represent AXI bus signals in DSLX. For instance, the AXI read @@ -27,7 +27,7 @@ to receive the read data. These channels are represented by the `AxiAr` and The structures used to represent AXI4 Stream interface can be found in the `axi_st.x` file. -# Main components +## Main components The primary components of this directory are `MemReader` and `MemWriter`, which facilitate issuing read and write transactions on the AXI bus. @@ -38,7 +38,8 @@ The `MemReader` includes several procs that can be used individually: - `AxiReader`: Handles the creation of AXI transactions, managing unaligned addresses and issuing additional transactions when crossing the 4KB boundary - or when the read request is longer than maximum possible burst size on the AXI bus + or when the read request is longer than maximum possible burst size on the + AXI bus - `AxiStreamDownscaler`: An optional proc available in `MemReaderAdv`, enabling DSLX designs to connect to a wider AXI bus. @@ -48,15 +49,16 @@ The `MemReader` includes several procs that can be used individually: The `MemWriter` proc is organised in a similar manner, it consists of: -- `AxiWriter`: Handles the creation of AXI write transactions, managing unaligned - addresses and issuing additional transactions when crossing the 4KB boundary, - or when the write request is longer than maximum possible burst size on the AXI bus +- `AxiWriter`: Handles the creation of AXI write transactions, managing + unaligned addresses and issuing additional transactions when crossing the 4KB + boundary, or when the write request is longer than maximum possible burst size + on the AXI bus - `AxiStreamAddEmpty`: Adds empty data bits in the stream of data to write. It is used to shift the data in the stream to facilitate writes to unaligned addresses. -# Usage +## Usage The list below shows the usage of the `MemReader` proc: diff --git a/xls/modules/zstd/memory/axi_ram_reader.x b/xls/modules/zstd/memory/axi_ram_reader.x index edea056195..9c6926712d 100644 --- a/xls/modules/zstd/memory/axi_ram_reader.x +++ b/xls/modules/zstd/memory/axi_ram_reader.x @@ -758,5 +758,3 @@ proc AxiRamReaderTest { send(tok, terminator, true); } } - - diff --git a/xls/modules/zstd/ram_mux.x b/xls/modules/zstd/ram_mux.x index 25d785a69f..a4cde3e576 100644 --- a/xls/modules/zstd/ram_mux.x +++ b/xls/modules/zstd/ram_mux.x @@ -235,4 +235,3 @@ proc RamMuxTest { let tok = send(tok, terminator, true); } } - diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x index 151d3f15e3..989ba75cd8 100644 --- a/xls/modules/zstd/zstd_dec_test.x +++ b/xls/modules/zstd/zstd_dec_test.x @@ -898,4 +898,3 @@ proc ZstdDecoderTest { send(tok, terminator, true); } } - From bafb36f36a5f81785b1a3afad7c9ae314b6025f3 Mon Sep 17 00:00:00 2001 From: Wojciech Sipak Date: Wed, 21 May 2025 16:34:27 +0200 Subject: [PATCH 78/85] add targets for generating test frames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pawel Czarnecki Co-authored-by: Krzysztof Obłonczek --- dependency_support/pip_requirements.in | 1 + dependency_support/pip_requirements_lock.txt | 99 ++++++++++++ xls/modules/zstd/BUILD | 33 ++++ xls/modules/zstd/data_generator.py | 66 ++++++++ xls/modules/zstd/zstd_frame_dslx.py | 155 +++++++++++++++++++ 5 files changed, 354 insertions(+) create mode 100644 xls/modules/zstd/data_generator.py create mode 100644 xls/modules/zstd/zstd_frame_dslx.py diff --git a/dependency_support/pip_requirements.in b/dependency_support/pip_requirements.in index 804306558e..034b9d393c 100644 --- a/dependency_support/pip_requirements.in +++ b/dependency_support/pip_requirements.in @@ -13,6 +13,7 @@ pyyaml==6.0.1 # We build most of z3 ourselves but building python is really complicated. Just # use pypi version z3-solver==4.14.0.0 +zstandard==0.23.0 # Note: numpy and scipy version availability seems to differ between Ubuntu # versions that we want to support (e.g. 18.04 vs 20.04), so we accept a diff --git a/dependency_support/pip_requirements_lock.txt b/dependency_support/pip_requirements_lock.txt index dc895c7f19..a63ef9ffcd 100644 --- a/dependency_support/pip_requirements_lock.txt +++ b/dependency_support/pip_requirements_lock.txt @@ -541,3 +541,102 @@ z3-solver==4.14.0.0 \ --hash=sha256:c10f899c6a876e3a50e9b2c4927604c2c3da3cca672b8ed3b7db1bc97259e47f \ --hash=sha256:e6ae32bc1cf4d25b96f790755d790a23118e8a09b9b6060e32238fe6ff43606d # via -r dependency_support/pip_requirements.in +zstandard==0.23.0 \ + --hash=sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473 \ + --hash=sha256:0a7f0804bb3799414af278e9ad51be25edf67f78f916e08afdb983e74161b916 \ + --hash=sha256:11e3bf3c924853a2d5835b24f03eeba7fc9b07d8ca499e247e06ff5676461a15 \ + --hash=sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072 \ + --hash=sha256:1516c8c37d3a053b01c1c15b182f3b5f5eef19ced9b930b684a73bad121addf4 \ + --hash=sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e \ + --hash=sha256:1bfe8de1da6d104f15a60d4a8a768288f66aa953bbe00d027398b93fb9680b26 \ + --hash=sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8 \ + --hash=sha256:1fd7e0f1cfb70eb2f95a19b472ee7ad6d9a0a992ec0ae53286870c104ca939e5 \ + --hash=sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd \ + --hash=sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c \ + --hash=sha256:29a2bc7c1b09b0af938b7a8343174b987ae021705acabcbae560166567f5a8db \ + --hash=sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5 \ + --hash=sha256:2ef3775758346d9ac6214123887d25c7061c92afe1f2b354f9388e9e4d48acfc \ + --hash=sha256:2f146f50723defec2975fb7e388ae3a024eb7151542d1599527ec2aa9cacb152 \ + --hash=sha256:2fb4535137de7e244c230e24f9d1ec194f61721c86ebea04e1581d9d06ea1269 \ + --hash=sha256:32ba3b5ccde2d581b1e6aa952c836a6291e8435d788f656fe5976445865ae045 \ + --hash=sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e \ + --hash=sha256:379b378ae694ba78cef921581ebd420c938936a153ded602c4fea612b7eaa90d \ + --hash=sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a \ + --hash=sha256:3aa014d55c3af933c1315eb4bb06dd0459661cc0b15cd61077afa6489bec63bb \ + --hash=sha256:4051e406288b8cdbb993798b9a45c59a4896b6ecee2f875424ec10276a895740 \ + --hash=sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105 \ + --hash=sha256:43da0f0092281bf501f9c5f6f3b4c975a8a0ea82de49ba3f7100e64d422a1274 \ + --hash=sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2 \ + --hash=sha256:48ef6a43b1846f6025dde6ed9fee0c24e1149c1c25f7fb0a0585572b2f3adc58 \ + --hash=sha256:50a80baba0285386f97ea36239855f6020ce452456605f262b2d33ac35c7770b \ + --hash=sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4 \ + --hash=sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db \ + --hash=sha256:53ea7cdc96c6eb56e76bb06894bcfb5dfa93b7adcf59d61c6b92674e24e2dd5e \ + --hash=sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9 \ + --hash=sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0 \ + --hash=sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813 \ + --hash=sha256:61062387ad820c654b6a6b5f0b94484fa19515e0c5116faf29f41a6bc91ded6e \ + --hash=sha256:61f89436cbfede4bc4e91b4397eaa3e2108ebe96d05e93d6ccc95ab5714be512 \ + --hash=sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0 \ + --hash=sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b \ + --hash=sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48 \ + --hash=sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a \ + --hash=sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772 \ + --hash=sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed \ + --hash=sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373 \ + --hash=sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea \ + --hash=sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd \ + --hash=sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f \ + --hash=sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc \ + --hash=sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23 \ + --hash=sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2 \ + --hash=sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db \ + --hash=sha256:82d17e94d735c99621bf8ebf9995f870a6b3e6d14543b99e201ae046dfe7de70 \ + --hash=sha256:837bb6764be6919963ef41235fd56a6486b132ea64afe5fafb4cb279ac44f259 \ + --hash=sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9 \ + --hash=sha256:8c24f21fa2af4bb9f2c492a86fe0c34e6d2c63812a839590edaf177b7398f700 \ + --hash=sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003 \ + --hash=sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba \ + --hash=sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a \ + --hash=sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c \ + --hash=sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90 \ + --hash=sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690 \ + --hash=sha256:a05e6d6218461eb1b4771d973728f0133b2a4613a6779995df557f70794fd60f \ + --hash=sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840 \ + --hash=sha256:a4ae99c57668ca1e78597d8b06d5af837f377f340f4cce993b551b2d7731778d \ + --hash=sha256:a8c86881813a78a6f4508ef9daf9d4995b8ac2d147dcb1a450448941398091c9 \ + --hash=sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35 \ + --hash=sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd \ + --hash=sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a \ + --hash=sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea \ + --hash=sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1 \ + --hash=sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573 \ + --hash=sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09 \ + --hash=sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094 \ + --hash=sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78 \ + --hash=sha256:b8c0bd73aeac689beacd4e7667d48c299f61b959475cdbb91e7d3d88d27c56b9 \ + --hash=sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5 \ + --hash=sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9 \ + --hash=sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391 \ + --hash=sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847 \ + --hash=sha256:c7c517d74bea1a6afd39aa612fa025e6b8011982a0897768a2f7c8ab4ebb78a2 \ + --hash=sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c \ + --hash=sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2 \ + --hash=sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057 \ + --hash=sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20 \ + --hash=sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d \ + --hash=sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4 \ + --hash=sha256:e2d1a054f8f0a191004675755448d12be47fa9bebbcffa3cdf01db19f2d30a54 \ + --hash=sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171 \ + --hash=sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e \ + --hash=sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160 \ + --hash=sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b \ + --hash=sha256:f8346bfa098532bc1fb6c7ef06783e969d87a99dd1d2a5a18a892c1d7a643c58 \ + --hash=sha256:f83fa6cae3fff8e98691248c9320356971b59678a17f20656a9e59cd32cee6d8 \ + --hash=sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33 \ + --hash=sha256:fb2b1ecfef1e67897d336de3a0e3f52478182d6a47eda86cbd42504c5cbd009a \ + --hash=sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880 \ + --hash=sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca \ + --hash=sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b \ + --hash=sha256:fe3b385d996ee0822fd46528d9f0443b880d4d05528fd26a9119a54ec3f91c69 + # via -r dependency_support/pip_requirements.in diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index d80a9bb0af..be61cb8c9f 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -17,6 +17,7 @@ load("@rules_hdl//place_and_route:build_defs.bzl", "place_and_route") load("@rules_hdl//synthesis:build_defs.bzl", "benchmark_synth", "synthesize_rtl") load("@rules_hdl//verilog:providers.bzl", "verilog_library") +load("@xls_pip_deps//:requirements.bzl", "requirement") load( "//xls/build_rules:xls_build_defs.bzl", "xls_benchmark_ir", @@ -367,6 +368,15 @@ cc_library( ], ) +py_library( + name = "data_generator_py", + srcs = ["data_generator.py"], + deps = [ + "//xls/common:runfiles", + "@zstd//:decodecorpus", + ], +) + xls_dslx_library( name = "common_dslx", srcs = [ @@ -1442,6 +1452,29 @@ xls_dslx_test( tags = ["manual"], ) +py_binary( + name = "zstd_test_frames_generator", + srcs = ["zstd_frame_dslx.py"], + imports = ["."], + main = "zstd_frame_dslx.py", + tags = ["manual"], + visibility = ["//xls:xls_users"], + deps = [ + requirement("zstandard"), + "//xls/common:runfiles", + ":data_generator_py", + "@com_google_protobuf//:protobuf_python", + ], +) + +genrule( + name = "zstd_test_frames_generate", + srcs = [], + outs = ["zstd_frame_testcases.x"], + cmd = "$(location :zstd_test_frames_generator) --seed 36 -n 1 --btype COMPRESSED -o $@", + tools = [":zstd_test_frames_generator"], +) + xls_dslx_library( name = "ram_merge_dslx", srcs = ["ram_merge.x"], diff --git a/xls/modules/zstd/data_generator.py b/xls/modules/zstd/data_generator.py new file mode 100644 index 0000000000..46a74acbfc --- /dev/null +++ b/xls/modules/zstd/data_generator.py @@ -0,0 +1,66 @@ +# 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. + +"""Module for generating and decompressing test frames.""" + +import pathlib +import enum + +from xls.common import runfiles +import subprocess +import zstandard + +class BlockType(enum.Enum): + """Enum encoding of ZSTD block types.""" + + RAW = 0 + RLE = 1 + COMPRESSED = 2 + RANDOM = 3 + + def __str__(self): + return self.name + + @staticmethod + def from_string(s): + try: + return BlockType[s] + except KeyError as e: + raise ValueError(str(e)) from e + +def CallDecodecorpus(args): + decodecorpus = pathlib.Path( + runfiles.get_path("decodecorpus", repository = "zstd") + ) + cmd = args + cmd.insert(0, str(decodecorpus)) + cmd_concat = " ".join(cmd) + subprocess.run(cmd_concat, shell=True, check=True) + +def DecompressFrame(data): + dctx = zstandard.ZstdDecompressor() + return dctx.decompress(data) + +def GenerateFrame(seed, btype, output_path): + args = [] + args.append("-s" + str(seed)) + if (btype != BlockType.RANDOM): + args.append("--block-type=" + str(btype.value)) + args.append("--content-size") + # Test payloads up to 16KB + args.append("--max-content-size-log=14") + args.append("-p" + output_path) + args.append("-vvvvvvv") + + CallDecodecorpus(args) diff --git a/xls/modules/zstd/zstd_frame_dslx.py b/xls/modules/zstd/zstd_frame_dslx.py new file mode 100644 index 0000000000..5304e07f45 --- /dev/null +++ b/xls/modules/zstd/zstd_frame_dslx.py @@ -0,0 +1,155 @@ +# 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. + +"""ZSTD test frame generator for DSLX tests. + +This module interacts with the data_generator module and underlying +Decodecorpus in order to generate inputs and expected outputs for the ZSTD +Decoder tests in DSLX. + +It generates the ZSTD frame and then decodes it with the reference ZSTD +library. Both the encoded frame and decoded data are written to a DSLX file by +converting raw bytes of the frame and decoded data into DSLX structures. + +Resulting file can be included in the ZSTD Decoder DSLX test and used as the +inputs and expected output for the testbench. +""" + +import argparse +import math +import random +import tempfile +import pathlib + +from xls.modules.zstd import data_generator + + +def GenerateTestData(seed, btype): + with tempfile.NamedTemporaryFile() as tmp: + data_generator.GenerateFrame(seed, btype, tmp.name) + tmp.seek(0) + return tmp.read() + + +def Bytes2DSLX(frames, bytes_per_word, array_name): + """Converts a list of byte frames to a DSLX constant array declaration. + + Args: + frames (List[bytes]): List of byte sequences representing frames. + bytes_per_word (int): Number of bytes per word in the output format. + array_name (str): Name of the resulting DSLX constant array. + + Returns: + str: A string containing the DSLX constant array declaration. + """ + frames_hex = [] + maxlen = max(len(frame) for frame in frames) + maxlen_size = math.ceil(maxlen / bytes_per_word) + bits_per_word = bytes_per_word * 8 + for i, frame in enumerate(frames): + frame_hex = [] + for i in range(0, len(frame), bytes_per_word): + # reverse byte order to make them little endian + word = bytes(reversed(frame[i : i + bytes_per_word])).hex() + frame_hex.append(f"uN[{bits_per_word}]:0x{word}") + + array_length = len(frame_hex) + if len(frame) < maxlen: + frame_hex += [f"uN[{bits_per_word}]:0x0", "..."] + + frame_array = ( + f"DataArray<{bits_per_word}, {maxlen_size}>{{\n" + f" length: u32:{len(frame)},\n" + f" array_length: u32:{array_length},\n" + f" data: uN[{bits_per_word}][{maxlen_size}]:[{', '.join(frame_hex)}]\n" + f"}}" + ) + frames_hex.append(frame_array) + + frames_str = ",\n".join(frames_hex) + frames_array = ( + f"pub const {array_name}:DataArray<\n" + f" u32:{bits_per_word},\n" + f" u32:{maxlen_size}\n" + f">[{len(frames_hex)}] = [{frames_str}];\n" + ) + return frames_array + + +def GenerateDataStruct(): + return ( + "pub struct DataArray{\n" + " data: uN[BITS_PER_WORD][LENGTH],\n" + " length: u32,\n" + " array_length: u32\n" + "}\n" + ) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-n", help="Number of testcases to generate", type=int, default=1 + ) + parser.add_argument( + "--seed", help="Seed for the testcases generator", type=int, default=0 + ) + parser.add_argument( + "--btype", + help=( + "Block types allowed in the generated testcases. If multiple block types " + "are supplied, generated testcases will cycle through them" + ), + type=data_generator.BlockType.from_string, + choices=list(data_generator.BlockType), + default=data_generator.BlockType.RANDOM, + nargs="+", + ) + parser.add_argument( + "-o", + "--output", + help="Filename of the DSLX output file", + type=pathlib.Path, + default=pathlib.Path("frames_test_data.x"), + ) + parser.add_argument( + "--bytes-per-word", + help="Width of a word in memory, in bytes", + type=int, + default=8, + ) + args = parser.parse_args() + + random.seed(args.seed) + byte_frames = [ + GenerateTestData(random.randrange(2**32), args.btype[i % len(args.btype)]) + for i in range(args.n) + ] + with open(args.output, "w") as dslx_output: + dslx_output.write(GenerateDataStruct()) + + dslx_frames = Bytes2DSLX(byte_frames, args.bytes_per_word, "FRAMES") + dslx_output.write(dslx_frames) + + byte_frames_decompressed = list( + map(data_generator.DecompressFrame, byte_frames) + ) + dslx_frames_decompressed = Bytes2DSLX( + byte_frames_decompressed, args.bytes_per_word, "DECOMPRESSED_FRAMES" + ) + dslx_output.write(dslx_frames_decompressed) + + +if __name__ == "__main__": + main() From 45dc6aa95ed0725a0b66c23279acdafb5910af1a Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Fri, 23 May 2025 09:51:16 +0200 Subject: [PATCH 79/85] Bump zstd Signed-off-by: Robert Winkler --- dependency_support/pip_requirements.in | 2 +- dependency_support/pip_requirements_lock.txt | 150 +++++++------------ 2 files changed, 53 insertions(+), 99 deletions(-) diff --git a/dependency_support/pip_requirements.in b/dependency_support/pip_requirements.in index 034b9d393c..6ec69c187a 100644 --- a/dependency_support/pip_requirements.in +++ b/dependency_support/pip_requirements.in @@ -13,7 +13,7 @@ pyyaml==6.0.1 # We build most of z3 ourselves but building python is really complicated. Just # use pypi version z3-solver==4.14.0.0 -zstandard==0.23.0 +zstandard==0.19.0 # Note: numpy and scipy version availability seems to differ between Ubuntu # versions that we want to support (e.g. 18.04 vs 20.04), so we accept a diff --git a/dependency_support/pip_requirements_lock.txt b/dependency_support/pip_requirements_lock.txt index a63ef9ffcd..cadc770f13 100644 --- a/dependency_support/pip_requirements_lock.txt +++ b/dependency_support/pip_requirements_lock.txt @@ -541,102 +541,56 @@ z3-solver==4.14.0.0 \ --hash=sha256:c10f899c6a876e3a50e9b2c4927604c2c3da3cca672b8ed3b7db1bc97259e47f \ --hash=sha256:e6ae32bc1cf4d25b96f790755d790a23118e8a09b9b6060e32238fe6ff43606d # via -r dependency_support/pip_requirements.in -zstandard==0.23.0 \ - --hash=sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473 \ - --hash=sha256:0a7f0804bb3799414af278e9ad51be25edf67f78f916e08afdb983e74161b916 \ - --hash=sha256:11e3bf3c924853a2d5835b24f03eeba7fc9b07d8ca499e247e06ff5676461a15 \ - --hash=sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072 \ - --hash=sha256:1516c8c37d3a053b01c1c15b182f3b5f5eef19ced9b930b684a73bad121addf4 \ - --hash=sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e \ - --hash=sha256:1bfe8de1da6d104f15a60d4a8a768288f66aa953bbe00d027398b93fb9680b26 \ - --hash=sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8 \ - --hash=sha256:1fd7e0f1cfb70eb2f95a19b472ee7ad6d9a0a992ec0ae53286870c104ca939e5 \ - --hash=sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd \ - --hash=sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c \ - --hash=sha256:29a2bc7c1b09b0af938b7a8343174b987ae021705acabcbae560166567f5a8db \ - --hash=sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5 \ - --hash=sha256:2ef3775758346d9ac6214123887d25c7061c92afe1f2b354f9388e9e4d48acfc \ - --hash=sha256:2f146f50723defec2975fb7e388ae3a024eb7151542d1599527ec2aa9cacb152 \ - --hash=sha256:2fb4535137de7e244c230e24f9d1ec194f61721c86ebea04e1581d9d06ea1269 \ - --hash=sha256:32ba3b5ccde2d581b1e6aa952c836a6291e8435d788f656fe5976445865ae045 \ - --hash=sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e \ - --hash=sha256:379b378ae694ba78cef921581ebd420c938936a153ded602c4fea612b7eaa90d \ - --hash=sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a \ - --hash=sha256:3aa014d55c3af933c1315eb4bb06dd0459661cc0b15cd61077afa6489bec63bb \ - --hash=sha256:4051e406288b8cdbb993798b9a45c59a4896b6ecee2f875424ec10276a895740 \ - --hash=sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105 \ - --hash=sha256:43da0f0092281bf501f9c5f6f3b4c975a8a0ea82de49ba3f7100e64d422a1274 \ - --hash=sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2 \ - --hash=sha256:48ef6a43b1846f6025dde6ed9fee0c24e1149c1c25f7fb0a0585572b2f3adc58 \ - --hash=sha256:50a80baba0285386f97ea36239855f6020ce452456605f262b2d33ac35c7770b \ - --hash=sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4 \ - --hash=sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db \ - --hash=sha256:53ea7cdc96c6eb56e76bb06894bcfb5dfa93b7adcf59d61c6b92674e24e2dd5e \ - --hash=sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9 \ - --hash=sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0 \ - --hash=sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813 \ - --hash=sha256:61062387ad820c654b6a6b5f0b94484fa19515e0c5116faf29f41a6bc91ded6e \ - --hash=sha256:61f89436cbfede4bc4e91b4397eaa3e2108ebe96d05e93d6ccc95ab5714be512 \ - --hash=sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0 \ - --hash=sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b \ - --hash=sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48 \ - --hash=sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a \ - --hash=sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772 \ - --hash=sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed \ - --hash=sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373 \ - --hash=sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea \ - --hash=sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd \ - --hash=sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f \ - --hash=sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc \ - --hash=sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23 \ - --hash=sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2 \ - --hash=sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db \ - --hash=sha256:82d17e94d735c99621bf8ebf9995f870a6b3e6d14543b99e201ae046dfe7de70 \ - --hash=sha256:837bb6764be6919963ef41235fd56a6486b132ea64afe5fafb4cb279ac44f259 \ - --hash=sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9 \ - --hash=sha256:8c24f21fa2af4bb9f2c492a86fe0c34e6d2c63812a839590edaf177b7398f700 \ - --hash=sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003 \ - --hash=sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba \ - --hash=sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a \ - --hash=sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c \ - --hash=sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90 \ - --hash=sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690 \ - --hash=sha256:a05e6d6218461eb1b4771d973728f0133b2a4613a6779995df557f70794fd60f \ - --hash=sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840 \ - --hash=sha256:a4ae99c57668ca1e78597d8b06d5af837f377f340f4cce993b551b2d7731778d \ - --hash=sha256:a8c86881813a78a6f4508ef9daf9d4995b8ac2d147dcb1a450448941398091c9 \ - --hash=sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35 \ - --hash=sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd \ - --hash=sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a \ - --hash=sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea \ - --hash=sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1 \ - --hash=sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573 \ - --hash=sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09 \ - --hash=sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094 \ - --hash=sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78 \ - --hash=sha256:b8c0bd73aeac689beacd4e7667d48c299f61b959475cdbb91e7d3d88d27c56b9 \ - --hash=sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5 \ - --hash=sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9 \ - --hash=sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391 \ - --hash=sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847 \ - --hash=sha256:c7c517d74bea1a6afd39aa612fa025e6b8011982a0897768a2f7c8ab4ebb78a2 \ - --hash=sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c \ - --hash=sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2 \ - --hash=sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057 \ - --hash=sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20 \ - --hash=sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d \ - --hash=sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4 \ - --hash=sha256:e2d1a054f8f0a191004675755448d12be47fa9bebbcffa3cdf01db19f2d30a54 \ - --hash=sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171 \ - --hash=sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e \ - --hash=sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160 \ - --hash=sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b \ - --hash=sha256:f8346bfa098532bc1fb6c7ef06783e969d87a99dd1d2a5a18a892c1d7a643c58 \ - --hash=sha256:f83fa6cae3fff8e98691248c9320356971b59678a17f20656a9e59cd32cee6d8 \ - --hash=sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33 \ - --hash=sha256:fb2b1ecfef1e67897d336de3a0e3f52478182d6a47eda86cbd42504c5cbd009a \ - --hash=sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880 \ - --hash=sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca \ - --hash=sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b \ - --hash=sha256:fe3b385d996ee0822fd46528d9f0443b880d4d05528fd26a9119a54ec3f91c69 +zstandard==0.19.0 \ + --hash=sha256:04c298d381a3b6274b0a8001f0da0ec7819d052ad9c3b0863fe8c7f154061f76 \ + --hash=sha256:0fde1c56ec118940974e726c2a27e5b54e71e16c6f81d0b4722112b91d2d9009 \ + --hash=sha256:126aa8433773efad0871f624339c7984a9c43913952f77d5abeee7f95a0c0860 \ + --hash=sha256:1a4fb8b4ac6772e4d656103ccaf2e43e45bd16b5da324b963d58ef360d09eb73 \ + --hash=sha256:2e4812720582d0803e84aefa2ac48ce1e1e6e200ca3ce1ae2be6d410c1d637ae \ + --hash=sha256:2f01b27d0b453f07cbcff01405cdd007e71f5d6410eb01303a16ba19213e58e4 \ + --hash=sha256:31d12fcd942dd8dbf52ca5f6b1bbe287f44e5d551a081a983ff3ea2082867863 \ + --hash=sha256:3c927b6aa682c6d96225e1c797f4a5d0b9f777b327dea912b23471aaf5385376 \ + --hash=sha256:3d5bb598963ac1f1f5b72dd006adb46ca6203e4fb7269a5b6e1f99e85b07ad38 \ + --hash=sha256:401508efe02341ae681752a87e8ac9ef76df85ef1a238a7a21786a489d2c983d \ + --hash=sha256:4514b19abe6dbd36d6c5d75c54faca24b1ceb3999193c5b1f4b685abeabde3d0 \ + --hash=sha256:47dfa52bed3097c705451bafd56dac26535545a987b6759fa39da1602349d7ba \ + --hash=sha256:4fa496d2d674c6e9cffc561639d17009d29adee84a27cf1e12d3c9be14aa8feb \ + --hash=sha256:55a513ec67e85abd8b8b83af8813368036f03e2d29a50fc94033504918273980 \ + --hash=sha256:55b3187e0bed004533149882ef8c24e954321f3be81f8a9ceffe35099b82a0d0 \ + --hash=sha256:593f96718ad906e24d6534187fdade28b611f8ed06e27ba972ba48aecec45fc6 \ + --hash=sha256:5e21032efe673b887464667d09406bab6e16d96b09ad87e80859e3a20b6745b6 \ + --hash=sha256:60a86b7b2b1c300779167cf595e019e61afcc0e20c4838692983a921db9006ac \ + --hash=sha256:619f9bf37cdb4c3dc9d4120d2a1003f5db9446f3618a323219f408f6a9df6725 \ + --hash=sha256:660b91eca10ee1b44c47843894abe3e6cfd80e50c90dee3123befbf7ca486bd3 \ + --hash=sha256:67710d220af405f5ce22712fa741d85e8b3ada7a457ea419b038469ba379837c \ + --hash=sha256:6caed86cd47ae93915d9031dc04be5283c275e1a2af2ceff33932071f3eeff4d \ + --hash=sha256:6d2182e648e79213b3881998b30225b3f4b1f3e681f1c1eaf4cacf19bde1040d \ + --hash=sha256:72758c9f785831d9d744af282d54c3e0f9db34f7eae521c33798695464993da2 \ + --hash=sha256:74c2637d12eaacb503b0b06efdf55199a11b1d7c580bd3dd9dfe84cac97ef2f6 \ + --hash=sha256:755020d5aeb1b10bffd93d119e7709a2a7475b6ad79c8d5226cea3f76d152ce0 \ + --hash=sha256:7ccc4727300f223184520a6064c161a90b5d0283accd72d1455bcd85ec44dd0d \ + --hash=sha256:81ab21d03e3b0351847a86a0b298b297fde1e152752614138021d6d16a476ea6 \ + --hash=sha256:8371217dff635cfc0220db2720fc3ce728cd47e72bb7572cca035332823dbdfc \ + --hash=sha256:876567136b0359f6581ecd892bdb4ca03a0eead0265db73206c78cff03bcdb0f \ + --hash=sha256:879411d04068bd489db57dcf6b82ffad3c5fb2a1fdd30817c566d8b7bedee442 \ + --hash=sha256:898500957ae5e7f31b7271ace4e6f3625b38c0ac84e8cedde8de3a77a7fdae5e \ + --hash=sha256:8c9ca56345b0c5574db47560603de9d05f63cce5dfeb3a456eb60f3fec737ff2 \ + --hash=sha256:8ec2c146e10b59c376b6bc0369929647fcd95404a503a7aa0990f21c16462248 \ + --hash=sha256:8f7c68de4f362c1b2f426395fe4e05028c56d0782b2ec3ae18a5416eaf775576 \ + --hash=sha256:909bdd4e19ea437eb9b45d6695d722f6f0fd9d8f493e837d70f92062b9f39faf \ + --hash=sha256:9d97c713433087ba5cee61a3e8edb54029753d45a4288ad61a176fa4718033ce \ + --hash=sha256:a65e0119ad39e855427520f7829618f78eb2824aa05e63ff19b466080cd99210 \ + --hash=sha256:aa9087571729c968cd853d54b3f6e9d0ec61e45cd2c31e0eb8a0d4bdbbe6da2f \ + --hash=sha256:aef0889417eda2db000d791f9739f5cecb9ccdd45c98f82c6be531bdc67ff0f2 \ + --hash=sha256:b253d0c53c8ee12c3e53d181fb9ef6ce2cd9c41cbca1c56a535e4fc8ec41e241 \ + --hash=sha256:b80f6f6478f9d4ca26daee6c61584499493bf97950cfaa1a02b16bb5c2c17e70 \ + --hash=sha256:be6329b5ba18ec5d32dc26181e0148e423347ed936dda48bf49fb243895d1566 \ + --hash=sha256:c7560f622e3849cc8f3e999791a915addd08fafe80b47fcf3ffbda5b5151047c \ + --hash=sha256:d1a7a716bb04b1c3c4a707e38e2dee46ac544fff931e66d7ae944f3019fc55b8 \ + --hash=sha256:d63b04e16df8ea21dfcedbf5a60e11cbba9d835d44cb3cbff233cfd037a916d5 \ + --hash=sha256:d777d239036815e9b3a093fa9208ad314c040c26d7246617e70e23025b60083a \ + --hash=sha256:e892d3177380ec080550b56a7ffeab680af25575d291766bdd875147ba246a91 \ + --hash=sha256:e9c90a44470f2999779057aeaf33461cbd8bb59d8f15e983150d10bb260e16e0 \ + --hash=sha256:f097dda5d4f9b9b01b3c9fa2069f9c02929365f48f341feddf3d6b32510a2f93 \ + --hash=sha256:f4ebfe03cbae821ef994b2e58e4df6a087470cc522aca502614e82a143365d45 # via -r dependency_support/pip_requirements.in From 9ef6f9ca3e28e9bbee7bb83437b155fbbf369c5c Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Fri, 23 May 2025 12:29:45 +0200 Subject: [PATCH 80/85] Rename *.v to *.sv --- xls/modules/zstd/BUILD | 22 +++++++++---------- xls/modules/zstd/rtl/BUILD | 6 ++--- ...xls_fifo_wrapper.v => xls_fifo_wrapper.sv} | 0 ...zstd_dec_wrapper.v => zstd_dec_wrapper.sv} | 0 4 files changed, 14 insertions(+), 14 deletions(-) rename xls/modules/zstd/rtl/{xls_fifo_wrapper.v => xls_fifo_wrapper.sv} (100%) rename xls/modules/zstd/rtl/{zstd_dec_wrapper.v => zstd_dec_wrapper.sv} (100%) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index be61cb8c9f..44720fa8f5 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -434,7 +434,7 @@ verilog_library( name = "frame_header_dec_verilog_lib", srcs = [ ":frame_header_dec.v", - "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", ], tags = ["manual"], ) @@ -530,7 +530,7 @@ verilog_library( name = "block_header_dec_verilog_lib", srcs = [ ":block_header_dec.v", - "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", ], tags = ["manual"], ) @@ -609,7 +609,7 @@ verilog_library( name = "raw_block_dec_verilog_lib", srcs = [ ":raw_block_dec.v", - "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", ], tags = ["manual"], ) @@ -687,7 +687,7 @@ verilog_library( name = "rle_block_dec_verilog_lib", srcs = [ ":rle_block_dec.v", - "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", ], tags = ["manual"], ) @@ -765,7 +765,7 @@ verilog_library( name = "dec_mux_verilog_lib", srcs = [ ":dec_mux.v", - "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", ], tags = ["manual"], ) @@ -905,7 +905,7 @@ verilog_library( name = "sequence_executor_lib", srcs = [ ":sequence_executor.v", - "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", ], tags = ["manual"], ) @@ -985,7 +985,7 @@ verilog_library( name = "axi_csr_accessor_verilog_lib", srcs = [ ":axi_csr_accessor.v", - "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", ], tags = ["manual"], ) @@ -1062,7 +1062,7 @@ verilog_library( name = "csr_config_verilog_lib", srcs = [ ":csr_config.v", - "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", ], tags = ["manual"], ) @@ -1142,7 +1142,7 @@ verilog_library( name = "ram_rw_handler_verilog_lib", srcs = [ ":ram_rw_handler.v", - "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", ], tags = ["manual"], ) @@ -1247,7 +1247,7 @@ verilog_library( name = "fse_proba_freq_dec_lib", srcs = [ ":fse_proba_freq_dec.v", - "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", ], tags = ["manual"], ) @@ -1393,7 +1393,7 @@ verilog_library( name = "refilling_shift_buffer_internal_verilog_lib", srcs = [ ":refilling_shift_buffer_internal.v", - "//xls/modules/zstd/rtl:xls_fifo_wrapper.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", ], tags = ["manual"], ) diff --git a/xls/modules/zstd/rtl/BUILD b/xls/modules/zstd/rtl/BUILD index 9ca9c83ee3..4447877821 100644 --- a/xls/modules/zstd/rtl/BUILD +++ b/xls/modules/zstd/rtl/BUILD @@ -33,15 +33,15 @@ package( exports_files( [ - "zstd_dec_wrapper.v", - "xls_fifo_wrapper.v", + "zstd_dec_wrapper.sv", + "xls_fifo_wrapper.sv", ], ) verilog_library( name = "xls_fifo_wrapper_verilog_lib", srcs = [ - ":xls_fifo_wrapper.v", + ":xls_fifo_wrapper.sv", ], tags = ["manual"], ) diff --git a/xls/modules/zstd/rtl/xls_fifo_wrapper.v b/xls/modules/zstd/rtl/xls_fifo_wrapper.sv similarity index 100% rename from xls/modules/zstd/rtl/xls_fifo_wrapper.v rename to xls/modules/zstd/rtl/xls_fifo_wrapper.sv diff --git a/xls/modules/zstd/rtl/zstd_dec_wrapper.v b/xls/modules/zstd/rtl/zstd_dec_wrapper.sv similarity index 100% rename from xls/modules/zstd/rtl/zstd_dec_wrapper.v rename to xls/modules/zstd/rtl/zstd_dec_wrapper.sv From 7b3568ba296f60e9b7a3b061acb591eaca996eb7 Mon Sep 17 00:00:00 2001 From: Wojciech Sipak Date: Thu, 29 May 2025 14:19:21 +0200 Subject: [PATCH 81/85] remove data generation with decodecorpus Let's use predefined data first and only then add zstd as dependency. Signed-off-by: Wojciech Sipak --- dependency_support/pip_requirements.in | 1 - dependency_support/pip_requirements_lock.txt | 53 ------- xls/modules/zstd/BUILD | 55 ------- xls/modules/zstd/data_generator.cc | 111 ------------- xls/modules/zstd/data_generator.h | 37 ----- xls/modules/zstd/data_generator.py | 66 -------- xls/modules/zstd/zstd_frame_dslx.py | 155 ------------------- 7 files changed, 478 deletions(-) delete mode 100644 xls/modules/zstd/data_generator.cc delete mode 100644 xls/modules/zstd/data_generator.h delete mode 100644 xls/modules/zstd/data_generator.py delete mode 100644 xls/modules/zstd/zstd_frame_dslx.py diff --git a/dependency_support/pip_requirements.in b/dependency_support/pip_requirements.in index 6ec69c187a..804306558e 100644 --- a/dependency_support/pip_requirements.in +++ b/dependency_support/pip_requirements.in @@ -13,7 +13,6 @@ pyyaml==6.0.1 # We build most of z3 ourselves but building python is really complicated. Just # use pypi version z3-solver==4.14.0.0 -zstandard==0.19.0 # Note: numpy and scipy version availability seems to differ between Ubuntu # versions that we want to support (e.g. 18.04 vs 20.04), so we accept a diff --git a/dependency_support/pip_requirements_lock.txt b/dependency_support/pip_requirements_lock.txt index cadc770f13..dc895c7f19 100644 --- a/dependency_support/pip_requirements_lock.txt +++ b/dependency_support/pip_requirements_lock.txt @@ -541,56 +541,3 @@ z3-solver==4.14.0.0 \ --hash=sha256:c10f899c6a876e3a50e9b2c4927604c2c3da3cca672b8ed3b7db1bc97259e47f \ --hash=sha256:e6ae32bc1cf4d25b96f790755d790a23118e8a09b9b6060e32238fe6ff43606d # via -r dependency_support/pip_requirements.in -zstandard==0.19.0 \ - --hash=sha256:04c298d381a3b6274b0a8001f0da0ec7819d052ad9c3b0863fe8c7f154061f76 \ - --hash=sha256:0fde1c56ec118940974e726c2a27e5b54e71e16c6f81d0b4722112b91d2d9009 \ - --hash=sha256:126aa8433773efad0871f624339c7984a9c43913952f77d5abeee7f95a0c0860 \ - --hash=sha256:1a4fb8b4ac6772e4d656103ccaf2e43e45bd16b5da324b963d58ef360d09eb73 \ - --hash=sha256:2e4812720582d0803e84aefa2ac48ce1e1e6e200ca3ce1ae2be6d410c1d637ae \ - --hash=sha256:2f01b27d0b453f07cbcff01405cdd007e71f5d6410eb01303a16ba19213e58e4 \ - --hash=sha256:31d12fcd942dd8dbf52ca5f6b1bbe287f44e5d551a081a983ff3ea2082867863 \ - --hash=sha256:3c927b6aa682c6d96225e1c797f4a5d0b9f777b327dea912b23471aaf5385376 \ - --hash=sha256:3d5bb598963ac1f1f5b72dd006adb46ca6203e4fb7269a5b6e1f99e85b07ad38 \ - --hash=sha256:401508efe02341ae681752a87e8ac9ef76df85ef1a238a7a21786a489d2c983d \ - --hash=sha256:4514b19abe6dbd36d6c5d75c54faca24b1ceb3999193c5b1f4b685abeabde3d0 \ - --hash=sha256:47dfa52bed3097c705451bafd56dac26535545a987b6759fa39da1602349d7ba \ - --hash=sha256:4fa496d2d674c6e9cffc561639d17009d29adee84a27cf1e12d3c9be14aa8feb \ - --hash=sha256:55a513ec67e85abd8b8b83af8813368036f03e2d29a50fc94033504918273980 \ - --hash=sha256:55b3187e0bed004533149882ef8c24e954321f3be81f8a9ceffe35099b82a0d0 \ - --hash=sha256:593f96718ad906e24d6534187fdade28b611f8ed06e27ba972ba48aecec45fc6 \ - --hash=sha256:5e21032efe673b887464667d09406bab6e16d96b09ad87e80859e3a20b6745b6 \ - --hash=sha256:60a86b7b2b1c300779167cf595e019e61afcc0e20c4838692983a921db9006ac \ - --hash=sha256:619f9bf37cdb4c3dc9d4120d2a1003f5db9446f3618a323219f408f6a9df6725 \ - --hash=sha256:660b91eca10ee1b44c47843894abe3e6cfd80e50c90dee3123befbf7ca486bd3 \ - --hash=sha256:67710d220af405f5ce22712fa741d85e8b3ada7a457ea419b038469ba379837c \ - --hash=sha256:6caed86cd47ae93915d9031dc04be5283c275e1a2af2ceff33932071f3eeff4d \ - --hash=sha256:6d2182e648e79213b3881998b30225b3f4b1f3e681f1c1eaf4cacf19bde1040d \ - --hash=sha256:72758c9f785831d9d744af282d54c3e0f9db34f7eae521c33798695464993da2 \ - --hash=sha256:74c2637d12eaacb503b0b06efdf55199a11b1d7c580bd3dd9dfe84cac97ef2f6 \ - --hash=sha256:755020d5aeb1b10bffd93d119e7709a2a7475b6ad79c8d5226cea3f76d152ce0 \ - --hash=sha256:7ccc4727300f223184520a6064c161a90b5d0283accd72d1455bcd85ec44dd0d \ - --hash=sha256:81ab21d03e3b0351847a86a0b298b297fde1e152752614138021d6d16a476ea6 \ - --hash=sha256:8371217dff635cfc0220db2720fc3ce728cd47e72bb7572cca035332823dbdfc \ - --hash=sha256:876567136b0359f6581ecd892bdb4ca03a0eead0265db73206c78cff03bcdb0f \ - --hash=sha256:879411d04068bd489db57dcf6b82ffad3c5fb2a1fdd30817c566d8b7bedee442 \ - --hash=sha256:898500957ae5e7f31b7271ace4e6f3625b38c0ac84e8cedde8de3a77a7fdae5e \ - --hash=sha256:8c9ca56345b0c5574db47560603de9d05f63cce5dfeb3a456eb60f3fec737ff2 \ - --hash=sha256:8ec2c146e10b59c376b6bc0369929647fcd95404a503a7aa0990f21c16462248 \ - --hash=sha256:8f7c68de4f362c1b2f426395fe4e05028c56d0782b2ec3ae18a5416eaf775576 \ - --hash=sha256:909bdd4e19ea437eb9b45d6695d722f6f0fd9d8f493e837d70f92062b9f39faf \ - --hash=sha256:9d97c713433087ba5cee61a3e8edb54029753d45a4288ad61a176fa4718033ce \ - --hash=sha256:a65e0119ad39e855427520f7829618f78eb2824aa05e63ff19b466080cd99210 \ - --hash=sha256:aa9087571729c968cd853d54b3f6e9d0ec61e45cd2c31e0eb8a0d4bdbbe6da2f \ - --hash=sha256:aef0889417eda2db000d791f9739f5cecb9ccdd45c98f82c6be531bdc67ff0f2 \ - --hash=sha256:b253d0c53c8ee12c3e53d181fb9ef6ce2cd9c41cbca1c56a535e4fc8ec41e241 \ - --hash=sha256:b80f6f6478f9d4ca26daee6c61584499493bf97950cfaa1a02b16bb5c2c17e70 \ - --hash=sha256:be6329b5ba18ec5d32dc26181e0148e423347ed936dda48bf49fb243895d1566 \ - --hash=sha256:c7560f622e3849cc8f3e999791a915addd08fafe80b47fcf3ffbda5b5151047c \ - --hash=sha256:d1a7a716bb04b1c3c4a707e38e2dee46ac544fff931e66d7ae944f3019fc55b8 \ - --hash=sha256:d63b04e16df8ea21dfcedbf5a60e11cbba9d835d44cb3cbff233cfd037a916d5 \ - --hash=sha256:d777d239036815e9b3a093fa9208ad314c040c26d7246617e70e23025b60083a \ - --hash=sha256:e892d3177380ec080550b56a7ffeab680af25575d291766bdd875147ba246a91 \ - --hash=sha256:e9c90a44470f2999779057aeaf33461cbd8bb59d8f15e983150d10bb260e16e0 \ - --hash=sha256:f097dda5d4f9b9b01b3c9fa2069f9c02929365f48f341feddf3d6b32510a2f93 \ - --hash=sha256:f4ebfe03cbae821ef994b2e58e4df6a087470cc522aca502614e82a143365d45 - # via -r dependency_support/pip_requirements.in diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 44720fa8f5..f8c8e56cab 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -17,7 +17,6 @@ load("@rules_hdl//place_and_route:build_defs.bzl", "place_and_route") load("@rules_hdl//synthesis:build_defs.bzl", "benchmark_synth", "synthesize_rtl") load("@rules_hdl//verilog:providers.bzl", "verilog_library") -load("@xls_pip_deps//:requirements.bzl", "requirement") load( "//xls/build_rules:xls_build_defs.bzl", "xls_benchmark_ir", @@ -347,36 +346,6 @@ xls_benchmark_verilog( verilog_target = "shift_buffer_verilog", ) -cc_library( - name = "data_generator", - srcs = ["data_generator.cc"], - hdrs = ["data_generator.h"], - data = [ - "@zstd//:decodecorpus", - ], - tags = ["manual"], - deps = [ - "//xls/common:subprocess", - "//xls/common/file:filesystem", - "//xls/common/file:get_runfile_path", - "//xls/common/status:status_macros", - "@com_google_absl//absl/algorithm:container", - "@com_google_absl//absl/status:statusor", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/time", - "@com_google_absl//absl/types:span", - ], -) - -py_library( - name = "data_generator_py", - srcs = ["data_generator.py"], - deps = [ - "//xls/common:runfiles", - "@zstd//:decodecorpus", - ], -) - xls_dslx_library( name = "common_dslx", srcs = [ @@ -1452,29 +1421,6 @@ xls_dslx_test( tags = ["manual"], ) -py_binary( - name = "zstd_test_frames_generator", - srcs = ["zstd_frame_dslx.py"], - imports = ["."], - main = "zstd_frame_dslx.py", - tags = ["manual"], - visibility = ["//xls:xls_users"], - deps = [ - requirement("zstandard"), - "//xls/common:runfiles", - ":data_generator_py", - "@com_google_protobuf//:protobuf_python", - ], -) - -genrule( - name = "zstd_test_frames_generate", - srcs = [], - outs = ["zstd_frame_testcases.x"], - cmd = "$(location :zstd_test_frames_generator) --seed 36 -n 1 --btype COMPRESSED -o $@", - tools = [":zstd_test_frames_generator"], -) - xls_dslx_library( name = "ram_merge_dslx", srcs = ["ram_merge.x"], @@ -1522,7 +1468,6 @@ xls_dslx_test( "data/comp_frame_huffman_fse.x", "zstd_dec.x", "zstd_dec_test.x", - "zstd_frame_testcases.x", ], tags = ["manual"], deps = zstd_dec_deps, diff --git a/xls/modules/zstd/data_generator.cc b/xls/modules/zstd/data_generator.cc deleted file mode 100644 index 98b1eb4dba..0000000000 --- a/xls/modules/zstd/data_generator.cc +++ /dev/null @@ -1,111 +0,0 @@ -// 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. -// - -#include "xls/modules/zstd/data_generator.h" - -#include -#include -#include -#include // NOLINT -#include -#include -#include -#include - -#include "absl/algorithm/container.h" -#include "absl/status/statusor.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_join.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" - -namespace xls::zstd { - -static std::string CreateNameForGeneratedFile( - absl::Span args, std::string_view ext, - std::optional prefix) { - std::string output; - - if (prefix.has_value()) { - absl::StrAppend(&output, prefix.value(), "_"); - } - absl::StrAppend(&output, absl::StrJoin(args, "")); - - std::erase(output, ' '); - absl::c_replace(output, '-', '_'); - absl::c_replace(output, '=', '_'); - - output += ext; - - return output; -} - -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/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)); - XLS_ASSIGN_OR_RETURN(auto raw_data, xls::GetFileContents(output_path)); - std::remove(output_path.c_str()); - return std::vector(raw_data.begin(), raw_data.end()); -} - -absl::StatusOr> GenerateFrame(int seed, BlockType btype) { - std::vector args; - args.push_back("-s" + std::to_string(seed)); - if (btype != BlockType::RANDOM) { - args.push_back("--block-type=" + std::to_string(static_cast(btype))); - } - if (btype == BlockType::RLE) { - args.push_back("--content-size"); - } - // Test payloads up to 16KB - args.push_back("--max-content-size-log=14"); - std::filesystem::path output_path = - std::filesystem::temp_directory_path() / - std::filesystem::path( - CreateNameForGeneratedFile(absl::MakeSpan(args), ".zstd", "frame")); - args.push_back("-p" + std::string(output_path)); - - XLS_ASSIGN_OR_RETURN(auto result, CallDecodecorpus(args)); - XLS_ASSIGN_OR_RETURN(auto raw_data, xls::GetFileContents(output_path)); - std::remove(output_path.c_str()); - return std::vector(raw_data.begin(), raw_data.end()); -} - -} // namespace xls::zstd diff --git a/xls/modules/zstd/data_generator.h b/xls/modules/zstd/data_generator.h deleted file mode 100644 index a3967a1317..0000000000 --- a/xls/modules/zstd/data_generator.h +++ /dev/null @@ -1,37 +0,0 @@ -// 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. -// -#ifndef XLS_MODULES_ZSTD_DATA_GENERATOR_H_ -#define XLS_MODULES_ZSTD_DATA_GENERATOR_H_ - -#include -#include - -#include "absl/status/statusor.h" - -namespace xls::zstd { - -enum class BlockType { - RAW, - RLE, - COMPRESSED, - RANDOM, -}; - -absl::StatusOr> GenerateFrameHeader(int seed, bool magic); -absl::StatusOr> GenerateFrame(int seed, BlockType btype); - -} // namespace xls::zstd - -#endif // XLS_MODULES_ZSTD_DATA_GENERATOR_H_ diff --git a/xls/modules/zstd/data_generator.py b/xls/modules/zstd/data_generator.py deleted file mode 100644 index 46a74acbfc..0000000000 --- a/xls/modules/zstd/data_generator.py +++ /dev/null @@ -1,66 +0,0 @@ -# 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. - -"""Module for generating and decompressing test frames.""" - -import pathlib -import enum - -from xls.common import runfiles -import subprocess -import zstandard - -class BlockType(enum.Enum): - """Enum encoding of ZSTD block types.""" - - RAW = 0 - RLE = 1 - COMPRESSED = 2 - RANDOM = 3 - - def __str__(self): - return self.name - - @staticmethod - def from_string(s): - try: - return BlockType[s] - except KeyError as e: - raise ValueError(str(e)) from e - -def CallDecodecorpus(args): - decodecorpus = pathlib.Path( - runfiles.get_path("decodecorpus", repository = "zstd") - ) - cmd = args - cmd.insert(0, str(decodecorpus)) - cmd_concat = " ".join(cmd) - subprocess.run(cmd_concat, shell=True, check=True) - -def DecompressFrame(data): - dctx = zstandard.ZstdDecompressor() - return dctx.decompress(data) - -def GenerateFrame(seed, btype, output_path): - args = [] - args.append("-s" + str(seed)) - if (btype != BlockType.RANDOM): - args.append("--block-type=" + str(btype.value)) - args.append("--content-size") - # Test payloads up to 16KB - args.append("--max-content-size-log=14") - args.append("-p" + output_path) - args.append("-vvvvvvv") - - CallDecodecorpus(args) diff --git a/xls/modules/zstd/zstd_frame_dslx.py b/xls/modules/zstd/zstd_frame_dslx.py deleted file mode 100644 index 5304e07f45..0000000000 --- a/xls/modules/zstd/zstd_frame_dslx.py +++ /dev/null @@ -1,155 +0,0 @@ -# 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. - -"""ZSTD test frame generator for DSLX tests. - -This module interacts with the data_generator module and underlying -Decodecorpus in order to generate inputs and expected outputs for the ZSTD -Decoder tests in DSLX. - -It generates the ZSTD frame and then decodes it with the reference ZSTD -library. Both the encoded frame and decoded data are written to a DSLX file by -converting raw bytes of the frame and decoded data into DSLX structures. - -Resulting file can be included in the ZSTD Decoder DSLX test and used as the -inputs and expected output for the testbench. -""" - -import argparse -import math -import random -import tempfile -import pathlib - -from xls.modules.zstd import data_generator - - -def GenerateTestData(seed, btype): - with tempfile.NamedTemporaryFile() as tmp: - data_generator.GenerateFrame(seed, btype, tmp.name) - tmp.seek(0) - return tmp.read() - - -def Bytes2DSLX(frames, bytes_per_word, array_name): - """Converts a list of byte frames to a DSLX constant array declaration. - - Args: - frames (List[bytes]): List of byte sequences representing frames. - bytes_per_word (int): Number of bytes per word in the output format. - array_name (str): Name of the resulting DSLX constant array. - - Returns: - str: A string containing the DSLX constant array declaration. - """ - frames_hex = [] - maxlen = max(len(frame) for frame in frames) - maxlen_size = math.ceil(maxlen / bytes_per_word) - bits_per_word = bytes_per_word * 8 - for i, frame in enumerate(frames): - frame_hex = [] - for i in range(0, len(frame), bytes_per_word): - # reverse byte order to make them little endian - word = bytes(reversed(frame[i : i + bytes_per_word])).hex() - frame_hex.append(f"uN[{bits_per_word}]:0x{word}") - - array_length = len(frame_hex) - if len(frame) < maxlen: - frame_hex += [f"uN[{bits_per_word}]:0x0", "..."] - - frame_array = ( - f"DataArray<{bits_per_word}, {maxlen_size}>{{\n" - f" length: u32:{len(frame)},\n" - f" array_length: u32:{array_length},\n" - f" data: uN[{bits_per_word}][{maxlen_size}]:[{', '.join(frame_hex)}]\n" - f"}}" - ) - frames_hex.append(frame_array) - - frames_str = ",\n".join(frames_hex) - frames_array = ( - f"pub const {array_name}:DataArray<\n" - f" u32:{bits_per_word},\n" - f" u32:{maxlen_size}\n" - f">[{len(frames_hex)}] = [{frames_str}];\n" - ) - return frames_array - - -def GenerateDataStruct(): - return ( - "pub struct DataArray{\n" - " data: uN[BITS_PER_WORD][LENGTH],\n" - " length: u32,\n" - " array_length: u32\n" - "}\n" - ) - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument( - "-n", help="Number of testcases to generate", type=int, default=1 - ) - parser.add_argument( - "--seed", help="Seed for the testcases generator", type=int, default=0 - ) - parser.add_argument( - "--btype", - help=( - "Block types allowed in the generated testcases. If multiple block types " - "are supplied, generated testcases will cycle through them" - ), - type=data_generator.BlockType.from_string, - choices=list(data_generator.BlockType), - default=data_generator.BlockType.RANDOM, - nargs="+", - ) - parser.add_argument( - "-o", - "--output", - help="Filename of the DSLX output file", - type=pathlib.Path, - default=pathlib.Path("frames_test_data.x"), - ) - parser.add_argument( - "--bytes-per-word", - help="Width of a word in memory, in bytes", - type=int, - default=8, - ) - args = parser.parse_args() - - random.seed(args.seed) - byte_frames = [ - GenerateTestData(random.randrange(2**32), args.btype[i % len(args.btype)]) - for i in range(args.n) - ] - with open(args.output, "w") as dslx_output: - dslx_output.write(GenerateDataStruct()) - - dslx_frames = Bytes2DSLX(byte_frames, args.bytes_per_word, "FRAMES") - dslx_output.write(dslx_frames) - - byte_frames_decompressed = list( - map(data_generator.DecompressFrame, byte_frames) - ) - dslx_frames_decompressed = Bytes2DSLX( - byte_frames_decompressed, args.bytes_per_word, "DECOMPRESSED_FRAMES" - ) - dslx_output.write(dslx_frames_decompressed) - - -if __name__ == "__main__": - main() From 3880499d0cc551024b80a4760b1d57e23a9f8be7 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Fri, 30 May 2025 10:54:25 +0200 Subject: [PATCH 82/85] Fix type checking in huffman_prescan.x Signed-off-by: Robert Winkler --- xls/modules/zstd/huffman_literals_dec.x | 8 ++------ xls/modules/zstd/huffman_prescan.x | 27 ++++++++++++------------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/xls/modules/zstd/huffman_literals_dec.x b/xls/modules/zstd/huffman_literals_dec.x index b60cadedad..22f328688d 100644 --- a/xls/modules/zstd/huffman_literals_dec.x +++ b/xls/modules/zstd/huffman_literals_dec.x @@ -29,10 +29,6 @@ import xls.modules.zstd.memory.axi_ram_reader; import xls.modules.zstd.memory.mem_reader as mem_reader; import xls.examples.ram; -pub fn WeightPreScanMetaDataSize() -> u32 { - prescan::WeightPreScanMetaDataSize() -} - pub type HuffmanLiteralsDecoderReq = ctrl::HuffmanControlAndSequenceCtrl; pub type HuffmanLiteralsDecoderResp = ctrl::HuffmanControlAndSequenceResp; pub type HuffmanLiteralsDecoderStatus = ctrl::HuffmanControlAndSequenceStatus; @@ -45,7 +41,7 @@ pub const WEIGHTS_NUM_PARTITIONS = ram::num_partitions(WEIGHTS_PARTITION_WORD_SI // pub const WEIGHTS_NUM_PARTITIONS: u32 = u32:1; pub const PRESCAN_ADDR_WIDTH: u32 = prescan::RAM_ADDR_WIDTH; -pub const PRESCAN_DATA_WIDTH: u32 = prescan::WeightPreScanMetaDataSize(); +pub const PRESCAN_DATA_WIDTH: u32 = prescan::WEIGHT_PRESCAN_METADATA_SIZE; pub const PRESCAN_PARTITION_WORD_SIZE: u32 = PRESCAN_DATA_WIDTH; pub const PRESCAN_NUM_PARTITIONS = ram::num_partitions(PRESCAN_PARTITION_WORD_SIZE, PRESCAN_DATA_WIDTH); @@ -498,7 +494,7 @@ pub const TEST_PRESCAN_RAM_ADDR_WIDTH = PRESCAN_ADDR_WIDTH; pub const TEST_PRESCAN_RAM_DATA_WIDTH = PRESCAN_DATA_WIDTH; pub const TEST_PRESCAN_RAM_NUM_PARTITIONS = PRESCAN_NUM_PARTITIONS; pub const TEST_PRESCAN_RAM_SIZE = prescan::RAM_SIZE; -pub const TEST_PRESCAN_WORD_PARTITION_SIZE = prescan::WeightPreScanMetaDataSize(); +pub const TEST_PRESCAN_WORD_PARTITION_SIZE = prescan::WEIGHT_PRESCAN_METADATA_SIZE; const TEST_WEIGHTS_DPD_RAM_DATA_W = u32:16; const TEST_WEIGHTS_DPD_RAM_SIZE = u32:256; diff --git a/xls/modules/zstd/huffman_prescan.x b/xls/modules/zstd/huffman_prescan.x index 47487a45c6..6a8338cd27 100644 --- a/xls/modules/zstd/huffman_prescan.x +++ b/xls/modules/zstd/huffman_prescan.x @@ -53,14 +53,13 @@ const COUNTER_WIDTH = hcommon::COUNTER_WIDTH; type WeightPreScanMetaData = hcommon::WeightPreScanMetaData; type WeightPreScanOutput = hcommon::WeightPreScanOutput; -pub fn WeightPreScanMetaDataSize() -> u32 { +pub const WEIGHT_PRESCAN_METADATA_SIZE = (COUNTER_WIDTH as u32) * (PARALLEL_ACCESS_WIDTH as u32) + (MAX_WEIGHT as u32) + u32:1 + - (COUNTER_WIDTH as u32) * (MAX_WEIGHT as u32 + u32:1) -} + (COUNTER_WIDTH as u32) * (MAX_WEIGHT as u32 + u32:1); fn InternalStructToBits< - BITS: u32 = {WeightPreScanMetaDataSize()}, + BITS: u32 = {WEIGHT_PRESCAN_METADATA_SIZE}, OCCURANCE_WIDTH: u32 ={COUNTER_WIDTH * PARALLEL_ACCESS_WIDTH}, > (internalStruct: WeightPreScanMetaData) -> bits[BITS] { (internalStruct.weights_count as bits[COUNTER_WIDTH * (MAX_WEIGHT + u32:1)] ++ @@ -69,7 +68,7 @@ fn InternalStructToBits< } fn BitsToInternalStruct< - BITS: u32 = {WeightPreScanMetaDataSize()}, + BITS: u32 = {WEIGHT_PRESCAN_METADATA_SIZE}, OCCURANCE_WIDTH: u32 ={COUNTER_WIDTH * PARALLEL_ACCESS_WIDTH}, > (rawBits: bits[BITS]) -> WeightPreScanMetaData { WeightPreScanMetaData { @@ -80,7 +79,7 @@ fn BitsToInternalStruct< } #[quickcheck(test_count=50000)] -fn bits_to_struct_to_bits_qtest(x: bits[WeightPreScanMetaDataSize()]) -> bool { +fn bits_to_struct_to_bits_qtest(x: bits[WEIGHT_PRESCAN_METADATA_SIZE]) -> bool { x == InternalStructToBits(BitsToInternalStruct(x)) } @@ -131,11 +130,11 @@ pub proc WeightPreScan type InternalRamAddr = uN[RAM_ADDR_WIDTH]; type InternalData = WeightPreScanMetaData; - type InternalRamData = bits[WeightPreScanMetaDataSize()]; + type InternalRamData = bits[WEIGHT_PRESCAN_METADATA_SIZE]; type InternalReadReq = ram::ReadReq; - type InternalReadResp = ram::ReadResp<{WeightPreScanMetaDataSize()}>; - type InternalWriteReq = ram::WriteReq; + type InternalReadResp = ram::ReadResp; + type InternalWriteReq = ram::WriteReq; type InternalWriteResp = ram::WriteResp; start_r: chan in; @@ -328,12 +327,12 @@ proc Prescan_test{ type ReadReq = ram::ReadReq; type ReadResp = ram::ReadResp; type WriteReq = ram::WriteReq; - type WriteResp = ram::WriteResp<>; + type WriteResp = ram::WriteResp; type InternalReadReq = ram::ReadReq; - type InternalReadResp = ram::ReadResp<{WeightPreScanMetaDataSize()}>; - type InternalWriteReq = ram::WriteReq; - type InternalWriteResp = ram::WriteResp<>; + type InternalReadResp = ram::ReadResp; + type InternalWriteReq = ram::WriteReq; + type InternalWriteResp = ram::WriteResp; terminator: chan out; external_ram_req: chan out; @@ -357,7 +356,7 @@ proc Prescan_test{ let (RAMInternalWriteResp_s, RAMInternalWriteResp_r) = chan("Internal_write_channel_resp"); let (RAMInternalReadReq_s, RAMInternalReadReq_r) = chan("Internal_read_channel_req"); let (RAMInternalReadResp_s, RAMInternalReadResp_r) = chan("Internal_read_channel_resp"); - spawn ram::RamModel<{WeightPreScanMetaDataSize()}, RAM_SIZE, {WeightPreScanMetaDataSize()}>( + spawn ram::RamModel( RAMInternalReadReq_r, RAMInternalReadResp_s, RAMInternalWriteReq_r, RAMInternalWriteResp_s ); From 8f32ca9513d883affc75161a555a397954dffc5f Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 3 Jun 2025 17:40:02 +0200 Subject: [PATCH 83/85] Mark long DSLX tests as local Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index f8c8e56cab..001b36234b 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1418,7 +1418,10 @@ xls_dslx_test( name = "comp_block_dec_dslx_test", size = "enormous", library = ":comp_block_dec_dslx", - tags = ["manual"], + tags = [ + "local", + "manual", + ], ) xls_dslx_library( @@ -1469,7 +1472,10 @@ xls_dslx_test( "zstd_dec.x", "zstd_dec_test.x", ], - tags = ["manual"], + tags = [ + "local", + "manual", + ], deps = zstd_dec_deps, ) @@ -2222,7 +2228,10 @@ xls_dslx_library( xls_dslx_test( name = "sequence_dec_dslx_test", library = ":sequence_dec_dslx", - tags = ["manual"], + tags = [ + "local", + "manual", + ], ) xls_dslx_verilog( @@ -3208,7 +3217,10 @@ xls_dslx_test( name = "huffman_weights_dec_dslx_test", size = "large", library = ":huffman_weights_dec_dslx", - tags = ["manual"], + tags = [ + "local", + "manual", + ], ) huffman_weights_dec_codegen_args = common_codegen_args | { From 2838baa29f5505c17b033614716851a429561f23 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Mon, 2 Jun 2025 17:05:23 +0200 Subject: [PATCH 84/85] Add suffix to long P&R targets The CI setup for the zstd module uses a bazel query to find groups of targets that share a common suffix. By appending `_skip` to a target name, the query no longer matches it, effectively excluding that target from the CI run. Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 001b36234b..f22eaac8ac 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -2687,8 +2687,9 @@ benchmark_synth( tags = ["manual"], ) +# TODO: Remove _skip suffix after fixing long P&R time. Used now to skip this target in CI. place_and_route( - name = "literals_decoder_place_and_route", + name = "literals_decoder_place_and_route_skip", clock_period = "750", core_padding_microns = 2, min_pin_distance = "0.5", @@ -3024,8 +3025,9 @@ benchmark_synth( tags = ["manual"], ) +# TODO: Remove _skip suffix after fixing long P&R time. Used now to skip this target in CI. place_and_route( - name = "huffman_data_preprocessor_place_and_route", + name = "huffman_data_preprocessor_place_and_route_skip", clock_period = "750", core_padding_microns = 2, min_pin_distance = "0.5", @@ -3101,8 +3103,9 @@ benchmark_synth( tags = ["manual"], ) +# TODO: Remove _skip suffix after fixing long P&R time. Used now to skip this target in CI. place_and_route( - name = "huffman_decoder_place_and_route", + name = "huffman_decoder_place_and_route_skip", clock_period = "750", core_padding_microns = 2, min_pin_distance = "0.5", @@ -3360,8 +3363,9 @@ benchmark_synth( tags = ["manual"], ) +# TODO: Remove _skip suffix after fixing long P&R time. Used now to skip this target in CI. place_and_route( - name = "huffman_literals_dec_place_and_route", + name = "huffman_literals_dec_place_and_route_skip", clock_period = CLOCK_PERIOD_PS, core_padding_microns = 2, min_pin_distance = "0.5", From 484cc69a32a6653b7001a8aabc884e839eb09561 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Mon, 9 Jun 2025 23:35:43 +0200 Subject: [PATCH 85/85] Make filter rules more strict Make Bazel queries stricter so that target names must end exactly with the specified suffix, without allowing any additional characters after it. Signed-off-by: Robert Winkler --- .github/workflows/modules-zstd.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/modules-zstd.yml b/.github/workflows/modules-zstd.yml index f82b5cd010..4272e4b91b 100644 --- a/.github/workflows/modules-zstd.yml +++ b/.github/workflows/modules-zstd.yml @@ -45,17 +45,17 @@ jobs: - name: Test ZSTD Module - DSLX Tests (opt) if: ${{ !cancelled() }} run: | - bazel test -c opt --test_output=errors -- $(bazel query 'filter(".*_dslx_test", kind(rule, //xls/modules/zstd/...))') + bazel test -c opt --test_output=errors -- $(bazel query 'filter("_dslx_test$", kind(rule, //xls/modules/zstd/...))') - name: Build ZSTD verilog targets (opt) if: ${{ !cancelled() }} run: | - bazel build -c opt -- $(bazel query 'filter(".*_verilog", kind(rule, //xls/modules/zstd/...))') + 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: | - for target in $(bazel query 'filter(".*_ir_benchmark$", kind(rule, //xls/modules/zstd/...))'); + for target in $(bazel query 'filter("_ir_benchmark$", kind(rule, //xls/modules/zstd/...))'); do echo "running $target"; bazel run -c opt $target -- --logtostderr; @@ -64,7 +64,7 @@ jobs: - name: Build and run synthesis benchmarks of the ZSTD module (opt) if: ${{ !cancelled() }} run: | - for target in $(bazel query 'filter(".*_benchmark_synth$", kind(rule, //xls/modules/zstd/...))'); + for target in $(bazel query 'filter("_benchmark_synth$", kind(rule, //xls/modules/zstd/...))'); do echo "running $target"; bazel run -c opt $target -- --logtostderr; @@ -73,4 +73,4 @@ jobs: - 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/...))') + bazel build -c opt -- $(bazel query 'filter("_place_and_route$", kind(rule, //xls/modules/zstd/...))')

      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

    2. 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