diff --git a/xls/modules/dbe/BUILD b/xls/modules/dbe/BUILD new file mode 100644 index 0000000000..682b1a3c2c --- /dev/null +++ b/xls/modules/dbe/BUILD @@ -0,0 +1,155 @@ +# Copyright 2023 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Build rules for XLS DBE/LZ4 algorithm implementation. + +load( + "//xls/build_rules:xls_build_defs.bzl", + "xls_dslx_library", + "xls_dslx_ir", + "xls_ir_opt_ir", + "xls_ir_verilog", + "xls_dslx_test", +) + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//xls:xls_users"], + licenses = ["notice"], +) + +# --------------------------------------------------------------------------- +# Common +# --------------------------------------------------------------------------- + +xls_dslx_library( + name = "dbe_common_dslx", + srcs = [ + "common.x", + "common_test.x" + ], +) + +# --------------------------------------------------------------------------- +# LZ4 decoder +# --------------------------------------------------------------------------- + +xls_dslx_library( + name = "dbe_lz4_decoder_dslx", + srcs = [ + "lz4_decoder.x" + ], + deps = [ + ":dbe_common_dslx", + "//xls/examples:ram_dslx", + ], +) + +xls_dslx_test( + name = "dbe_lz4_decoder_dslx_test", + dslx_test_args = { + "compare": "none", + }, + library = "dbe_lz4_decoder_dslx", +) + +xls_dslx_ir( + name = "dbe_lz4_decoder_ir", + dslx_top = "decoder", + library = "dbe_lz4_decoder_dslx", + ir_file = "dbe_lz4_decoder_ir.ir", +) + +xls_ir_opt_ir( + name = "dbe_lz4_decoder_opt_ir", + src = "dbe_lz4_decoder_ir.ir", + top = "__lz4_decoder__decoder__decoder_base_0__16_16_8_next", + ram_rewrites = [ + ":lz4_decoder_ram1r1w_rewrites.textproto", + ], +) + +xls_ir_verilog( + name = "dbe_lz4_decoder_verilog", + src = "dbe_lz4_decoder_opt_ir.opt.ir", + verilog_file = "dbe_lz4_decoder.v", + codegen_args = { + "module_name": "dbe_lz4_decoder", + "delay_model": "unit", + "pipeline_stages": "3", + "reset": "rst", + "use_system_verilog": "false", + "streaming_channel_data_suffix": "_data", + "ram_configurations": "ram_hb:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_comp}".format( + rd_req = "ram_hb_read_req", + rd_resp = "ram_hb_read_resp", + wr_req = "ram_hb_write_req", + wr_comp = "ram_hb_write_completion", + ), + }, +) + +# --------------------------------------------------------------------------- +# LZ4 encoder +# --------------------------------------------------------------------------- + +xls_dslx_library( + name = "dbe_lz4_encoder_dslx", + srcs = [ + "lz4_encoder.x" + ], + deps = [ + ":dbe_common_dslx", + "//xls/examples:ram_dslx", + # decoder is referenced by test code + ":dbe_lz4_decoder_dslx", + ], +) + +xls_dslx_test( + name = "dbe_lz4_encoder_dslx_test", + dslx_test_args = { + "compare": "none", + }, + library = "dbe_lz4_encoder_dslx", +) + +#8K hash specialization +xls_dslx_ir( + name = "dbe_lz4_encoder_8k_ir", + dslx_top = "encoder_8k", + library = "dbe_lz4_encoder_dslx", + ir_file = "dbe_lz4_encoder_8k_ir.ir", +) + +xls_ir_opt_ir( + name = "dbe_lz4_encoder_8k_opt_ir", + src = "dbe_lz4_encoder_8k_ir.ir", + top = "__lz4_encoder__encoder_8k__encoder_base_0__16_3_13_12_13_65536_8192_4_16_8_next", +) + +xls_ir_verilog( + name = "dbe_lz4_encoder_8k_verilog", + src = "dbe_lz4_encoder_8k_opt_ir.opt.ir", + verilog_file = "dbe_lz4_encoder_8k.v", + codegen_args = { + "module_name": "dbe_lz4_encoder", + "delay_model": "unit", + "pipeline_stages": "3", + "worst_case_throughput": "3", + "reset": "rst", + "use_system_verilog": "false", + "streaming_channel_data_suffix": "_data", + }, +) diff --git a/xls/modules/dbe/common.x b/xls/modules/dbe/common.x new file mode 100644 index 0000000000..db464c1ce2 --- /dev/null +++ b/xls/modules/dbe/common.x @@ -0,0 +1,53 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import std + +pub enum Mark : u4 { + // Default initialization value used when marker field is not needed + NONE = 0, + // Signals end of sequence/block + END = 1, + // Requests reset of the processing chain + RESET = 2, + + _ERROR_FIRST = 8, + // Only error marks have values >= __ERROR_FIRST + ERROR_BAD_MARK = 8, + ERROR_INVAL_CP = 9, +} + +pub fn is_error(mark: Mark) -> bool { + (mark as u32) >= (Mark::_ERROR_FIRST as u32) +} + +pub enum TokenKind : u2 { + LITERAL = 0, + COPY_POINTER = 1, + MARK = 2, +} + +pub struct Token { + kind: TokenKind, + literal: uN[SYM_WIDTH], + copy_pointer_offset: uN[PTR_WIDTH], + copy_pointer_count: uN[CNT_WIDTH], + mark: Mark +} + +pub struct PlainData { + is_mark: bool, + data: uN[DATA_WIDTH], // symbol + mark: Mark, // marker code +} diff --git a/xls/modules/dbe/common_test.x b/xls/modules/dbe/common_test.x new file mode 100644 index 0000000000..a391ce2700 --- /dev/null +++ b/xls/modules/dbe/common_test.x @@ -0,0 +1,171 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import xls.modules.dbe.common as dbe + +type TokenKind = dbe::TokenKind; +type Token = dbe::Token; +type PlainData = dbe::PlainData; + +/// +/// This library contains helper processes and functions used by DBE tests +/// + +pub proc data_sender { + data: PlainData[NUM_SYMS]; + o_data: chan> out; + + init { u32:0 } + + config ( + data: PlainData[NUM_SYMS], + o_data: chan> out, + ) { + (data, o_data) + } + + next (tok: token, state: u32) { + let next_idx = state + u32:1; + let is_done = (state >= NUM_SYMS); + + if (!is_done) { + let tosend = data[state]; + trace_fmt!("Sending {}", tosend); + send(tok, o_data, tosend); + next_idx + } else { + state + } + } +} + +pub proc data_validator { + ref_data: PlainData[NUM_SYMS]; + i_data: chan> in; + o_term: chan out; + + init { u32:0 } + + config( + ref_data: PlainData[NUM_SYMS], + i_data: chan> in, + o_term: chan out + ) { + (ref_data, i_data, o_term) + } + + next (tok: token, state: u32) { + // state = [0, NUM_SYMS-1] - expect data + // state >= NUM_SYMS - expect nothing + let next_idx = state + u32:1; + let is_end = (state == NUM_SYMS - u32:1); + let is_done = (state >= NUM_SYMS); + + let (tok, rx) = recv(tok, i_data); + trace_fmt!("Received {}", rx); + + let (fail, next_state) = if is_done { + // Shouldn't get here + (true, state) + } else { + let expect = ref_data[state]; + let fail = if (rx != expect) { + trace_fmt!("MISMATCH! Expected {}, got {}", expect, rx); + true + } else { + false + }; + (fail, next_idx) + }; + + send_if(tok, o_term, fail || is_end, !fail); + + next_state + } +} + +pub proc token_sender< + NUM_TOKS: u32, SYM_WIDTH: u32, PTR_WIDTH: u32, CNT_WIDTH: u32> { + toks: Token[NUM_TOKS]; + o_toks: chan> out; + + init { u32:0 } + + config ( + toks: Token[NUM_TOKS], + o_toks: chan> out, + ) { + (toks, o_toks) + } + + next (tok: token, state: u32) { + let next_idx = state + u32:1; + let is_done = (state >= NUM_TOKS); + + if (!is_done) { + let tosend = toks[state]; + trace_fmt!("Sending {}", tosend); + send(tok, o_toks, tosend); + next_idx + } else { + state + } + } +} + +pub proc token_validator< + NUM_TOKS: u32, SYM_WIDTH: u32, PTR_WIDTH: u32, CNT_WIDTH: u32> { + ref_toks: Token[NUM_TOKS]; + i_token: chan> in; + o_term: chan out; + + init { u32:0 } + + config( + ref_toks: Token[NUM_TOKS], + i_token: chan> in, + o_term: chan out + ) { + (ref_toks, i_token, o_term) + } + + next (tok: token, state: u32) { + // state = [0, NUM_TOKS-1] - expect token + // state >= NUM_TOKS - expect nothing + let next_idx = state + u32:1; + let is_end = (state == NUM_TOKS - u32:1); + let is_done = (state >= NUM_TOKS); + + let (tok, rx) = recv(tok, i_token); + trace_fmt!("Received {}", rx); + + let (fail, next_state) = if is_done { + // Shouldn't get here + (true, state) + } else { + let expect = ref_toks[state]; + let fail = if (rx != expect) { + trace_fmt!("MISMATCH! Expected {}, got {}", expect, rx); + true + } else { + false + }; + (fail, next_idx) + }; + + send_if(tok, o_term, fail || is_end, !fail); + + next_state + } +} diff --git a/xls/modules/dbe/lz4_decoder.x b/xls/modules/dbe/lz4_decoder.x new file mode 100644 index 0000000000..0167485ed6 --- /dev/null +++ b/xls/modules/dbe/lz4_decoder.x @@ -0,0 +1,588 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import std +import xls.examples.ram as ram +import xls.modules.dbe.common as dbe + +type Mark = dbe::Mark; +type TokenKind = dbe::TokenKind; +type Token = dbe::Token; +type PlainData = dbe::PlainData; +type AbstractRamWriteReq = ram::WriteReq; +type AbstractRamWriteResp = ram::WriteResp; +type AbstractRamReadReq = ram::ReadReq; +type AbstractRamReadResp = ram::ReadResp; + + +/// The behavior of our decoder is defined as follows. +/// +/// A. History buffer is a buffer of (1< { + // Write pointer - next location _to be written_ + wr_ptr: uN[PTR_WIDTH], + // Read pointer - next location _to be read_ + rd_ptr: uN[PTR_WIDTH], + // Read counter - remaining number of symbols to be read out from the + // history buffer + rd_ctr_rem: uN[CNT_WIDTH], + // Whether we're in the ERROR state + err: bool, + // Helps to track which cells in HB are written and which ones are not + // (if some COPY_POINTER token points to an unwritten cell, we detect that and + // generate an error, as otherwise that will make decoder output garbage + // data that may leak some stale data from previous blocks) + hb_all_valid: bool, +} + +pub proc decoder_base { + encoded_data: chan> in; + plain_data: chan> out; + + /// History buffer RAM + /// Size: (1<> out; + ram_hb_rd_resp: chan> in; + ram_hb_wr_req: chan> out; + ram_hb_wr_comp: chan in; + + init { + State { + wr_ptr: uN[PTR_WIDTH]:0, + rd_ptr: uN[PTR_WIDTH]:0, + rd_ctr_rem: uN[CNT_WIDTH]:0, + err: false, + hb_all_valid: false, + } + } + + config ( + encoded_data: chan> in, + plain_data: chan> out, + // RAM + ram_hb_rd_req: chan> out, + ram_hb_rd_resp: chan> in, + ram_hb_wr_req: chan> out, + ram_hb_wr_comp: chan in, + ) { + ( + encoded_data, plain_data, + ram_hb_rd_req, ram_hb_rd_resp, ram_hb_wr_req, ram_hb_wr_comp, + ) + } + + next (tok: token, state: State) { + type DecToken = Token; + type DecData = PlainData; + type HbRamReadResp = AbstractRamReadResp; + + let is_copying = state.rd_ctr_rem != uN[CNT_WIDTH]:0; + let do_recv = !is_copying; + + // Receive token + let (tok, rx) = recv_if(tok, encoded_data, do_recv, + zero!()); + + let rx_lt = do_recv && rx.kind == TokenKind::LITERAL; + let rx_cp = do_recv && rx.kind == TokenKind::COPY_POINTER; + let rx_mark = do_recv && rx.kind == TokenKind::MARK; + let rx_end = rx_mark && rx.mark == Mark::END; + let rx_error = rx_mark && dbe::is_error(rx.mark); + let rx_reset = rx_mark && rx.mark == Mark::RESET; + let rx_unexpected_mark = rx_mark && !(rx_end || rx_reset || rx_error); + + // Are we going to output a literal symbol or a symbol from HB? + let emit_sym_lit = rx_lt; + let emit_sym_from_hb = rx_cp || is_copying; + + // Reset, set or increment read pointer + let rd_ptr = if rx_reset { + uN[PTR_WIDTH]:0 + } else if rx_cp { + state.wr_ptr - rx.copy_pointer_offset - uN[PTR_WIDTH]:1 + } else if is_copying { + state.rd_ptr + uN[PTR_WIDTH]:1 + } else { + state.rd_ptr + }; + + // Check whether rd_ptr points to an unwritten HB cell + let rd_ptr_valid = (rd_ptr < state.wr_ptr) || state.hb_all_valid; + + // HB READ - read the symbol from history buffer + let do_hb_read = emit_sym_from_hb && rd_ptr_valid; + let tok = send_if(tok, ram_hb_rd_req, do_hb_read, + ram::ReadWordReq(rd_ptr)); + let (tok, hb_rd) = recv_if(tok, ram_hb_rd_resp, do_hb_read, + zero!()); + + // Generate ERROR_INVAL_CP when we should've read from HB but + // we couldn't because COPY_POINTER points to an invalid location + let err_inval_cp = emit_sym_from_hb && !do_hb_read; + + // Send decoded data symbol or Mark + let zero_data = zero!(); + let (data_valid, data) = if rx_reset { + // Propagate RESET in all states + (true, DecData { + is_mark: true, + mark: Mark::RESET, + ..zero_data + }) + } else if state.err { + // Do not send anything else while in an error state + (false, zero_data) + } else if rx_error || rx_end { + // Propagate ERROR and END tokens + (true, DecData { + is_mark: true, + mark: rx.mark, + ..zero_data + }) + } else if rx_unexpected_mark { + // Generate ERROR_BAD_MARK + (true, DecData { + is_mark: true, + mark: Mark::ERROR_BAD_MARK, + ..zero_data + }) + } else if err_inval_cp { + // Generate ERROR_INVAL_CP + (true, DecData { + is_mark: true, + mark: Mark::ERROR_INVAL_CP, + ..zero_data + }) + } else if emit_sym_lit { + // Replicate symbol from LITERAL token + (true, DecData { + is_mark: false, + data: rx.literal, + ..zero_data + }) + } else if emit_sym_from_hb { + // Replicate symbol from HB + (true, DecData { + is_mark: false, + data: hb_rd.data, + ..zero_data + }) + } else { + (false, zero_data) + }; + let tok = send_if(tok, plain_data, data_valid, data); + + // HB WRITE - write emitted symbol to the history buffer + let do_hb_write = data_valid && !data.is_mark; + let tok = send_if(tok, ram_hb_wr_req, do_hb_write, + ram::WriteWordReq(state.wr_ptr, data.data)); + let (tok, _) = recv_if(tok, ram_hb_wr_comp, do_hb_write, + zero!()); + + // Reset/increment write pointer + let wr_ptr = if rx_reset { + uN[PTR_WIDTH]:0 + } else if do_hb_write { + state.wr_ptr + uN[PTR_WIDTH]:1 + } else { + state.wr_ptr + }; + + // When write pointer wraps over to 0 after the increment, + // that means we've filled the complete HB, so set a corresponding flag + let hb_all_valid = if rx_reset { + false + } else if do_hb_write && wr_ptr == uN[PTR_WIDTH]:0 { + true + } else { + state.hb_all_valid + }; + + // Read count set & decrement + let rd_ctr_rem = if rx_reset { + uN[CNT_WIDTH]:0 + } else if rx_cp { + rx.copy_pointer_count + } else if is_copying { + state.rd_ctr_rem - uN[CNT_WIDTH]:1 + } else { + state.rd_ctr_rem + }; + + // Enter/exit error state + let err = if rx_reset { + false + } else if rx_error || rx_unexpected_mark || err_inval_cp { + true + } else { + state.err + }; + + State{ + wr_ptr: wr_ptr, + rd_ptr: rd_ptr, + rd_ctr_rem: rd_ctr_rem, + hb_all_valid: hb_all_valid, + err: err, + } + } +} + +/// Version of `decoder` with embedded RamModel +/// Intended to be used only for tests +pub proc decoder_base_modelram { + init{()} + + config ( + encoded_data: chan> in, + plain_data: chan> out, + ) { + let (hb_wr_req_s, hb_wr_req_r) = + chan>; + let (hb_wr_comp_s, hb_wr_comp_r) = + chan; + let (hb_rd_req_s, hb_rd_req_r) = + chan>; + let (hb_rd_resp_s, hb_rd_resp_r) = + chan>; + + spawn ram::RamModel + (hb_rd_req_r, hb_rd_resp_s, hb_wr_req_r, hb_wr_comp_s); + + spawn decoder_base + ( + encoded_data, plain_data, + hb_rd_req_s, hb_rd_resp_r, hb_wr_req_s, hb_wr_comp_r, + ); + } + + next (tok: token, state: ()) { + } +} + +/// Version of `decoder_base` compatible with classic LZ4 algorithm +pub const LZ4C_SYM_WIDTH = u32:8; +pub const LZ4C_PTR_WIDTH = u32:16; +pub const LZ4C_CNT_WIDTH = u32:16; + +pub proc decoder { + init{} + + config ( + encoded_data: + chan> in, + plain_data: chan> out, + ram_hb_rd_req: chan> out, + ram_hb_rd_resp: chan> in, + ram_hb_wr_req: + chan> out, + ram_hb_wr_comp: chan in, + ) { + spawn decoder_base + + ( + encoded_data, plain_data, + ram_hb_rd_req, ram_hb_rd_resp, ram_hb_wr_req, ram_hb_wr_comp, + ); + } + + next(tok: token, st: ()) { + } +} + +/// Version of `decoder` with embedded RamModel +/// Intended to be used only for tests +pub proc decoder_modelram { + init{()} + + config ( + encoded_data: + chan> in, + plain_data: chan> out, + ) { + let (hb_wr_req_s, hb_wr_req_r) = + chan>; + let (hb_wr_comp_s, hb_wr_comp_r) = + chan; + let (hb_rd_req_s, hb_rd_req_r) = + chan>; + let (hb_rd_resp_s, hb_rd_resp_r) = + chan>; + + spawn ram::RamModel + + (hb_rd_req_r, hb_rd_resp_s, hb_wr_req_r, hb_wr_comp_s); + + spawn decoder + ( + encoded_data, plain_data, + hb_rd_req_s, hb_rd_resp_r, hb_wr_req_s, hb_wr_comp_r, + ); + } + + next (tok: token, state: ()) { + } +} + + +/// +/// Tests +/// +import xls.modules.dbe.common_test as test + +const TST_SYM_WIDTH = u32:4; +const TST_PTR_WIDTH = u32:3; +const TST_CNT_WIDTH = u32:4; + + +// Reference input +const TST_SIMPLE_INPUT_LEN = u32:32; +const TST_SIMPLE_INPUT = Token[TST_SIMPLE_INPUT_LEN]: [ + Token{kind: TokenKind::LITERAL, literal: uN[4]:12, copy_pointer_offset: uN[3]:0, copy_pointer_count: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: uN[4]: 1, copy_pointer_offset: uN[3]:0, copy_pointer_count: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: uN[4]:15, copy_pointer_offset: uN[3]:0, copy_pointer_count: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: uN[4]: 9, copy_pointer_offset: uN[3]:0, copy_pointer_count: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: uN[4]:11, copy_pointer_offset: uN[3]:0, copy_pointer_count: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::COPY_POINTER, literal: uN[4]:0, copy_pointer_offset: uN[3]: 1, copy_pointer_count: uN[4]: 1, mark: Mark::NONE}, + Token{kind: TokenKind::COPY_POINTER, literal: uN[4]:0, copy_pointer_offset: uN[3]: 0, copy_pointer_count: uN[4]: 2, mark: Mark::NONE}, + Token{kind: TokenKind::COPY_POINTER, literal: uN[4]:0, copy_pointer_offset: uN[3]: 4, copy_pointer_count: uN[4]: 0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: uN[4]:15, copy_pointer_offset: uN[3]:0, copy_pointer_count: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::COPY_POINTER, literal: uN[4]:0, copy_pointer_offset: uN[3]: 1, copy_pointer_count: uN[4]: 0, mark: Mark::NONE}, + Token{kind: TokenKind::COPY_POINTER, literal: uN[4]:0, copy_pointer_offset: uN[3]: 2, copy_pointer_count: uN[4]:13, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: uN[4]: 1, copy_pointer_offset: uN[3]:0, copy_pointer_count: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::COPY_POINTER, literal: uN[4]:0, copy_pointer_offset: uN[3]: 5, copy_pointer_count: uN[4]: 0, mark: Mark::NONE}, + Token{kind: TokenKind::COPY_POINTER, literal: uN[4]:0, copy_pointer_offset: uN[3]: 5, copy_pointer_count: uN[4]: 2, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: uN[4]: 7, copy_pointer_offset: uN[3]:0, copy_pointer_count: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: uN[4]: 2, copy_pointer_offset: uN[3]:0, copy_pointer_count: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::COPY_POINTER, literal: uN[4]:0, copy_pointer_offset: uN[3]: 0, copy_pointer_count: uN[4]:11, mark: Mark::NONE}, + Token{kind: TokenKind::COPY_POINTER, literal: uN[4]:0, copy_pointer_offset: uN[3]: 1, copy_pointer_count: uN[4]: 6, mark: Mark::NONE}, + Token{kind: TokenKind::COPY_POINTER, literal: uN[4]:0, copy_pointer_offset: uN[3]: 5, copy_pointer_count: uN[4]:15, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: uN[4]: 3, copy_pointer_offset: uN[3]:0, copy_pointer_count: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: uN[4]: 9, copy_pointer_offset: uN[3]:0, copy_pointer_count: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::COPY_POINTER, literal: uN[4]:0, copy_pointer_offset: uN[3]: 5, copy_pointer_count: uN[4]: 1, mark: Mark::NONE}, + Token{kind: TokenKind::COPY_POINTER, literal: uN[4]:0, copy_pointer_offset: uN[3]: 3, copy_pointer_count: uN[4]:13, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: uN[4]:14, copy_pointer_offset: uN[3]:0, copy_pointer_count: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::COPY_POINTER, literal: uN[4]:0, copy_pointer_offset: uN[3]: 1, copy_pointer_count: uN[4]: 2, mark: Mark::NONE}, + Token{kind: TokenKind::COPY_POINTER, literal: uN[4]:0, copy_pointer_offset: uN[3]: 0, copy_pointer_count: uN[4]: 0, mark: Mark::NONE}, + Token{kind: TokenKind::COPY_POINTER, literal: uN[4]:0, copy_pointer_offset: uN[3]: 7, copy_pointer_count: uN[4]: 0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: uN[4]:15, copy_pointer_offset: uN[3]:0, copy_pointer_count: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::COPY_POINTER, literal: uN[4]:0, copy_pointer_offset: uN[3]: 4, copy_pointer_count: uN[4]: 0, mark: Mark::NONE}, + Token{kind: TokenKind::COPY_POINTER, literal: uN[4]:0, copy_pointer_offset: uN[3]: 5, copy_pointer_count: uN[4]:11, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: uN[4]: 8, copy_pointer_offset: uN[3]:0, copy_pointer_count: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::MARK, literal: uN[4]:0, copy_pointer_offset: uN[3]:0, copy_pointer_count: uN[4]:0, mark: Mark::END} +]; +// Reference output +const TST_SIMPLE_OUTPUT_LEN = u32:109; +const TST_SIMPLE_OUTPUT = PlainData[TST_SIMPLE_OUTPUT_LEN]: [ + PlainData{is_mark: false, data: uN[4]:12, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:1, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:15, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:11, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:11, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:11, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:11, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:11, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:15, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:15, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:15, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:15, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:15, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:15, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:1, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:15, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:7, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:3, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:3, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:3, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:3, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:3, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:14, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:14, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:15, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:14, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:14, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:15, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:14, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:14, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:9, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:15, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:14, mark: Mark::NONE}, + PlainData{is_mark: false, data: uN[4]:8, mark: Mark::NONE}, + PlainData{is_mark: true, data: uN[4]:0, mark: Mark::END} +]; + +#[test_proc] +proc test_simple { + term: chan out; + recv_last_r: chan in; + + init {()} + + config(term: chan out) { + // tokens from sender and to decoder + let (send_toks_s, send_toks_r) = + chan>; + // symbols & last indicator from receiver + let (recv_data_s, recv_data_r) = chan>; + let (recv_last_s, recv_last_r) = chan; + + + spawn test::token_sender + + (TST_SIMPLE_INPUT, send_toks_s); + spawn decoder_base_modelram( + send_toks_r, recv_data_s); + spawn test::data_validator( + TST_SIMPLE_OUTPUT, recv_data_r, recv_last_s); + + (term, recv_last_r) + } + + next (tok: token, state: ()) { + let (tok, recv_term) = recv(tok, recv_last_r); + send(tok, term, recv_term); + } +} diff --git a/xls/modules/dbe/lz4_decoder_ram1r1w_rewrites.textproto b/xls/modules/dbe/lz4_decoder_ram1r1w_rewrites.textproto new file mode 100644 index 0000000000..a23b2a1d40 --- /dev/null +++ b/xls/modules/dbe/lz4_decoder_ram1r1w_rewrites.textproto @@ -0,0 +1,32 @@ +# proto-file: xls/ir/ram_rewrite.proto +# proto-message: RamRewrites + +rewrites { + from_config { + kind: RAM_ABSTRACT + depth: 65536 + word_partition_size: 8 + } + to_config { + kind: RAM_1R1W + depth: 65536 + word_partition_size: 8 + } + from_channels_logical_to_physical: { + key: "abstract_read_req" + value: "lz4_decoder__ram_hb_rd_req" + } + from_channels_logical_to_physical: { + key: "abstract_read_resp" + value: "lz4_decoder__ram_hb_rd_resp" + } + from_channels_logical_to_physical: { + key: "abstract_write_req" + value: "lz4_decoder__ram_hb_wr_req" + } + from_channels_logical_to_physical: { + key: "write_completion" + value: "lz4_decoder__ram_hb_wr_comp" + } + to_name_prefix: "ram_hb" +} diff --git a/xls/modules/dbe/lz4_encoder.x b/xls/modules/dbe/lz4_encoder.x new file mode 100644 index 0000000000..16621ad8ed --- /dev/null +++ b/xls/modules/dbe/lz4_encoder.x @@ -0,0 +1,824 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import std +import xls.examples.ram as ram +import xls.modules.dbe.common as dbe + +type Mark = dbe::Mark; +type TokenKind = dbe::TokenKind; +type Token = dbe::Token; +type PlainData = dbe::PlainData; +type RamReq = ram::SinglePortRamReq; +type RamResp = ram::SinglePortRamResp; + + +fn fifo_shift_left( + fifo: uN[DATA_WIDTH][FIFO_SIZE], + data: uN[DATA_WIDTH]) -> uN[DATA_WIDTH][FIFO_SIZE] { + let fifo = for (i, arr): (u32, uN[DATA_WIDTH][FIFO_SIZE]) + in u32:1..FIFO_SIZE { + update(arr, i - u32:1, fifo[i]) + } (uN[DATA_WIDTH][FIFO_SIZE]: [0, ...]); + let fifo = update(fifo, FIFO_SIZE - u32:1, data); + fifo +} + +fn fifo_shift_right( + fifo: uN[DATA_WIDTH][FIFO_SIZE], + data: uN[DATA_WIDTH]) -> uN[DATA_WIDTH][FIFO_SIZE] { + let fifo = for (i, arr): (u32, uN[DATA_WIDTH][FIFO_SIZE]) + in u32:1..FIFO_SIZE { + update(arr, i, fifo[i - u32:1]) + } (uN[DATA_WIDTH][FIFO_SIZE]: [0, ...]); + let fifo = update(fifo, u32:0, data); + fifo +} + +fn RamWriteReq + (addr:uN[ADDR_WIDTH], data:uN[DATA_WIDTH]) + -> RamReq { + RamReq { + addr:addr, + data:data, + write_mask: (), + read_mask: (), + we: true, + re: false, + } +} + +fn RamReadReq + (addr:uN[ADDR_WIDTH]) + -> RamReq { + RamReq { + addr:addr, + data:uN[DATA_WIDTH]:0, + write_mask: (), + read_mask: (), + we: false, + re: true, + } +} + +enum FsmSt: u4 { + RESET = 0, + HASH_TABLE_CLEAR = 1, + RESTART = 2, + FIFO_PREFILL = 3, + FIFO_POSTFILL = 4, + START_MATCH_0 = 5, + START_MATCH_1 = 6, + CONTINUE_MATCH_0 = 7, + CONTINUE_MATCH_1 = 8, + EMIT_SHORT_MATCH = 9, + EMIT_FINAL_LITERALS = 10, + EMIT_END = 11, + ERROR = 12, +} + +struct State { + fifo_cache: uN[SYM_WIDTH][FIFO_CACHE_SZ], + fifo_in: uN[SYM_WIDTH][FIFO_IN_SZ], + fifo_in_count: u32, + fifo_in_nvalid: u32, + wp: uN[PTR_WIDTH], + hb_all_valid: bool, + pnew: uN[PTR_WIDTH], + pold: uN[PTR_WIDTH], + match_nconts: u32, + ht_ptr: uN[HASH_BITS], + recycle: bool, + finalize: bool, + fsm: FsmSt, +} + +fn init_state() + -> State { + State{ + fifo_cache: uN[SYM_WIDTH][FIFO_CACHE_SZ]: [uN[SYM_WIDTH]:0, ...], + fifo_in: uN[SYM_WIDTH][FIFO_IN_SZ]: [uN[SYM_WIDTH]:0, ...], + fifo_in_count: u32:0, + fifo_in_nvalid: u32:0, + wp: uN[PTR_WIDTH]:0, + hb_all_valid: false, + pnew: uN[PTR_WIDTH]:0, + pold: uN[PTR_WIDTH]:0, + match_nconts: u32:0, + ht_ptr: uN[HASH_BITS]:0, + recycle: false, + finalize: false, + fsm: FsmSt::RESET + } +} + +pub proc encoder_base< + SYM_WIDTH: u32, PTR_WIDTH: u32, CNT_WIDTH: u32, HASH_BITS: u32, + MINMATCH: u32 = {u32:4}, FINAL_LITERALS: u32 = {u32:12}, + FIFO_CACHE_SZ: u32 = {MINMATCH - u32:1}, + FIFO_IN_SZ: u32 = {std::umax(MINMATCH, FINAL_LITERALS + u32:1)}, + HB_RAM_SZ: u32 = {u32:1 << PTR_WIDTH}, + HT_RAM_SZ: u32 = {u32:1 << HASH_BITS}> { + i_data: chan> in; + o_encoded: chan> out; + + /// History buffer RAM + /// Size: (1<> out; + i_ram_hb_resp: chan> in; + i_ram_hb_wr_comp: chan<()> in; + + /// Hash table RAM + /// Size: (1<> out; + i_ram_ht_resp: chan> in; + i_ram_ht_wr_comp: chan<()> in; + + init { + init_state() + } + + config ( + // Data in, tokens out + i_data: chan> in, + o_encoded: chan> out, + // RAM + o_ram_hb_req: chan> out, + i_ram_hb_resp: chan> in, + i_ram_hb_wr_comp: chan<()> in, + o_ram_ht_req: chan> out, + i_ram_ht_resp: chan> in, + i_ram_ht_wr_comp: chan<()> in, + ) { + ( + i_data, o_encoded, + o_ram_hb_req, i_ram_hb_resp, i_ram_hb_wr_comp, + o_ram_ht_req, i_ram_ht_resp, i_ram_ht_wr_comp, + ) + } + + next (tok: token, cur: State) { + type EncToken = Token; + type EncData = PlainData; + type EncHbRamReq = RamReq; + type EncHbRamResp = RamResp; + type EncHtRamReq = RamReq; + type EncHtRamResp = RamResp; + let upd = cur; + + // Read new symbol from input + let do_recv = !cur.recycle && ( + cur.fsm == FsmSt::FIFO_PREFILL + || cur.fsm == FsmSt::START_MATCH_0 + || cur.fsm == FsmSt::CONTINUE_MATCH_0 + || cur.fsm == FsmSt::ERROR + ); + + // I_DATA RECV + let (tok, rx) = recv_if(tok, i_data, do_recv, + zero!()); + + // Classify input markers + let rx_symbol = do_recv && !rx.is_mark; + let rx_mark = do_recv && rx.is_mark; + let rx_end = rx_mark && rx.mark == Mark::END; + let rx_error = rx_mark && dbe::is_error(rx.mark); + let rx_reset = rx_mark && rx.mark == Mark::RESET; + let rx_unexpected_mark = rx_mark && !(rx_end || rx_reset || rx_error); + + let upd = if cur.fsm == FsmSt::ERROR || cur.recycle { + // Do not shift FIFOs / write HB in these states + upd + } else if rx_symbol { + let wp = upd.wp + uN[PTR_WIDTH]:1; + // Update state, shift input FIFOs + let upd = State{ + wp: wp, + fifo_cache: + fifo_shift_right(upd.fifo_cache, upd.fifo_in[0]), + fifo_in: + fifo_shift_left(upd.fifo_in, rx.data), + hb_all_valid: + if wp == uN[PTR_WIDTH]:0 {true} else {upd.hb_all_valid}, + fifo_in_count: + std::umin(upd.fifo_in_count + u32:1, FIFO_IN_SZ), + ..upd + }; + upd + } else if rx_end { + // When receiving END marker, shift input FIFO anyhow as we're + // expected to drop the symbol at OP + let new_count = std::umin(upd.fifo_in_count + u32:1, FIFO_IN_SZ); + let upd = State{ + finalize: true, + fifo_cache: + fifo_shift_right(upd.fifo_cache, upd.fifo_in[0]), + fifo_in: + fifo_shift_left(upd.fifo_in, uN[SYM_WIDTH]:0), + fifo_in_count: new_count, + fifo_in_nvalid: new_count - u32:1, + ..upd + }; + upd + } else if ( + cur.fsm == FsmSt::FIFO_POSTFILL + || cur.fsm == FsmSt::EMIT_FINAL_LITERALS + ) { + // Feed input FIFO with 0s and shift + let (new_count, new_nvalid) = if cur.fsm == FsmSt::FIFO_POSTFILL { + ( + std::umin(upd.fifo_in_count + u32:1, FIFO_IN_SZ), + upd.fifo_in_nvalid + ) + } else { + ( + upd.fifo_in_count, + upd.fifo_in_nvalid - u32:1 + ) + }; + let upd = State{ + fifo_in: + fifo_shift_left(upd.fifo_in, uN[SYM_WIDTH]:0), + fifo_in_count: new_count, + fifo_in_nvalid: new_nvalid, + ..upd + }; + upd + } else { + upd + }; + + // Calculate origin pointer OP from WP + let op = ((upd.wp as u32) - FIFO_IN_SZ + u32:1) as uN[PTR_WIDTH]; + + // Calculate u32 Fibonacci hash function + let hsh_input = (upd.fifo_in[3] as u8) + ++ (upd.fifo_in[2] as u8) + ++ (upd.fifo_in[1] as u8) + ++ (upd.fifo_in[0] as u8); + let hsh32 = hsh_input * u32:2654435761; + let hsh = (hsh32 >> (u32:32 - HASH_BITS)) as uN[HASH_BITS]; + + // NOTE: HT RAM, HB RAM accesses and o_token emission all happen + // in parallel. + + // Access HT RAM + let (ht_vld, ht_req) = if cur.fsm == FsmSt::START_MATCH_0 { + (true, RamReadReq(hsh)) + } else if cur.fsm == FsmSt::START_MATCH_1 { + (true, RamWriteReq(hsh, op)) + } else if cur.fsm == FsmSt::HASH_TABLE_CLEAR { + (true, RamWriteReq(upd.ht_ptr, uN[PTR_WIDTH]:0)) + } else { + (false, zero!()) + }; + let tok_ht = send_if(tok, o_ram_ht_req, ht_vld, ht_req); + let (tok_ht, ht_resp) = recv_if(tok_ht, i_ram_ht_resp, ht_vld && ht_req.re, + zero!()); + let (tok_ht, _) = recv_if(tok_ht, i_ram_ht_wr_comp, ht_vld && ht_req.we, + ()); + + // Update match pointers & HT ptr used to clean hash table + let upd = if cur.fsm == FsmSt::START_MATCH_0 { + State { + pold: ht_resp.data, + pnew: op, + ..upd + } + } else if cur.fsm == FsmSt::HASH_TABLE_CLEAR { + State { + ht_ptr: upd.ht_ptr + uN[HASH_BITS]:1, + ..upd + } + } else { + upd + }; + + // Prepare to check for a match + let (mchk_do, mchk_pos, mchk_canextend) = if ( + cur.fsm == FsmSt::START_MATCH_1 + ) { + ( + true, + upd.pold, + true + ) + } else if (cur.fsm == FsmSt::CONTINUE_MATCH_1) { + ( + true, + upd.pold + upd.match_nconts as uN[PTR_WIDTH] + uN[PTR_WIDTH]:1, + upd.match_nconts < ((u32:1 << CNT_WIDTH) - u32:1) + ) + } else { + ( + false, + uN[PTR_WIDTH]:0, + false + ) + }; + + // Do not match-check unwritten (uninitialized) HB RAM locations + let mchk_is_hb_written = + upd.hb_all_valid + || (mchk_pos >= uN[PTR_WIDTH]:1 && mchk_pos < upd.wp); + + // Access HB RAM + let (hb_vld, hb_req) = if rx_symbol && cur.fsm != FsmSt::ERROR { + (true, RamWriteReq(upd.wp, rx.data)) + } else if mchk_do && mchk_is_hb_written { + (true, RamReadReq(mchk_pos)) + } else { + (false, zero!()) + }; + let tok_hb = send_if(tok, o_ram_hb_req, hb_vld, hb_req); + let (tok_hb, hb_resp) = recv_if(tok_hb, i_ram_hb_resp, hb_vld && hb_req.re, + zero!()); + let (tok_hb, _) = recv_if(tok_hb, i_ram_hb_wr_comp, hb_vld && hb_req.we, + ()); + + // Actually check for a match + let is_match = if mchk_do { + let sym = hb_resp.data; + // For match to happen, following criteria have to be met: + // 1. pos should not point to an unwritten HB entry + // (see `mchk_is_hb_written` above) + // 2. pos should not point between OP and WP inclusive + let isold = (mchk_pos - op) > (upd.wp - op); + // 3. sym should match current origin symbol + let _matches = sym == upd.fifo_in[0]; + // 4. our existing matching string should not be too long + (mchk_is_hb_written && isold && _matches && mchk_canextend) + } else { + false + }; + + // Update match_nconts + let upd = State{ + match_nconts: if cur.fsm == FsmSt::START_MATCH_1 { + u32:0 + } else if cur.fsm == FsmSt::CONTINUE_MATCH_1 { + upd.match_nconts + u32:1 + } else if cur.fsm == FsmSt::EMIT_SHORT_MATCH { + upd.match_nconts - u32:1 + } else { + upd.match_nconts + }, + ..upd + }; + + // Handle match termination + let ( + is_match_terminated, + match_len, + match_is_long + ) = if cur.fsm == FsmSt::CONTINUE_MATCH_1 { + ( + !is_match || upd.finalize, + upd.match_nconts, + upd.match_nconts >= MINMATCH, + ) + } else { + (false, u32:0, false) + }; + + // Emit compressed data token + let zero_tok = zero!(); + let (do_emit, encoded_tok) = if rx_reset { + // Propagate RESET + (true, Token{ + kind: TokenKind::MARK, + mark: Mark::RESET, + ..zero_tok + }) + } else if cur.fsm == FsmSt::ERROR { + // Do not emit anything in error state + (false, zero_tok) + } else if rx_error { + // Propagate error + (true, Token{ + kind: TokenKind::MARK, + mark: rx.mark, + ..zero_tok + }) + } else if rx_unexpected_mark { + // Generate error + (true, Token{ + kind: TokenKind::MARK, + mark: Mark::ERROR_BAD_MARK, + ..zero_tok + }) + } else if ( + cur.fsm == FsmSt::START_MATCH_1 && + (!is_match || upd.finalize) + ) { + // Emit symbol at OP as literal + (true, Token{ + kind: TokenKind::LITERAL, + literal: upd.fifo_in[0], + ..zero_tok + }) + } else if cur.fsm == FsmSt::CONTINUE_MATCH_1 && is_match_terminated { + // If match is long enough, emit a single CP + // If not, emit symbols from Cache FIFO as literals + if match_is_long { + let off = upd.pnew - upd.pold; + let t = Token { + kind: TokenKind::COPY_POINTER, + copy_pointer_offset: off - uN[PTR_WIDTH]:1, + copy_pointer_count: (match_len - u32:1) as uN[CNT_WIDTH], + ..zero_tok + }; + (true, t) + } else { + (true, Token { + kind: TokenKind::LITERAL, + literal: upd.fifo_cache[upd.match_nconts - u32:1], + ..zero_tok + }) + } + } else if cur.fsm == FsmSt::EMIT_SHORT_MATCH { + (true, Token { + kind: TokenKind::LITERAL, + literal: upd.fifo_cache[upd.match_nconts - u32:1], + ..zero_tok + }) + } else if cur.fsm == FsmSt::EMIT_FINAL_LITERALS { + // We dump literals from Input FIFO till nvalid becomes 0 + (true, Token { + kind: TokenKind::LITERAL, + literal: upd.fifo_in[u32:0], + ..zero_tok + }) + } else if cur.fsm == FsmSt::EMIT_END { + (true, Token { + kind: TokenKind::MARK, + mark: Mark::END, + ..zero_tok + }) + } else { + (false, zero_tok) + }; + let tok_out = send_if(tok, o_encoded, do_emit, encoded_tok); + + // Handle state re-initialization + let upd = if cur.fsm == FsmSt::RESET { + // Full reset + // NOTE: .fsm value will be overridden by the state change logic + init_state() + } else if cur.fsm == FsmSt::RESTART { + // Intra-block partial reset, keeping HB and HT intact + State { + fifo_in_count: u32:0, + fifo_in_nvalid: u32:0, + match_nconts: u32:0, + finalize: false, + recycle: false, + ..upd + } + } else { + upd + }; + + // State change logic + let fsm = if rx_error || rx_unexpected_mark { + FsmSt::ERROR + } else if rx_reset { + FsmSt::RESET + } else { + match cur.fsm { + FsmSt::RESET => FsmSt::HASH_TABLE_CLEAR, + FsmSt::HASH_TABLE_CLEAR => { + if upd.ht_ptr == uN[HASH_BITS]:0 { + FsmSt::RESTART + } else { + cur.fsm + } + }, + FsmSt::RESTART => FsmSt::FIFO_PREFILL, + FsmSt::FIFO_PREFILL => { + if rx_end { + if upd.fifo_in_nvalid > u32:0 { + if upd.fifo_in_count < FIFO_IN_SZ { + FsmSt::FIFO_POSTFILL + } else { + FsmSt::EMIT_FINAL_LITERALS + } + } else { + // This handles empty input blocks + FsmSt::EMIT_END + } + } else if upd.fifo_in_count >= FIFO_IN_SZ { + FsmSt::START_MATCH_0 + } else { + cur.fsm + } + }, + FsmSt::START_MATCH_0 => FsmSt::START_MATCH_1, + FsmSt::START_MATCH_1 => { + if upd.finalize { + FsmSt::EMIT_FINAL_LITERALS + } else if is_match { + FsmSt::CONTINUE_MATCH_0 + } else { + FsmSt::START_MATCH_0 + } + }, + FsmSt::CONTINUE_MATCH_0 => FsmSt::CONTINUE_MATCH_1, + FsmSt::CONTINUE_MATCH_1 => { + if is_match_terminated { + // Match failed or interrupted + if match_is_long || upd.match_nconts == u32:1 { + // Copy pointer or a sole literal has been + // emitted + if upd.finalize { + FsmSt::EMIT_FINAL_LITERALS + } else { + FsmSt::START_MATCH_0 + } + } else { + // Still need to emit some literals to terminate + // the match + FsmSt::EMIT_SHORT_MATCH + } + } else { + // Continue matching + FsmSt::CONTINUE_MATCH_0 + } + }, + FsmSt::EMIT_SHORT_MATCH => { + if upd.match_nconts == u32:1 { + if upd.finalize { + // Finish block transcription + FsmSt::EMIT_FINAL_LITERALS + } else { + // Restart matching + FsmSt::START_MATCH_0 + } + } else { + cur.fsm + } + }, + FsmSt::FIFO_POSTFILL => { + // It is worth noting that we get into this state only when + // finalizing very short blocks (so short that they don't + // even fill the input FIFO). + if upd.fifo_in_count == FIFO_IN_SZ { + FsmSt::EMIT_FINAL_LITERALS + } else { + cur.fsm + } + }, + FsmSt::EMIT_FINAL_LITERALS => { + if upd.fifo_in_nvalid == u32:1 { + FsmSt::EMIT_END + } else { + cur.fsm + } + }, + FsmSt::EMIT_END => FsmSt::RESTART, + _ => fail!("unhandled_fsm_state", cur.fsm) + } + }; + + let upd = State { + fsm: fsm, + ..upd + }; + + // Set 'recycle' flag when we want to "stall" Input FIFO to not lose + // the symbol at the OP, which is needed for certain state transitions + let recycle = if ( + upd.fsm == FsmSt::START_MATCH_0 + && cur.fsm != FsmSt::START_MATCH_1 + ) { + true + } else if ( + upd.fsm == FsmSt::EMIT_FINAL_LITERALS + && cur.fsm != FsmSt::EMIT_FINAL_LITERALS + && cur.fsm != FsmSt::START_MATCH_1 + ) { + true + } else { + false + }; + + let upd = State { + recycle: recycle, + ..upd + }; + + upd + } +} + +/// Version of `encoder_base` that uses RamModel +/// Intended to be used only for tests +pub proc encoder_base_modelram + { + init{()} + + config ( + i_data: chan> in, + o_encoded: chan> out, + ) { + let (o_hb_req, i_hb_req) = + chan>; + let (o_hb_resp, i_hb_resp) = + chan>; + let (o_hb_wr_comp, i_hb_wr_comp) = + chan<()>; + let (o_ht_req, i_ht_req) = + chan>; + let (o_ht_resp, i_ht_resp) = + chan>; + let (o_ht_wr_comp, i_ht_wr_comp) = + chan<()>; + + spawn ram::SinglePortRamModel + + (i_hb_req, o_hb_resp, o_hb_wr_comp); + spawn ram::SinglePortRamModel + + (i_ht_req, o_ht_resp, o_ht_wr_comp); + + spawn encoder_base + + ( + i_data, o_encoded, + o_hb_req, i_hb_resp, i_hb_wr_comp, + o_ht_req, i_ht_resp, i_ht_wr_comp, + ); + } + + next (tok: token, state: ()) { + } +} + +/// LZ4 encoder with 8K hash table + +const LZ4C_SYM_WIDTH = u32:8; +const LZ4C_PTR_WIDTH = u32:16; +const LZ4C_CNT_WIDTH = u32:16; +const LZ4C_HASH_BITS_8K = u32:13; + +pub proc encoder_8k { + init{()} + + config ( + i_data: + chan> in, + o_encoded: + chan> out, + o_ram_hb_req: + chan> out, + i_ram_hb_resp: + chan> in, + i_ram_hb_wr_comp: + chan<()> in, + o_ram_ht_req: + chan> out, + i_ram_ht_resp: + chan> in, + i_ram_ht_wr_comp: + chan<()> in, + ) { + spawn encoder_base + + ( + i_data, o_encoded, + o_ram_hb_req, i_ram_hb_resp, i_ram_hb_wr_comp, + o_ram_ht_req, i_ram_ht_resp, i_ram_ht_wr_comp, + ); + } + + next(tok: token, st: ()) { + } +} + +/// Version of `encoder_8k` that uses RamModel +/// Intended to be used only for tests +pub proc encoder_8k_modelram { + init{()} + + config ( + i_data: + chan> in, + o_encoded: + chan> out, + ) { + spawn encoder_base_modelram + + (i_data, o_encoded); + } + + next (tok: token, state: ()) { + } +} + +/// +/// Tests +/// + +import xls.modules.dbe.common_test as test +import xls.modules.dbe.lz4_decoder as dec + +const TST_SYMS_LEN = u32:19; +const TST_SYMS = PlainData<8>[TST_SYMS_LEN]: [ + // Check basic LT and CP generation + PlainData{is_mark: false, data: u8:1, mark: Mark::NONE}, + PlainData{is_mark: false, data: u8:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: u8:1, mark: Mark::NONE}, + PlainData{is_mark: false, data: u8:2, mark: Mark::NONE}, + PlainData{is_mark: false, data: u8:1, mark: Mark::NONE}, + PlainData{is_mark: false, data: u8:2, mark: Mark::NONE}, + // Final token sequence - should stay as literals + PlainData{is_mark: false, data: u8:7, mark: Mark::NONE}, + PlainData{is_mark: false, data: u8:7, mark: Mark::NONE}, + PlainData{is_mark: false, data: u8:7, mark: Mark::NONE}, + PlainData{is_mark: false, data: u8:7, mark: Mark::NONE}, + PlainData{is_mark: false, data: u8:7, mark: Mark::NONE}, + PlainData{is_mark: false, data: u8:7, mark: Mark::NONE}, + PlainData{is_mark: false, data: u8:7, mark: Mark::NONE}, + PlainData{is_mark: false, data: u8:7, mark: Mark::NONE}, + PlainData{is_mark: false, data: u8:7, mark: Mark::NONE}, + PlainData{is_mark: false, data: u8:7, mark: Mark::NONE}, + PlainData{is_mark: false, data: u8:7, mark: Mark::NONE}, + PlainData{is_mark: false, data: u8:7, mark: Mark::NONE}, + PlainData{is_mark: true, data: u8:0, mark: Mark::END}, +]; + +const TST_TOKS_LEN = u32:16; +const TST_TOKS = +Token[TST_TOKS_LEN]: [ + Token{kind: TokenKind::LITERAL, literal: u8:1, copy_pointer_offset: u16:0, copy_pointer_count: u16:0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: u8:2, copy_pointer_offset: u16:0, copy_pointer_count: u16:0, mark: Mark::NONE}, + Token{kind: TokenKind::COPY_POINTER, literal: u8:0, copy_pointer_offset: u16:1, copy_pointer_count: u16:3, mark: Mark::NONE}, + // Final literal token sequence + Token{kind: TokenKind::LITERAL, literal: u8:7, copy_pointer_offset: u16:0, copy_pointer_count: u16:0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: u8:7, copy_pointer_offset: u16:0, copy_pointer_count: u16:0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: u8:7, copy_pointer_offset: u16:0, copy_pointer_count: u16:0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: u8:7, copy_pointer_offset: u16:0, copy_pointer_count: u16:0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: u8:7, copy_pointer_offset: u16:0, copy_pointer_count: u16:0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: u8:7, copy_pointer_offset: u16:0, copy_pointer_count: u16:0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: u8:7, copy_pointer_offset: u16:0, copy_pointer_count: u16:0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: u8:7, copy_pointer_offset: u16:0, copy_pointer_count: u16:0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: u8:7, copy_pointer_offset: u16:0, copy_pointer_count: u16:0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: u8:7, copy_pointer_offset: u16:0, copy_pointer_count: u16:0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: u8:7, copy_pointer_offset: u16:0, copy_pointer_count: u16:0, mark: Mark::NONE}, + Token{kind: TokenKind::LITERAL, literal: u8:7, copy_pointer_offset: u16:0, copy_pointer_count: u16:0, mark: Mark::NONE}, + Token{kind: TokenKind::MARK, literal: u8:0, copy_pointer_offset: u16:0, copy_pointer_count: u16:0, mark: Mark::END}, +]; + +#[test_proc] +proc test_encoder_8k_simple { + o_term: chan out; + i_recv_term: chan in; + + init {()} + + config(o_term: chan out) { + let (o_send_data, i_send_data) = + chan>; + let (enc_toks_s, enc_toks_r) = + chan>; + let (o_recv_term, i_recv_term) = + chan; + + spawn test::data_sender + (TST_SYMS, o_send_data); + spawn encoder_8k_modelram( + i_send_data, enc_toks_s, + ); + spawn test::token_validator + + (TST_TOKS, enc_toks_r, o_recv_term); + + (o_term, i_recv_term) + } + + next (tok: token, state: ()) { + // Here, test::token_validator validates the data received from encoder + // We only forward its o_term to our o_term + let (tok, recv_term) = recv(tok, i_recv_term); + send(tok, o_term, recv_term); + } +}