From 5c3b791349ef22e778754d5d1df73e2c37182b61 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 17 May 2023 17:15:08 +0200 Subject: [PATCH] Add a simple RLE encoder Currently, only a simple implementation is provided, which has no optimizations related to the RLE algorithm. The provided tests check if: * output of the encoder matches the expected values * sum of received symbols adds up to the amount of sent symbols * received symbols don't repeat unless the RLE counter overflowed or the end of transmission was requested (using `last` flag) Additional bazel rules added in this commit include: * Verilog code generation * IR benchmark generation * Verilog benchmark generation Internal-tag: [#44560] Co-authored-by: Maciej Dudek Signed-off-by: Robert Winkler Signed-off-by: Maciej Dudek --- xls/modules/rle/BUILD | 95 +++++++++ xls/modules/rle/rle.x | 449 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 544 insertions(+) create mode 100644 xls/modules/rle/BUILD create mode 100644 xls/modules/rle/rle.x diff --git a/xls/modules/rle/BUILD b/xls/modules/rle/BUILD new file mode 100644 index 0000000000..b4f7b6d3ef --- /dev/null +++ b/xls/modules/rle/BUILD @@ -0,0 +1,95 @@ +# 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 RLE Encoder implementation. + +load( + "//xls/build_rules:xls_build_defs.bzl", + "xls_dslx_library", + "xls_dslx_test", + "xls_dslx_ir", + "xls_ir_opt_ir", + "xls_ir_verilog", + "xls_benchmark_ir", +) + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//xls:xls_users"], + licenses = ["notice"], +) + +xls_dslx_library( + name = "rle_dslx", + srcs = ["rle.x"], +) + +xls_dslx_test( + name = "rle_dslx_test", + dslx_test_args = { + "compare": "none", + }, + library = "rle_dslx", +) + +xls_dslx_test( + name = "rle_dslx_ir_test", + dslx_test_args = { + "compare": "interpreter", + }, + library = "rle_dslx", +) + +xls_dslx_test( + name = "rle_dslx_jit_test", + dslx_test_args = { + "compare": "jit", + }, + library = "rle_dslx", +) + +xls_dslx_ir( + name = "rle_enc_ir", + dslx_top = "RunLengthEncoder32", + library = "rle_dslx", + ir_file = "rle_enc.ir", +) + +xls_ir_opt_ir( + name = "rle_enc_opt_ir", + src = "rle_enc.ir", + top = "__rle__RunLengthEncoder32__RunLengthEncoder_0__2_32_next", +) + +xls_ir_verilog( + name = "rle_enc_verilog", + src = ":rle_enc_opt_ir.opt.ir", + verilog_file = "rle_enc.v", + codegen_args = { + "module_name": "rle_enc", + "delay_model": "unit", + "pipeline_stages": "2", + "reset": "rst", + "use_system_verilog": "false", + }, +) + +xls_benchmark_ir( + name = "rle_enc_ir_benchmark", + src = ":rle_enc_opt_ir.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "2", + "delay_model": "unit", + } +) diff --git a/xls/modules/rle/rle.x b/xls/modules/rle/rle.x new file mode 100644 index 0000000000..4beac115b4 --- /dev/null +++ b/xls/modules/rle/rle.x @@ -0,0 +1,449 @@ +// 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 implements a parametric RLE encoder +// +// The encoder uses Run Length Encoding (RLE) to compress the input stream of +// repeating symbols to the output stream that contains the symbols and +// the number of its consequect occurrences in the input stream. +// Both the input and the output channels use additional `last` flag +// that indicates whether the packet ends the transmission. After sending +// the last packet encoder dumps all the data to the output stream. +// The behavior of the encoder is presented on the waveform below: +// ──────╥─────╥─────╥─────╥─────╥─────╥─────╥─────╥──── +// next evaluation XXXXXX║ 0 ║ 1 ║ 2 ║ 3 ║ 4 ║ 5 ║ 6 ║ ... +// ──────╨─────╨─────╨─────╨─────╨─────╨─────╨─────╨──── +// ──────╥───────────╥─────╥─────╥─────╥─────╥────────── +// symbol XXXXXX║ A ║ B ║XXXXX║ B ║ C ║XXXXXXXXXX +// (input channel) ──────╨───────────╨─────╨─────╨─────╨─────╨────────── +// last ┌─────┐ ┌─────┐ +// (input channel) ──────────────────┘ └───────────┘ └────────── +// ╥─────╥─────╥─────╥─────╥─────╥─────╥─────╥────────── +// state.prev_symbol ║ 0 ║ A ║ A ║ B ║ 0 ║ B ║ C ║ 0 +// (set state value) ╨─────╨─────╨─────╨─────╨─────╨─────╨─────╨────────── +// ╥─────╥─────╥─────╥─────╥─────╥─────╥─────╥────────── +// state.prev_count ║ 0 ║ 1 ║ 2 ║ 1 ║ 0 ║ 1 ║ 1 ║ 0 +// (set state value) ╨─────╨─────╨─────╨─────╨─────╨─────╨─────╨────────── +// +// do_send ┌───────────┐ ┌───────────┐ +// ──────────────────┘ └─────┘ └──── +// ──────────────────╥─────╥─────╥─────╥─────╥─────╥──── +// symbol, count XXXXXXXXXXXXXXXXXX║ A,2 ║ B,1 ║XXXXX║ B,1 ║ C,1 ║XXXX +// (output channel) ──────────────────╨─────╨─────╨─────╨─────╨─────╨──── +// last ┌─────┐ ┌─────┐ +// (output channel) ────────────────────────┘ └───────────┘ └──── + +import std + +// structure containing input data for an RLE encoder +pub struct EncInData { + symbol: bits[SYMBOL_WIDTH], // input symbol to compress + last: bool, // if this is the last symbol +} + +// structure containing output data from an RLE encoder +pub struct EncOutData { + symbol: bits[SYMBOL_WIDTH], // compressed symbol + count: bits[COUNT_WIDTH], // symbol count + last: bool, // if this is the last symbol +} + +// structure to preserve the state of an RLE encoder +struct RunLengthEncoderState { + // symbol from the previous RunLengthEncoder::next evaluation, + // valid if prev_count > 0 + prev_symbol: bits[SYMBOL_WIDTH], + // symbol count from the previous RunLengthEncoder::next evaluation. + // zero means that the previous evaluation sent all the data and + // we start counting from the beginning + prev_count: bits[COUNT_WIDTH], + // flag indicating that the previous symbol was the last one + // in the transmission + prev_last: bool, +} + +// RLE encoder implementation +pub proc RunLengthEncoder { + input_r: chan> in; + output_s: chan> out; + + init {( + RunLengthEncoderState { + prev_symbol: bits[SYMBOL_WIDTH]:0, + prev_count: bits[COUNT_WIDTH]:0, + prev_last: false, + } + )} + + config ( + input_r: chan> in, + output_s: chan> out, + ) {(input_r, output_s)} + + next (tok: token, state: RunLengthEncoderState) { + let zero_input = EncInData { + symbol: bits[SYMBOL_WIDTH]:0, + last: false + }; + let (input_tok, input) = recv_if( + tok, input_r, !state.prev_last, zero_input); + + let prev_symbol_valid = state.prev_count != bits[COUNT_WIDTH]:0; + let symbol_differ = prev_symbol_valid && ( + input.symbol != state.prev_symbol); + let overflow = + state.prev_count == std::unsigned_max_value(); + + let (symbol, count, last) = if (state.prev_last) { + ( + bits[SYMBOL_WIDTH]:0, + bits[COUNT_WIDTH]:0, + false + ) + } else if (symbol_differ || overflow) { + ( + input.symbol, + bits[COUNT_WIDTH]:1, + input.last, + ) + } else { + ( + input.symbol, + state.prev_count + bits[COUNT_WIDTH]:1, + input.last, + ) + }; + + let data = EncOutData { + symbol: state.prev_symbol, + count: state.prev_count, + last: state.prev_last + }; + + let do_send = state.prev_last || symbol_differ || overflow; + let data_tok = send_if(input_tok, output_s, do_send, data); + + RunLengthEncoderState { + prev_symbol: symbol, + prev_count: count, + prev_last: last, + } + } +} + +// RLE encoder specialization for the codegen +proc RunLengthEncoder32 { + + init {()} + + config ( + input_r: chan> in, + output_s: chan> out, + ) { + spawn RunLengthEncoder(input_r, output_s); + () + } + + next (tok: token, state: ()) { + () + } +} + +// Tests + +const TEST_COMMON_SYMBOL_WIDTH = u32:32; +// Make counter large enough so that it overflows only in overflow testcase. +const TEST_COMMON_COUNT_WIDTH = u32:32; + +type TestCommonSymbol = bits[TEST_COMMON_SYMBOL_WIDTH]; +type TestCommonCount = bits[TEST_COMMON_COUNT_WIDTH]; +type TestCommonEncInData = EncInData; +type TestCommonEncOutData = + EncOutData; + +// Simple transaction without overflow +const CountSymbolTestSymbolWidth = TEST_COMMON_SYMBOL_WIDTH; +const CountSymbolTestCountWidth = TEST_COMMON_COUNT_WIDTH; + +type CountSymbolTestStimulus = TestCommonSymbol; +type CountSymbolTestSymbol = TestCommonSymbol; +type CountSymbolTestCount = TestCommonCount; +type CountSymbolTestEncInData = TestCommonEncInData; +type CountSymbolTestEncOutData = TestCommonEncOutData; + +#[test_proc] +proc RunLengthEncoderCountSymbolTest { + terminator: chan out; + enc_input_s: chan out; + enc_output_r: chan in; + + init {()} + config (terminator: chan out) { + let (enc_input_s, enc_input_r) = chan; + let (enc_output_s, enc_output_r) = chan; + + spawn RunLengthEncoder( + enc_input_r, enc_output_s); + (terminator, enc_input_s, enc_output_r) + } + next (tok: token, state:()) { + let CountSymbolTestTestStimuli: CountSymbolTestStimulus[4] = [ + CountSymbolTestStimulus:0xA, CountSymbolTestStimulus:0xA, + CountSymbolTestStimulus:0xA, CountSymbolTestStimulus:0xB, + ]; + let tok = for ((counter, symbol), tok): + ((u32, CountSymbolTestStimulus) , token) + in enumerate(CountSymbolTestTestStimuli) { + let last = counter == (array_size(CountSymbolTestTestStimuli) - u32:1); + let stimulus = CountSymbolTestEncInData{symbol: symbol, last: last}; + let tok = send(tok, enc_input_s, stimulus); + trace_fmt!("Sent {} stimuli, symbol: 0x{:x}, last: {}", + counter, stimulus.symbol, stimulus.last); + (tok) + }(tok); + let CountSymbolTestTestOutput: + (CountSymbolTestSymbol, CountSymbolTestCount)[2] = [ + (CountSymbolTestSymbol:0xA, CountSymbolTestCount:0x3), + (CountSymbolTestSymbol:0xB, CountSymbolTestCount:0x1), + ]; + let tok = for ((counter, (symbol, count)), tok): + ((u32, (CountSymbolTestSymbol, CountSymbolTestCount)) , token) + in enumerate(CountSymbolTestTestOutput) { + let last = counter == (array_size(CountSymbolTestTestOutput) - u32:1); + let expected = CountSymbolTestEncOutData{ + symbol: symbol, count: count, last: last}; + let (tok, enc_output) = recv(tok, enc_output_r); + trace_fmt!( + "Received {} pairs, symbol: 0x{:x}, count: {}, last: {}", + counter, enc_output.symbol, enc_output.count, enc_output.last + ); + assert_eq(enc_output, expected); + (tok) + }(tok); + send(tok, terminator, true); + () + } +} + +// Transaction with counter overflow +const OverflowSymbolWidth = TEST_COMMON_SYMBOL_WIDTH; +const OverflowCountWidth = u32:2; + +type OverflowStimulus = TestCommonSymbol; +type OverflowSymbol = TestCommonSymbol; +type OverflowCount = bits[OverflowCountWidth]; +type OverflowEncInData = TestCommonEncInData; +type OverflowEncOutData = + EncOutData; + +#[test_proc] +proc RunLengthEncoderOverflowTest { + terminator: chan out; + enc_input_s: chan out; + enc_output_r: chan in; + + init {()} + config (terminator: chan out) { + let (enc_input_s, enc_input_r) = chan; + let (enc_output_s, enc_output_r) = chan; + + spawn RunLengthEncoder( + enc_input_r, enc_output_s); + (terminator, enc_input_s, enc_output_r) + } + next (tok: token, state:()) { + let OverflowTestStimuli: OverflowStimulus[14] = [ + OverflowStimulus:0xB, OverflowStimulus:0xB, + OverflowStimulus:0x1, OverflowStimulus:0xC, + OverflowStimulus:0xC, OverflowStimulus:0xC, + OverflowStimulus:0xC, OverflowStimulus:0xC, + OverflowStimulus:0xC, OverflowStimulus:0x3, + OverflowStimulus:0x3, OverflowStimulus:0x3, + OverflowStimulus:0x2, OverflowStimulus:0x2, + ]; + let tok = for ((counter, symbol), tok): + ((u32, OverflowStimulus) , token) + in enumerate(OverflowTestStimuli) { + let last = counter == ( + array_size(OverflowTestStimuli) - u32:1); + let stimulus = OverflowEncInData{symbol: symbol, last: last}; + let tok = send(tok, enc_input_s, stimulus); + trace_fmt!("Sent {} stimuli, symbol: 0x{:x}, last: {}", + counter, stimulus.symbol, stimulus.last); + (tok) + }(tok); + let OverflowTestOutput: + (OverflowSymbol, OverflowCount)[6] = [ + (OverflowSymbol:0xB, OverflowCount:0x2), + (OverflowSymbol:0x1, OverflowCount:0x1), + (OverflowSymbol:0xC, OverflowCount:0x3), + (OverflowSymbol:0xC, OverflowCount:0x3), + (OverflowSymbol:0x3, OverflowCount:0x3), + (OverflowSymbol:0x2, OverflowCount:0x2), + ]; + let tok = for ((counter, (symbol, count)), tok): + ((u32, (OverflowSymbol, OverflowCount)) , token) + in enumerate(OverflowTestOutput) { + let last = counter == (array_size(OverflowTestOutput) - u32:1); + let expected = OverflowEncOutData{ + symbol: symbol, count: count, last: last}; + let (tok, enc_output) = recv(tok, enc_output_r); + trace_fmt!( + "Received {} pairs, symbol: 0x{:x}, count: {}, last: {}", + counter, enc_output.symbol, enc_output.count, enc_output.last + ); + assert_eq(enc_output, expected); + (tok) + }(tok); + send(tok, terminator, true); + () + } +} + +// Check that RLE encoder will create 2 `last` output packets, +// when 2 `last` input packets were consumed. +const LastAfterLastSymbolWidth = TEST_COMMON_SYMBOL_WIDTH; +const LastAfterLastCountWidth = TEST_COMMON_COUNT_WIDTH; + +type LastAfterLastStimulus = TestCommonEncInData; +type LastAfterLastSymbol = TestCommonSymbol; +type LastAfterLastCount = TestCommonCount; +type LastAfterLastEncInData = TestCommonEncInData; +type LastAfterLastEncOutData = TestCommonEncOutData; +type LastAfterLastOutput = TestCommonEncOutData; + +#[test_proc] +proc RunLengthEncoderLastAfterLastTest { + terminator: chan out; + enc_input_s: chan out; + enc_output_r: chan in; + + init {()} + config (terminator: chan out) { + let (enc_input_s, enc_input_r) = chan; + let (enc_output_s, enc_output_r) = chan; + + spawn RunLengthEncoder( + enc_input_r, enc_output_s); + (terminator, enc_input_s, enc_output_r) + } + next (tok: token, state:()) { + let LastAfterLastTestStimuli: LastAfterLastStimulus[2] = [ + LastAfterLastStimulus {symbol: LastAfterLastSymbol:0x1, last: true}, + LastAfterLastStimulus {symbol: LastAfterLastSymbol:0x1, last: true}, + ]; + let tok = for ((counter, stimuli), tok): + ((u32, LastAfterLastStimulus) , token) + in enumerate(LastAfterLastTestStimuli) { + let tok = send(tok, enc_input_s, stimuli); + trace_fmt!("Sent {} transactions, symbol: 0x{:x}, last: {}", + counter, stimuli.symbol, stimuli.last); + (tok) + }(tok); + let LastAfterLastTestOutput: LastAfterLastOutput[2] = [ + LastAfterLastOutput { + symbol: LastAfterLastSymbol:0x1, + count: LastAfterLastCount:0x1, + last:true}, + LastAfterLastOutput { + symbol: LastAfterLastSymbol:0x1, + count: LastAfterLastCount:0x1, + last:true}, + ]; + let tok = for ((counter, expected), tok): + ((u32, LastAfterLastOutput) , token) + in enumerate(LastAfterLastTestOutput) { + let (tok, enc_output) = recv(tok, enc_output_r); + trace_fmt!( + "Received {} pairs, symbol: 0x{:x}, count: {}, last: {}", + counter, enc_output.symbol, enc_output.count, enc_output.last + ); + assert_eq(enc_output, expected); + + (tok) + }(tok); + send(tok, terminator, true); + () + } +} + +// Check overflow condition trigger on packet with `last` +const OverflowWithLastSymbolWidth = TEST_COMMON_SYMBOL_WIDTH; +const OverflowWithLastCountWidth = u32:2; + +type OverflowWithLastStimulus = TestCommonSymbol; +type OverflowWithLastSymbol = TestCommonSymbol; +type OverflowWithLastCount = + bits[OverflowWithLastCountWidth]; +type OverflowWithLastEncInData = TestCommonEncInData; +type OverflowWithLastEncOutData = + EncOutData; + +#[test_proc] +proc RunLengthEncoderOverflowWithLastTest { + terminator: chan out; + enc_input_s: chan out; + enc_output_r: chan in; + + init {()} + config (terminator: chan out) { + let (enc_input_s, enc_input_r) = chan; + let (enc_output_s, enc_output_r) = + chan; + + spawn RunLengthEncoder( + enc_input_r, enc_output_s); + (terminator, enc_input_s, enc_output_r) + } + next (tok: token, state:()) { + let OverflowWithLastTestStimuli: OverflowWithLastStimulus[4] = [ + OverflowWithLastStimulus:0xC, OverflowWithLastStimulus:0xC, + OverflowWithLastStimulus:0xC, OverflowWithLastStimulus:0xC, + ]; + let tok = for ((counter, symbol), tok): + ((u32, OverflowWithLastStimulus) , token) + in enumerate(OverflowWithLastTestStimuli) { + let last = counter == ( + array_size(OverflowWithLastTestStimuli) - u32:1); + let stimulus = OverflowWithLastEncInData{symbol: symbol, last: last}; + let tok = send(tok, enc_input_s, stimulus); + trace_fmt!("Sent {} stimuli, symbol: 0x{:x}, last: {}", + counter, stimulus.symbol, stimulus.last); + (tok) + }(tok); + let OverflowWithLastTestOutput: + (OverflowWithLastSymbol, OverflowWithLastCount)[2] = [ + (OverflowWithLastSymbol:0xC, OverflowWithLastCount:0x3), + (OverflowWithLastSymbol:0xC, OverflowWithLastCount:0x1), + ]; + let tok = for ((counter, (symbol, count)), tok): + ((u32, (OverflowWithLastSymbol, OverflowWithLastCount)) , token) + in enumerate(OverflowWithLastTestOutput) { + let last = counter == (array_size(OverflowWithLastTestOutput) - u32:1); + let expected = OverflowWithLastEncOutData{ + symbol: symbol, count: count, last: last}; + let (tok, enc_output) = recv(tok, enc_output_r); + trace_fmt!( + "Received {} pairs, symbol: 0x{:x}, count: {}, last: {}", + counter, enc_output.symbol, enc_output.count, enc_output.last + ); + assert_eq(enc_output, expected); + (tok) + }(tok); + send(tok, terminator, true); + () + } +}