diff --git a/dependency_support/pip_requirements.txt b/dependency_support/pip_requirements.txt index c1337074ac..2e9ce1e12d 100644 --- a/dependency_support/pip_requirements.txt +++ b/dependency_support/pip_requirements.txt @@ -8,6 +8,8 @@ termcolor==1.1.0 psutil==5.7.0 portpicker==1.3.1 pyyaml==5.4.1 +cocotb==1.8.0 +cocotb_bus==0.2.1 # 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/rules_hdl/workspace.bzl b/dependency_support/rules_hdl/workspace.bzl index fcb5b6def8..06ef9c5a94 100644 --- a/dependency_support/rules_hdl/workspace.bzl +++ b/dependency_support/rules_hdl/workspace.bzl @@ -29,9 +29,9 @@ def repo(): ], ) - # Commit on 2023-06-13, current as of 2023-06-13. - git_hash = "e6540a5bccbfb124aec0b19deaa9cf855781b3a5" - git_sha256 = "21307b0c14a036f1b4879c8f1d4d50a115053eb87c428307d4d6569c3e7ba859" + # Commit on 2023-07-07, current as of 2023-07-07. + git_hash = "83b51f4b10e0e8970f88b087b5fab3f90eb89834" + git_sha256 = "506d4e7ad746cf3fbeeb2e073667a940612bdd2a258ed9fe637d0c9f7d5c8930" maybe( http_archive, @@ -39,6 +39,6 @@ def repo(): sha256 = git_sha256, strip_prefix = "bazel_rules_hdl-%s" % git_hash, urls = [ - "https://github.com/hdl/bazel_rules_hdl/archive/%s.tar.gz" % git_hash, + "https://github.com/antmicro/bazel_rules_hdl/archive/%s.tar.gz" % git_hash, ], ) diff --git a/xls/build_rules/cocotb_xls_test.bzl b/xls/build_rules/cocotb_xls_test.bzl new file mode 100644 index 0000000000..552a55e519 --- /dev/null +++ b/xls/build_rules/cocotb_xls_test.bzl @@ -0,0 +1,37 @@ +# 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. + +load("@rules_hdl//cocotb:cocotb.bzl", "cocotb_test") + +def cocotb_xls_test(**kwargs): + name = kwargs["name"] + top = kwargs["hdl_toplevel"] + + if "timescale" in kwargs: + timescale = kwargs["timescale"] + timestamp_target = name + "-timescale" + timestamp_verilog = name + "_timescale.v" + native.genrule( + name = timestamp_target, + srcs = [], + cmd = "echo \\`timescale {}/{} > $@".format( + timescale["unit"], + timescale["precission"], + ), + outs = [timestamp_verilog], + ) + kwargs["verilog_sources"].insert(0, timestamp_verilog) + kwargs.pop("timescale") + + cocotb_test(**kwargs) diff --git a/xls/examples/cocotb/BUILD b/xls/examples/cocotb/BUILD new file mode 100644 index 0000000000..bbe56df4d3 --- /dev/null +++ b/xls/examples/cocotb/BUILD @@ -0,0 +1,55 @@ +# 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. + +load("@xls_pip_deps//:requirements.bzl", "requirement") +load("//xls/build_rules:xls_build_defs.bzl", "xls_dslx_verilog") +load("//xls/build_rules:cocotb_xls_test.bzl", "cocotb_xls_test") + +xls_dslx_verilog( + name = "running_counter_verilog", + srcs = ["running_counter.x"], + deps = [], + codegen_args = { + "delay_model": "unit", + "clock_period_ps": "5000", + "reset": "rst", + "module_name": "RunningCounter", + "use_system_verilog": "false", + "streaming_channel_data_suffix": "_data", + }, + dslx_top = "RunningCounter", + verilog_file = "running_counter.v", +) + +cocotb_xls_test( + name = "running_counter_cocotb", + hdl_toplevel = "RunningCounter", + hdl_toplevel_lang = "verilog", + test_module = [ + "cocotb_running_counter.py", + ], + verilog_sources = [ + "dumpvcd.v", + ":running_counter.v", + ], + timescale = { + "unit": "1ns", + "precission": "1ps", + }, + deps = [ + requirement("cocotb"), + requirement("cocotb_bus"), + "//xls/simulation/cocotb:cocotb_xls", + ], +) diff --git a/xls/examples/cocotb/cocotb_running_counter.py b/xls/examples/cocotb/cocotb_running_counter.py new file mode 100644 index 0000000000..e33aa09c18 --- /dev/null +++ b/xls/examples/cocotb/cocotb_running_counter.py @@ -0,0 +1,70 @@ +# 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 cocotb + +from cocotb.clock import Clock +from cocotb.triggers import Event, ClockCycles +from cocotb.binary import BinaryValue + +from cocotb_bus.scoreboard import Scoreboard +from cocotb_xls import XLSChannelDriver, XLSChannelMonitor + +from typing import List, Any + + +def list_to_binary_value(data: List, n_bits: int = 32, bigEndian=False) -> List[BinaryValue]: + return [BinaryValue(x, n_bits, bigEndian) for x in data] + + +def init_sim(dut, data_to_send: List[BinaryValue], data_to_recv: List[BinaryValue]): + clock = Clock(dut.clk, 10, units="us") + + driver = XLSChannelDriver(dut, "running_counter__base_r", dut.clk) + monitor = XLSChannelMonitor(dut, "running_counter__cnt_s", dut.clk) + + scoreboard = Scoreboard(dut, fail_immediately=True) + scoreboard.add_interface(monitor, data_to_recv) + + total_of_packets = len(data_to_recv) + terminate = Event("Last packet of data received") + + def terminate_cb(transaction): + if monitor.stats.received_transactions == total_of_packets: + terminate.set() + + monitor.add_callback(terminate_cb) + monitor.bus.rdy.setimmediatevalue(1) + + return (clock, driver, terminate) + + +@cocotb.coroutine +async def reset(clk, rst, cycles=1): + rst.setimmediatevalue(1) + await ClockCycles(clk, cycles) + rst.value = 0 + + +@cocotb.test(timeout_time=10, timeout_unit="ms") +async def counter_test(dut): + data_to_send = list_to_binary_value([0x100, 0x100, 0x100]) + data_to_recv = list_to_binary_value([0x102, 0x103, 0x104]) + + (clock, driver, terminate) = init_sim(dut, data_to_send, data_to_recv) + + cocotb.start_soon(clock.start()) + await reset(dut.clk, dut.rst, 10) + await driver.write(data_to_send) + await terminate.wait() diff --git a/xls/examples/cocotb/dumpvcd.v b/xls/examples/cocotb/dumpvcd.v new file mode 100644 index 0000000000..a3b6ac1c5e --- /dev/null +++ b/xls/examples/cocotb/dumpvcd.v @@ -0,0 +1,20 @@ +// 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. + +module wavedump(); +initial begin + $dumpfile("dump.vcd"); + $dumpvars(0, RunningCounter); +end +endmodule diff --git a/xls/examples/cocotb/running_counter.x b/xls/examples/cocotb/running_counter.x new file mode 100644 index 0000000000..28cd3ca42a --- /dev/null +++ b/xls/examples/cocotb/running_counter.x @@ -0,0 +1,67 @@ +// 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 + +proc RunningCounter { + base_r: chan in; + cnt_s: chan out; + + init { u32:0 } + + config(base_r: chan in, cnt_s: chan out) { + (base_r, cnt_s) + } + + next(tok: token, cnt: u32) { + let (tok, base, base_valid) = recv_non_blocking(tok, base_r, u32:0); + let tok = send_if(tok, cnt_s, base_valid, cnt + base); + let cnt_next = cnt + u32:1; + cnt_next + } +} + +#[test_proc] +proc RunningCounterTester { + terminator: chan out; + base_s: chan out; + cnt_r: chan in; + + init { u32:0 } + + config (terminator: chan out) { + let (base_s, base_r) = chan; + let (cnt_s, cnt_r) = chan; + + spawn RunningCounter(base_r, cnt_s); + (terminator, base_s, cnt_r) + } + + next(tok: token, recv_cnt: u32) { + let next_state = if (recv_cnt < u32:10) { + let tok = send(tok, base_s, u32:1); + let (tok, cnt) = recv(tok, cnt_r); + + trace_fmt!("Received {} cnt", recv_cnt); + assert_lt(u32:1, cnt); + + recv_cnt + u32:1 + } else { + send(tok, terminator, true); + u32:0 + }; + + (next_state) + } +} diff --git a/xls/examples/cocotb/timescale.v b/xls/examples/cocotb/timescale.v new file mode 100644 index 0000000000..a8b8dbbcbc --- /dev/null +++ b/xls/examples/cocotb/timescale.v @@ -0,0 +1,15 @@ +// 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. + +`timescale 1ns/1ps diff --git a/xls/examples/ram.x b/xls/examples/ram.x index ae9a108225..9ae9eb88db 100644 --- a/xls/examples/ram.x +++ b/xls/examples/ram.x @@ -273,7 +273,7 @@ proc RamModel { - data: unmasked_read_value & read_req.mask, + data: unmasked_read_value & expand_mask(read_req.mask), }; let tok = send_if(tok, read_resp, read_req_valid, read_resp_value); diff --git a/xls/modules/dbe/BUILD b/xls/modules/dbe/BUILD new file mode 100644 index 0000000000..172719d77e --- /dev/null +++ b/xls/modules/dbe/BUILD @@ -0,0 +1,139 @@ +# 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", +) + +load("@xls_pip_deps//:requirements.bzl", "requirement") +load("//xls/build_rules:cocotb_xls_test.bzl", "cocotb_xls_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" + ], +) + +py_library( + name = "dbe_common_test_py", + srcs = [ + "dbe_common_test.py", + ], + imports = [ + "." + ], + deps = [ + "//xls/modules/dbe/scripts:dbe", + requirement("cocotb"), + ], +) + +# --------------------------------------------------------------------------- +# 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", +) + +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", + "io_constraints": ",".join([ + "lz4_decoder__o_ram_hb_rd_req:send:lz4_decoder__i_ram_hb_rd_resp:recv:1:none", + "lz4_decoder__o_ram_hb_wr_req:send:lz4_decoder__i_ram_hb_wr_resp:recv:1:none", + ]), + }, +) + +cocotb_xls_test( + name = "dbe_lz4_decoder_cocotb_test", + sim_name = "icarus", + hdl_toplevel = "lz4_decoder_wrap", + hdl_toplevel_lang = "verilog", + test_module = [ + "lz4_decoder_test.py", + ], + verilog_sources = [ + ":dbe_lz4_decoder.v", + "ram_model.v", + "lz4_decoder_wrap.v", + ], + timescale = { + "unit": "1ns", + "precission": "1ps", + }, + deps = [ + "dbe_common_test_py", + "//xls/modules/dbe/scripts:dbe", + "//xls/simulation/cocotb:cocotb_xls", + requirement("cocotb"), + requirement("cocotb_bus"), + ], +) diff --git a/xls/modules/dbe/common.x b/xls/modules/dbe/common.x new file mode 100644 index 0000000000..f3d3f79d93 --- /dev/null +++ b/xls/modules/dbe/common.x @@ -0,0 +1,75 @@ +// 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 { + LT = 0, // Literal + CP = 1, // Copy pointer + MK = 2, // Control marker +} + +pub struct Token { + kind: TokenKind, + // Literal fields + lt_sym: uN[SYM_WIDTH], + // Copy pointer fields + cp_off: uN[PTR_WIDTH], + cp_cnt: uN[CNT_WIDTH], + // Marker fields + mark: Mark +} + +pub fn zero_token() + -> Token { + Token{ + kind: TokenKind::MK, + lt_sym: uN[SYM_WIDTH]:0, + cp_off: uN[PTR_WIDTH]:0, + cp_cnt: uN[CNT_WIDTH]:0, + mark: Mark::NONE, + } +} + +pub struct PlainData { + is_mark: bool, + data: uN[DATA_WIDTH], // symbol + mark: Mark, // marker code +} + +pub fn zero_plain_data() -> PlainData { + PlainData{ + is_mark: true, + data: uN[DATA_WIDTH]:0, + mark: Mark::NONE, + } +} diff --git a/xls/modules/dbe/common_test.x b/xls/modules/dbe/common_test.x new file mode 100644 index 0000000000..59acf91e2a --- /dev/null +++ b/xls/modules/dbe/common_test.x @@ -0,0 +1,107 @@ +// 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 Mark = dbe::Mark; +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_validator { + ref_syms: uN[SYM_WIDTH][NUM_SYMS]; + i_data: chan> in; + o_term: chan out; + + init { u32:0 } + + config( + ref_syms: uN[SYM_WIDTH][NUM_SYMS], + i_data: chan> in, + o_term: chan out + ) { + (ref_syms, i_data, o_term) + } + + next (tok: token, state: u32) { + // state = [0, NUM_SYMS-1] - expect symbol + // state = NUM_SYMS - expect END + // state = NUM_SYMS+1 - expect nothing + let next_idx = state + u32:1; + let is_end = (state == NUM_SYMS); + 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 if (is_end) { + let fail = if (!rx.is_mark || rx.mark != Mark::END) { + trace_fmt!("MISMATCH! Expected END mark, got {}", rx); + true + } else { + false + }; + (fail, next_idx) + } else { + let exp_sym = ref_syms[state]; + let fail = if (rx.is_mark || rx.data != exp_sym) { + trace_fmt!("MISMATCH! Expected sym:{}, got {}", exp_sym, 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 + } + } +} diff --git a/xls/modules/dbe/dbe_common_test.py b/xls/modules/dbe/dbe_common_test.py new file mode 100644 index 0000000000..b8446bda7b --- /dev/null +++ b/xls/modules/dbe/dbe_common_test.py @@ -0,0 +1,199 @@ +# 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. + + +from cocotb.binary import BinaryValue +from dbe import MarkedByte, TokMK, TokCP, TokLT, Token, Mark +import enum +from typing import List, Tuple, Mapping, Any, Type + + +class Struct: + _STRUCT_FIELDS_ = [] + + @classmethod + def from_int(cls, v: int): + return cls(v) + + @classmethod + def from_fields(cls, *fields_seq, **fields_dict): + if fields_seq: + return cls(cls._fields_to_int(fields_seq)) + else: + fields = cls._fields_dict_to_list(fields_dict) + return cls(cls._fields_to_int(fields)) + + @classmethod + def from_binary(cls, bin: BinaryValue): + if bin.n_bits != cls._structlen(): + raise ValueError( + f'Given {bin.n_bits} bits value, expect {cls._structlen()}') + return cls.from_int(bin.integer) + + @classmethod + def _fields(cls) -> List[Tuple[str, int, Type]]: + return list(reversed(cls._STRUCT_FIELDS_)) + + @classmethod + def _structlen(cls) -> int: + return sum(d[1] for d in cls._fields()) + + @classmethod + def _masks(cls) -> List[Tuple[int, int]]: + r = [] + off = 0 + for f in cls._fields(): + assert f[1] >= 1, f"Field {f} width should be at least 1" + mask = (1 << f[1]) - 1 + r.append((off, mask)) + off += f[1] + return r + + @classmethod + def _int_to_fields(cls, v: int) -> List[Any]: + defs = cls._fields() + masks = cls._masks() + r = [] + for d, m in zip(defs, masks): + t = d[2] + r.append(t((v >> m[0]) & m[1])) + return r + + @classmethod + def _fields_to_int(cls, fields: List[Any]) -> int: + defs = cls._fields() + masks = cls._masks() + if len(fields) != len(defs): + raise RuntimeError( + f"Need {len(defs)} fields, passed: {fields}") + r = 0 + for f, d, m in zip(fields, defs, masks): + i = int(f) + # TODO: signed ints will need special handling + if (i & m[1]) != i: + raise ValueError( + f'Field {d[0]} was given value {f!r} (int {i}) that is out of range') + r |= (i & m[1]) << m[0] + return r + + @classmethod + def _fields_dict_to_list(cls, dct: Mapping[str, Any]) -> List[Any]: + r = [] + for d in cls._fields(): + r.append(dct[d[0]]) + return r + + def __init__(self, v: int): + fields = self._int_to_fields(v) + for f, d in zip(fields, self._fields()): + setattr(self, d[0], d[2](f)) + + def __int__(self) -> int: + return self._fields_to_int(self._fields_dict_to_list(self.__dict__)) + + def __repr__(self) -> str: + attrs = ','.join(f'{d[0]}={getattr(self, d[0])}' + for d in reversed(self._fields())) + return f'{self.__class__.__name__}({attrs})' + + def as_binary(self) -> BinaryValue: + b = BinaryValue( + value=int(self), n_bits=self._structlen(), bigEndian=False) + return b + + # Convenience methods + @staticmethod + def to_int_list(values: List[Any]) -> List[int]: + return [int(x) for x in values] + + @classmethod + def to_binary_list(cls, values: List[Any]) -> List[BinaryValue]: + return [BinaryValue(int(x), cls._structlen()) for x in values] + + +class DslxPlainData8(Struct): + _STRUCT_FIELDS_ = [ + ('is_mark', 1, bool), + ('data', 8, int), + ('mark', 4, Mark), + ] + + @classmethod + def from_markedbyte(cls, value: MarkedByte): + if isinstance(value, Mark): + d = { + 'is_mark': True, + 'mark': value, + 'data': 0 + } + else: + d = { + 'is_mark': False, + 'mark': Mark.NONE, + 'data': value + } + return cls.from_fields(**d) + + def to_markedbyte(self) -> MarkedByte: + if self.is_mark: + return Mark(self.mark) + return self.data + + +class DslxToken(Struct): + class Kind(enum.IntEnum): + LT = 0 + CP = 1 + MK = 2 + + _STRUCT_FIELDS_ = [ + ('kind', 2, Kind), + ('lt_sym', 8, int), + ('cp_off', 16, int), + ('cp_cnt', 16, int), + ('mark', 4, Mark) + ] + + @classmethod + def from_token(cls, tok: Token): + d = { + 'kind': cls.Kind.MK, + 'lt_sym': 0, + 'cp_off': 0, + 'cp_cnt': 0, + 'mark': Mark.NONE + } + if isinstance(tok, TokMK): + d['mark'] = tok.mark + elif isinstance(tok, TokCP): + assert tok.cnt >= 1 + d['kind'] = cls.Kind.CP + d['cp_off'] = tok.ofs + d['cp_cnt'] = tok.cnt - 1 + elif isinstance(tok, TokLT): + d['kind'] = cls.Kind.LT + d['lt_sym'] = tok.sym + else: + raise TypeError(f'Unexpected token type: {tok!r} ({type(tok)})') + return cls.from_fields(**d) + + def to_token(self) -> Token: + if self.kind == self.Kind.LT: + return TokLT(self.lt_sym) + elif self.kind == self.Kind.CP: + return TokCP(self.cp_off, self.cp_cnt + 1) + elif self.kind == self.Kind.MK: + return TokMK(self.mark) + else: + return RuntimeError(f'Decoding unsupported token kind: {self}') diff --git a/xls/modules/dbe/lz4_decoder.x b/xls/modules/dbe/lz4_decoder.x new file mode 100644 index 0000000000..bbf6ecfb48 --- /dev/null +++ b/xls/modules/dbe/lz4_decoder.x @@ -0,0 +1,518 @@ +// 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 RamWriteReq = ram::WriteReq; +type RamWriteResp = ram::WriteResp; +type RamReadReq = ram::ReadReq; +type RamReadResp = ram::ReadResp; + + +fn RamZeroReadReq + () -> RamReadReq { + RamReadReq { + addr:uN[ADDR_WIDTH]:0, + mask:uN[NUM_PARTITIONS]:0, + } +} + +fn RamZeroReadResp + () -> RamReadResp { + RamReadResp { + data:uN[DATA_WIDTH]:0, + } +} + +fn RamZeroWriteReq + () -> RamWriteReq { + RamWriteReq { + addr:uN[ADDR_WIDTH]:0, + data:uN[DATA_WIDTH]:0, + mask:uN[NUM_PARTITIONS]:0, + } +} + +fn RamZeroWriteResp() -> RamWriteResp { + RamWriteResp {} +} + +/// 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 CP 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 { + i_encoded: chan> in; + o_data: chan> out; + + /// History buffer RAM + /// Size: (1<> out; + i_ram_hb_wr_resp: chan in; + o_ram_hb_rd_req: chan> out; + i_ram_hb_rd_resp: 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 ( + i_encoded: chan> in, + o_symb: chan> out, + // RAM + o_ram_hb_wr_req: chan> out, + i_ram_hb_wr_resp: chan in, + o_ram_hb_rd_req: chan> out, + i_ram_hb_rd_resp: chan> in, + ) { + ( + i_encoded, o_symb, + o_ram_hb_wr_req, i_ram_hb_wr_resp, + o_ram_hb_rd_req, i_ram_hb_rd_resp, + ) + } + + next (tok: token, state: State) { + let is_copying = state.rd_ctr_rem != uN[CNT_WIDTH]:0; + let do_recv = !is_copying; + + // Receive token + let (tok, rx) = recv_if(tok, i_encoded, do_recv, + dbe::zero_token()); + + let rx_lt = do_recv && rx.kind == TokenKind::LT; + let rx_cp = do_recv && rx.kind == TokenKind::CP; + let rx_mark = do_recv && rx.kind == TokenKind::MK; + 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.cp_off - 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, o_ram_hb_rd_req, do_hb_read, + ram::ReadWordReq(rd_ptr)); + let (tok, hb_rd) = recv_if(tok, i_ram_hb_rd_resp, do_hb_read, + RamZeroReadResp()); + + // Generate ERROR_INVAL_CP when we should've read from HB but + // we couldn't because CP 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 = dbe::zero_plain_data(); + let (data_valid, data) = if rx_reset { + // Propagate RESET in all states + (true, PlainData { + 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, PlainData { + is_mark: true, + mark: rx.mark, + ..zero_data + }) + } else if rx_unexpected_mark { + // Generate ERROR_BAD_MARK + (true, PlainData { + is_mark: true, + mark: Mark::ERROR_BAD_MARK, + ..zero_data + }) + } else if err_inval_cp { + // Generate ERROR_INVAL_CP + (true, PlainData { + is_mark: true, + mark: Mark::ERROR_INVAL_CP, + ..zero_data + }) + } else if emit_sym_lit { + // Replicate symbol from LT token + (true, PlainData { + is_mark: false, + data: rx.lt_sym, + ..zero_data + }) + } else if emit_sym_from_hb { + // Replicate symbol from HB + (true, PlainData { + is_mark: false, + data: hb_rd.data, + ..zero_data + }) + } else { + (false, zero_data) + }; + let tok = send_if(tok, o_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, o_ram_hb_wr_req, do_hb_write, + ram::WriteWordReq(state.wr_ptr, data.data)); + let (tok, _) = recv_if(tok, i_ram_hb_wr_resp, do_hb_write, + RamZeroWriteResp()); + + // 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.cp_cnt + } 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 ( + i_encoded: chan> in, + o_data: chan> out, + ) { + let (o_hb_wr_req, i_hb_wr_req) = + chan>; + let (o_hb_wr_resp, i_hb_wr_resp) = + chan; + let (o_hb_rd_req, i_hb_rd_req) = + chan>; + let (o_hb_rd_resp, i_hb_rd_resp) = + chan>; + + spawn ram::RamModel + (i_hb_rd_req, o_hb_rd_resp, i_hb_wr_req, o_hb_wr_resp); + + spawn decoder_base + ( + i_encoded, o_data, + o_hb_wr_req, i_hb_wr_resp, + o_hb_rd_req, i_hb_rd_resp, + ); + } + + 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 ( + i_encoded: + chan> in, + o_data: chan> out, + o_ram_hb_wr_req: + chan> out, + i_ram_hb_wr_resp: chan in, + o_ram_hb_rd_req: chan> out, + i_ram_hb_rd_resp: chan> in, + ) { + spawn decoder_base + + ( + i_encoded, o_data, + o_ram_hb_wr_req, i_ram_hb_wr_resp, + o_ram_hb_rd_req, i_ram_hb_rd_resp, + ); + } + + next(tok: token, st: ()) { + } +} + +/// Version of `decoder` with embedded RamModel +/// Intended to be used only for tests +pub proc decoder_modelram { + init{()} + + config ( + i_encoded: + chan> in, + o_symb: chan> out, + ) { + let (o_hb_wr_req, i_hb_wr_req) = + chan>; + let (o_hb_wr_resp, i_hb_wr_resp) = + chan; + let (o_hb_rd_req, i_hb_rd_req) = + chan>; + let (o_hb_rd_resp, i_hb_rd_resp) = + chan>; + + spawn ram::RamModel + + (i_hb_rd_req, o_hb_rd_resp, i_hb_wr_req, o_hb_wr_resp); + + spawn decoder + ( + i_encoded, o_symb, + o_hb_wr_req, i_hb_wr_resp, + o_hb_rd_req, i_hb_rd_resp, + ); + } + + 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::LT, lt_sym: uN[4]:12, cp_off: uN[3]:0, cp_cnt: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::LT, lt_sym: uN[4]: 1, cp_off: uN[3]:0, cp_cnt: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::LT, lt_sym: uN[4]:15, cp_off: uN[3]:0, cp_cnt: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::LT, lt_sym: uN[4]: 9, cp_off: uN[3]:0, cp_cnt: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::LT, lt_sym: uN[4]:11, cp_off: uN[3]:0, cp_cnt: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::CP, lt_sym: uN[4]:0, cp_off: uN[3]: 1, cp_cnt: uN[4]: 1, mark: Mark::NONE}, + Token{kind: TokenKind::CP, lt_sym: uN[4]:0, cp_off: uN[3]: 0, cp_cnt: uN[4]: 2, mark: Mark::NONE}, + Token{kind: TokenKind::CP, lt_sym: uN[4]:0, cp_off: uN[3]: 4, cp_cnt: uN[4]: 0, mark: Mark::NONE}, + Token{kind: TokenKind::LT, lt_sym: uN[4]:15, cp_off: uN[3]:0, cp_cnt: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::CP, lt_sym: uN[4]:0, cp_off: uN[3]: 1, cp_cnt: uN[4]: 0, mark: Mark::NONE}, + Token{kind: TokenKind::CP, lt_sym: uN[4]:0, cp_off: uN[3]: 2, cp_cnt: uN[4]:13, mark: Mark::NONE}, + Token{kind: TokenKind::LT, lt_sym: uN[4]: 1, cp_off: uN[3]:0, cp_cnt: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::CP, lt_sym: uN[4]:0, cp_off: uN[3]: 5, cp_cnt: uN[4]: 0, mark: Mark::NONE}, + Token{kind: TokenKind::CP, lt_sym: uN[4]:0, cp_off: uN[3]: 5, cp_cnt: uN[4]: 2, mark: Mark::NONE}, + Token{kind: TokenKind::LT, lt_sym: uN[4]: 7, cp_off: uN[3]:0, cp_cnt: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::LT, lt_sym: uN[4]: 2, cp_off: uN[3]:0, cp_cnt: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::CP, lt_sym: uN[4]:0, cp_off: uN[3]: 0, cp_cnt: uN[4]:11, mark: Mark::NONE}, + Token{kind: TokenKind::CP, lt_sym: uN[4]:0, cp_off: uN[3]: 1, cp_cnt: uN[4]: 6, mark: Mark::NONE}, + Token{kind: TokenKind::CP, lt_sym: uN[4]:0, cp_off: uN[3]: 5, cp_cnt: uN[4]:15, mark: Mark::NONE}, + Token{kind: TokenKind::LT, lt_sym: uN[4]: 3, cp_off: uN[3]:0, cp_cnt: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::LT, lt_sym: uN[4]: 9, cp_off: uN[3]:0, cp_cnt: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::CP, lt_sym: uN[4]:0, cp_off: uN[3]: 5, cp_cnt: uN[4]: 1, mark: Mark::NONE}, + Token{kind: TokenKind::CP, lt_sym: uN[4]:0, cp_off: uN[3]: 3, cp_cnt: uN[4]:13, mark: Mark::NONE}, + Token{kind: TokenKind::LT, lt_sym: uN[4]:14, cp_off: uN[3]:0, cp_cnt: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::CP, lt_sym: uN[4]:0, cp_off: uN[3]: 1, cp_cnt: uN[4]: 2, mark: Mark::NONE}, + Token{kind: TokenKind::CP, lt_sym: uN[4]:0, cp_off: uN[3]: 0, cp_cnt: uN[4]: 0, mark: Mark::NONE}, + Token{kind: TokenKind::CP, lt_sym: uN[4]:0, cp_off: uN[3]: 7, cp_cnt: uN[4]: 0, mark: Mark::NONE}, + Token{kind: TokenKind::LT, lt_sym: uN[4]:15, cp_off: uN[3]:0, cp_cnt: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::CP, lt_sym: uN[4]:0, cp_off: uN[3]: 4, cp_cnt: uN[4]: 0, mark: Mark::NONE}, + Token{kind: TokenKind::CP, lt_sym: uN[4]:0, cp_off: uN[3]: 5, cp_cnt: uN[4]:11, mark: Mark::NONE}, + Token{kind: TokenKind::LT, lt_sym: uN[4]: 8, cp_off: uN[3]:0, cp_cnt: uN[4]:0, mark: Mark::NONE}, + Token{kind: TokenKind::MK, lt_sym: uN[4]:0, cp_off: uN[3]:0, cp_cnt: uN[4]:0, mark: Mark::END} +]; +// Reference output +const TST_SIMPLE_OUTPUT_LEN = u32:108; +const TST_SIMPLE_OUTPUT = uN[TST_SYM_WIDTH][TST_SIMPLE_OUTPUT_LEN]: [ + 12, 1, 15, 9, 11, 9, 11, 11, 11, 11, 9, 15, 9, 9, 15, 9, + 9, 15, 9, 9, 15, 9, 9, 15, 9, 9, 15, 1, 9, 15, 9, 9, + 7, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 3, 9, 2, 2, 3, 9, 2, 2, 3, 9, 2, + 2, 3, 9, 2, 2, 3, 9, 14, 9, 14, 9, 9, 2, 15, 14, 14, + 9, 9, 2, 15, 14, 14, 9, 9, 2, 15, 14, 8 +]; + +#[test_proc] +proc test_simple { + o_term: chan out; + recv_last_r: chan in; + + init {()} + + config(o_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); + /// NOTE(2023-06-20): + /// can not pass o_term directly to data_validator for some reason + spawn test::data_validator( + TST_SIMPLE_OUTPUT, recv_data_r, recv_last_s); + + (o_term, recv_last_r) + } + + next (tok: token, state: ()) { + // Here, test::data_validator validates the data received from decoder + // We just forward its o_term to our o_term + let (tok, v) = recv(tok, recv_last_r); + send(tok, o_term, v); + } +} diff --git a/xls/modules/dbe/lz4_decoder_test.py b/xls/modules/dbe/lz4_decoder_test.py new file mode 100644 index 0000000000..9cb7ebc3fa --- /dev/null +++ b/xls/modules/dbe/lz4_decoder_test.py @@ -0,0 +1,680 @@ +# 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 cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge, ClockCycles, Event + +from cocotb_bus.scoreboard import Scoreboard +from cocotb_xls import XLSChannelDriver, XLSChannelMonitor + +from dbe import TokMK, TokCP, TokLT, Mark, Token, MarkedByte +from dbe_common_test import DslxPlainData8, DslxToken + +from typing import Sequence + + +@cocotb.coroutine +async def reset(clk, rst, cycles=1): + await RisingEdge(clk) + rst.value = 1 + await ClockCycles(clk, cycles) + rst.value = 0 + + +async def do_comparison_test( + dut, + test_in: Sequence[Token], expected_out: Sequence[MarkedByte], + post_watch_cycles: int = 100): + """ + Tests the module by feeding it with `test_in`, reading the output, + and comparing the output with `expected_out`. + + If the test receives a data item that does not match the one which + is expected according to the `expected_out`, it terminates with a failure + immediately. + If the test receives all data items specified by `expected_out` after + feeding all the `test_in` data, it continues to run for `post_watch_cycles` + clock cycles, checking if it will receive any "garbage" data. If it does, + that is also considered a failure. If it doesn't, then that'll be a pass. + """ + # Convert i/o arrays to BinaryData + test_in_bd = list(DslxToken.from_token(t).as_binary() for t in test_in) + expected_out_bd = list( + DslxPlainData8.from_markedbyte(b).as_binary() for b in expected_out) + + all_data_received = Event("All expected data received") + + clock = Clock(dut.clk, 1, units="us") + + i_token = XLSChannelDriver(dut, "i_token", dut.clk) + o_data = XLSChannelMonitor(dut, "o_data", dut.clk) + + sb = Scoreboard(dut, fail_immediately=True) + sb.add_interface(o_data, expected_out_bd) + + def check_if_all_data_received(_): + if o_data.stats.received_transactions == len(expected_out): + all_data_received.set() + + o_data.add_callback(check_if_all_data_received) + o_data.bus.rdy.setimmediatevalue(1) + + await cocotb.start(clock.start()) + await reset(dut.clk, dut.rst, 1) + await i_token.write(test_in_bd) + if expected_out: + await all_data_received.wait() + await ClockCycles(dut.clk, post_watch_cycles) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_void(dut): + """ + Check that in absence of input the module does not generate any output + """ + await do_comparison_test( + dut, + test_in=[ + ], + expected_out=[ + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_lt1(dut): + """ + Check that a single LT produces one symbol + """ + await do_comparison_test( + dut, + test_in=[ + TokLT(10), + ], + expected_out=[ + 10, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_lt2(dut): + """ + Check that a few LTs produce the same number of symbols + """ + await do_comparison_test( + dut, + test_in=[ + TokLT(10), + TokLT(20), + TokLT(30), + ], + expected_out=[ + 10, + 20, + 30, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_cp1(dut): + """ + Check that a CP with length 1 works correctly + """ + await do_comparison_test( + dut, + test_in=[ + TokLT(10), + TokCP(0, 1) + ], + expected_out=[ + 10, + 10, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_cp2(dut): + """ + Check that a CP with length >1 works correctly + """ + await do_comparison_test( + dut, + test_in=[ + TokLT(10), + TokLT(20), + TokLT(30), + TokLT(40), + TokCP(2, 3) + ], + expected_out=[ + 10, + 20, + 30, + 40, + 20, 30, 40, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_cp3(dut): + """ + Check that CP can properly repeat symbols produced by itself + """ + await do_comparison_test( + dut, + test_in=[ + TokLT(10), + TokLT(20), + TokCP(1, 6) + ], + expected_out=[ + 10, + 20, + 10, 20, 10, 20, 10, 20 + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_cp4(dut): + """ + Check that decoder handles several CP tokens correctly + """ + await do_comparison_test( + dut, + test_in=[ + TokLT(10), + TokLT(20), + TokCP(1, 2), + TokCP(2, 4), + ], + expected_out=[ + 10, + 20, + 10, 20, + 20, 10, 20, 20 + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_cp4(dut): + """ + Check that decoder handles mixed CP and LT tokens + """ + await do_comparison_test( + dut, + test_in=[ + TokLT(10), + TokCP(0, 1), + TokLT(30), + TokCP(0, 2), + TokLT(40), + TokCP(1, 2), + ], + expected_out=[ + 10, + 10, + 30, + 30, 30, + 40, + 30, 40 + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_end1(dut): + """ + Check that END mark is propagated + """ + await do_comparison_test( + dut, + test_in=[ + TokMK(Mark.END), + ], + expected_out=[ + Mark.END, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_end2(dut): + """ + Check that several END marks without any data in between are propagated + """ + await do_comparison_test( + dut, + test_in=[ + TokMK(Mark.END), + TokMK(Mark.END), + TokMK(Mark.END), + TokMK(Mark.END), + ], + expected_out=[ + Mark.END, + Mark.END, + Mark.END, + Mark.END, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_block1(dut): + """ + Check that a simple block with a single LT is handled properly + """ + await do_comparison_test( + dut, + test_in=[ + TokLT(10), + TokMK(Mark.END), + ], + expected_out=[ + 10, + Mark.END, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_block2(dut): + """ + Check that a simple block with two LT is handled properly + """ + await do_comparison_test( + dut, + test_in=[ + TokLT(10), + TokLT(20), + TokMK(Mark.END), + ], + expected_out=[ + 10, + 20, + Mark.END, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_block3(dut): + """ + Check that a simple block with LT and CP is handled properly + """ + await do_comparison_test( + dut, + test_in=[ + TokLT(10), + TokCP(0, 1), + TokMK(Mark.END), + ], + expected_out=[ + 10, + 10, + Mark.END, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_blocks1(dut): + """ + Check that two blocks with LT are handled properly + """ + await do_comparison_test( + dut, + test_in=[ + TokLT(10), + TokMK(Mark.END), + TokLT(20), + TokMK(Mark.END), + ], + expected_out=[ + 10, + Mark.END, + 20, + Mark.END, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_blocks2(dut): + """ + Check that two blocks with CP are handled correctly + (this also validates that END token is not written to HB) + """ + await do_comparison_test( + dut, + test_in=[ + TokLT(10), + TokMK(Mark.END), + TokCP(0, 1), + TokMK(Mark.END), + ], + expected_out=[ + 10, + Mark.END, + 10, + Mark.END, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_blocks3(dut): + """ + Check that several blocks with CP are handled correctly + (this also validates that END token is not written to HB) + """ + await do_comparison_test( + dut, + test_in=[ + TokLT(10), + TokMK(Mark.END), + TokMK(Mark.END), + TokLT(20), + TokMK(Mark.END), + TokMK(Mark.END), + TokCP(1, 2), + ], + expected_out=[ + 10, + Mark.END, + Mark.END, + 20, + Mark.END, + Mark.END, + 10, 20 + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_reset1(dut): + """ + Check that RESET token is propagated + """ + await do_comparison_test( + dut, + test_in=[ + TokMK(Mark.RESET), + ], + expected_out=[ + Mark.RESET, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_reset2(dut): + """ + Check that RESET & END tokens are propagated + """ + await do_comparison_test( + dut, + test_in=[ + TokMK(Mark.RESET), + TokMK(Mark.END), + TokMK(Mark.RESET), + TokMK(Mark.RESET), + TokMK(Mark.END), + TokMK(Mark.END), + ], + expected_out=[ + Mark.RESET, + Mark.END, + Mark.RESET, + Mark.RESET, + Mark.END, + Mark.END, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_error1(dut): + """ + Check that ERROR token is propagated + """ + await do_comparison_test( + dut, + test_in=[ + TokMK(Mark.ERROR_BAD_MARK), + ], + expected_out=[ + Mark.ERROR_BAD_MARK, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_error2(dut): + """ + Check that ERROR token is propagated and no further tokens are + processed afterwards + """ + await do_comparison_test( + dut, + test_in=[ + TokMK(Mark.ERROR_BAD_MARK), + TokLT(1), + TokCP(0, 1), + TokMK(Mark.END), + TokMK(Mark.ERROR_BAD_MARK), + ], + expected_out=[ + Mark.ERROR_BAD_MARK, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_error_reset1(dut): + """ + Check that ERROR token is propagated, no further tokens are + processed afterwards, and module can be RESET and start accepting tokens + after that + """ + await do_comparison_test( + dut, + test_in=[ + TokLT(1), + TokMK(Mark.ERROR_BAD_MARK), + TokLT(2), + TokCP(0, 1), + TokMK(Mark.END), + TokMK(Mark.RESET), + TokLT(3), + TokMK(Mark.END), + ], + expected_out=[ + 1, + Mark.ERROR_BAD_MARK, + Mark.RESET, + 3, + Mark.END, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_error_inval_cp1(dut): + """ + Check that when given a CP that points to an unwritten HB location, + ERROR_INVAL_CP is generated and no data is leaked from unwritten part + of HB. + """ + await do_comparison_test( + dut, + test_in=[ + TokCP(0, 1), + ], + expected_out=[ + Mark.ERROR_INVAL_CP, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_error_inval_cp2(dut): + """ + Check that ERROR_INVAL_CP can be reset. + """ + await do_comparison_test( + dut, + test_in=[ + TokCP(0, 1), + TokMK(Mark.RESET), + TokLT(10), + TokCP(0, 1), + ], + expected_out=[ + Mark.ERROR_INVAL_CP, + Mark.RESET, + 10, + 10 + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_error_inval_cp3(dut): + """ + Check that RESET resets HB pointers and then when given a CP that points + to an unwritten HB location, ERROR_INVAL_CP is generated and no data is + leaked from HB. + """ + await do_comparison_test( + dut, + test_in=[ + TokLT(10), + TokCP(0, 1), + TokMK(Mark.RESET), + TokCP(0, 1), + ], + expected_out=[ + 10, + 10, + Mark.RESET, + Mark.ERROR_INVAL_CP, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_error_inval_cp4(dut): + """ + Check that after ERROR_INVAL_CP is produced, no other data is emitted + besides RESET (if present) + """ + await do_comparison_test( + dut, + test_in=[ + TokCP(0, 1), + TokLT(10), + TokCP(0, 1), + TokCP(10, 1), + TokMK(Mark.END), + TokMK(Mark.ERROR_BAD_MARK), + TokMK(Mark.RESET), + ], + expected_out=[ + Mark.ERROR_INVAL_CP, + Mark.RESET, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_error_inval_cp5(dut): + """ + Check that ERROR_INVAL_CP is produced for CP offset > 0 + """ + await do_comparison_test( + dut, + test_in=[ + TokLT(1), + TokLT(2), + TokLT(3), + TokCP(0, 1), + TokCP(2, 2), + TokCP(10, 1), + ], + expected_out=[ + 1, + 2, + 3, + 3, + 2, 3, + Mark.ERROR_INVAL_CP, + ]) + + +@cocotb.test(timeout_time=1, timeout_unit='ms') +async def test_error_inval_cp6(dut): + """ + Check that after ERROR_INVAL_CP is produced for maximum LZ4 CP offset + """ + await do_comparison_test( + dut, + test_in=[ + TokCP(0xFFFF, 1), + TokMK(Mark.RESET), + TokLT(10), + TokCP(0xFFFF, 1), + ], + expected_out=[ + Mark.ERROR_INVAL_CP, + Mark.RESET, + 10, + Mark.ERROR_INVAL_CP, + ]) + + +@cocotb.test(timeout_time=1000, timeout_unit='ms') +async def test_error_inval_cp7(dut): + """ + Check that ERROR_INVAL_CP is produced when only 1 symbol in buffer is + unwritten + """ + await do_comparison_test( + dut, + test_in=[TokLT(10)] * 65535 + [TokCP(65535, 1)], + expected_out=[10] * 65535 + [Mark.ERROR_INVAL_CP], + ) + + +@cocotb.test(timeout_time=1000, timeout_unit='ms') +async def test_error_inval_cp8(dut): + """ + Check that ERROR_INVAL_CP is not produced when buffer is full (test 1) + """ + await do_comparison_test( + dut, + test_in=[TokLT(10)] * 65536 + [TokCP(65535, 1)], + expected_out=[10] * 65536 + [10], + ) + + +@cocotb.test(timeout_time=1000, timeout_unit='ms') +async def test_error_inval_cp9(dut): + """ + Check that ERROR_INVAL_CP is not produced when buffer is full (test 2) + """ + await do_comparison_test( + dut, + test_in=[TokLT(10)] * 65536 + [TokCP(0, 1)], + expected_out=[10] * 65536 + [10], + ) + + +@cocotb.test(timeout_time=1000, timeout_unit='ms') +async def test_cp_maxcount(dut): + """ + Check that CP token with maximum permitted cp_cnt value works + """ + await do_comparison_test( + dut, + test_in=[ + TokLT(7), + TokCP(0, 65536), + TokLT(15), + ], + expected_out=[7] * 65537 + [15], + ) diff --git a/xls/modules/dbe/lz4_decoder_wrap.v b/xls/modules/dbe/lz4_decoder_wrap.v new file mode 100644 index 0000000000..9304e74b1d --- /dev/null +++ b/xls/modules/dbe/lz4_decoder_wrap.v @@ -0,0 +1,97 @@ +// 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. + + +module lz4_decoder_wrap ( + input clk, + input rst, + // tokens input + input [45:0] i_token_data, + input i_token_vld, + output i_token_rdy, + // data output + output [12:0] o_data_data, + output o_data_vld, + input o_data_rdy +); + + localparam RAM_HB_ADDR_WIDTH = 16; + localparam RAM_HB_DATA_WIDTH = 8; + localparam RAM_HB_NUM_PART = 1; + + // HB RAM bus + wire [RAM_HB_ADDR_WIDTH+RAM_HB_DATA_WIDTH+RAM_HB_NUM_PART-1:0] + ram_hb_wr_req_data; + wire ram_hb_wr_req_vld; + wire ram_hb_wr_req_rdy; + wire ram_hb_wr_resp_vld; + wire ram_hb_wr_resp_rdy; + wire [RAM_HB_ADDR_WIDTH+RAM_HB_NUM_PART-1:0] ram_hb_rd_req_data; + wire ram_hb_rd_req_vld; + wire ram_hb_rd_req_rdy; + wire [RAM_HB_DATA_WIDTH-1:0] ram_hb_rd_resp_data; + wire ram_hb_rd_resp_vld; + wire ram_hb_rd_resp_rdy; + + // HB RAM + sdpram_xls_chan #( + .DATA_WIDTH(RAM_HB_DATA_WIDTH), + .ADDR_WIDTH(RAM_HB_ADDR_WIDTH), + .NUM_PARTITIONS(RAM_HB_NUM_PART) + ) ram_hb ( + .clk(clk), + .rst(rst), + .wr_req_data(ram_hb_wr_req_data), + .wr_req_vld(ram_hb_wr_req_vld), + .wr_req_rdy(ram_hb_wr_req_rdy), + .wr_resp_vld(ram_hb_wr_resp_vld), + .wr_resp_rdy(ram_hb_wr_resp_rdy), + .rd_req_data(ram_hb_rd_req_data), + .rd_req_vld(ram_hb_rd_req_vld), + .rd_req_rdy(ram_hb_rd_req_rdy), + .rd_resp_data(ram_hb_rd_resp_data), + .rd_resp_vld(ram_hb_rd_resp_vld), + .rd_resp_rdy(ram_hb_rd_resp_rdy) + ); + + // Decoder engine + dbe_lz4_decoder dut ( + .clk(clk), + .rst(rst), + .lz4_decoder__i_encoded_data(i_token_data), + .lz4_decoder__i_encoded_vld(i_token_vld), + .lz4_decoder__i_encoded_rdy(i_token_rdy), + .lz4_decoder__o_data_data(o_data_data), + .lz4_decoder__o_data_vld(o_data_vld), + .lz4_decoder__o_data_rdy(o_data_rdy), + // RAM + .lz4_decoder__o_ram_hb_wr_req_data(ram_hb_wr_req_data), + .lz4_decoder__o_ram_hb_wr_req_vld(ram_hb_wr_req_vld), + .lz4_decoder__o_ram_hb_wr_req_rdy(ram_hb_wr_req_rdy), + .lz4_decoder__i_ram_hb_wr_resp_vld(ram_hb_wr_resp_vld), + .lz4_decoder__i_ram_hb_wr_resp_rdy(ram_hb_wr_resp_rdy), + .lz4_decoder__o_ram_hb_rd_req_data(ram_hb_rd_req_data), + .lz4_decoder__o_ram_hb_rd_req_vld(ram_hb_rd_req_vld), + .lz4_decoder__o_ram_hb_rd_req_rdy(ram_hb_rd_req_rdy), + .lz4_decoder__i_ram_hb_rd_resp_data(ram_hb_rd_resp_data), + .lz4_decoder__i_ram_hb_rd_resp_vld(ram_hb_rd_resp_vld), + .lz4_decoder__i_ram_hb_rd_resp_rdy(ram_hb_rd_resp_rdy) + ); + + initial begin + $dumpfile("dump.vcd"); + $dumpvars(0, lz4_decoder_wrap); + end + +endmodule \ No newline at end of file diff --git a/xls/modules/dbe/ram_model.v b/xls/modules/dbe/ram_model.v new file mode 100644 index 0000000000..01f916718a --- /dev/null +++ b/xls/modules/dbe/ram_model.v @@ -0,0 +1,154 @@ +// 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. + + +// Semi-dual-port RAM with XLS-native channel interface +// - Can be used with Verilog modules generated without using +// `ram_configurations` codegen option +// - Simultaneous write & read behavior: reads new value +module sdpram_xls_chan #( + parameter DATA_WIDTH = 8, + parameter ADDR_WIDTH = 16, + parameter NUM_PARTITIONS = 1 +) ( + input clk, + input rst, + // write request + input [DATA_WIDTH+ADDR_WIDTH+NUM_PARTITIONS-1:0] wr_req_data, + input wr_req_vld, + output wr_req_rdy, + // write response (no data) + output wr_resp_vld, + input wr_resp_rdy, + // read request + input [ADDR_WIDTH+NUM_PARTITIONS-1:0] rd_req_data, + input rd_req_vld, + output rd_req_rdy, + // read response + output [DATA_WIDTH-1:0] rd_resp_data, + output rd_resp_vld, + input rd_resp_rdy +); + + localparam SIZE = 32'd1 << ADDR_WIDTH; + + // RAM array + reg [DATA_WIDTH-1:0] mem [SIZE]; + + // Disassemble requests + reg [DATA_WIDTH-1:0] r_wr_req_f_data; + reg [ADDR_WIDTH-1:0] r_wr_req_f_addr; + reg [NUM_PARTITIONS-1:0] r_wr_req_f_mask; + reg [ADDR_WIDTH-1:0] r_rd_req_f_addr; + reg [NUM_PARTITIONS-1:0] r_rd_req_f_mask; + + always @* begin + { + r_wr_req_f_addr, + r_wr_req_f_data, + r_wr_req_f_mask + } = wr_req_data; + { + r_rd_req_f_addr, + r_rd_req_f_mask + } = rd_req_data; + end + + // Assemble response + reg [DATA_WIDTH-1:0] r_rd_resp_f_data; + assign rd_resp_data = r_rd_resp_f_data; + + // Reset RAM on reset (simulator only) + integer i; + always @(posedge clk) begin + if (rst) begin + for (i = 0; i < SIZE; i = i + 1) begin + mem[i] <= {DATA_WIDTH{1'bx}}; + end + end + end + + // RAM-write state machine + reg r_wr_req_canhandle; + reg r_wr_req_ack; + reg r_wr_resp_pending; + reg r_wr_resp_ack; + + always @* begin + r_wr_req_ack = wr_req_vld && wr_req_rdy; + r_wr_resp_ack = wr_resp_vld && wr_resp_rdy; + // We're accepting new request only if there is no pending response + // or that response is gonna be acknowledged in this cycle + r_wr_req_canhandle = !r_wr_resp_pending || r_wr_resp_ack; + end + + assign wr_req_rdy = r_wr_req_canhandle; + assign wr_resp_vld = r_wr_resp_pending; + + always @(posedge clk) begin + if (rst) begin + r_wr_resp_pending <= 1'b0; + end else begin + if (r_wr_resp_ack) begin + r_wr_resp_pending <= 1'b0; + end + if (r_wr_req_ack) begin + // write to RAM and signal response availability + mem[r_wr_req_f_addr] <= r_wr_req_f_data; + r_wr_resp_pending <= 1'b1; + end + end + end + + // RAM-read state machine + reg r_rd_req_canhandle; + reg r_rd_req_ack; + reg r_rd_resp_pending; + reg r_rd_resp_ack; + + always @* begin + r_rd_req_ack = rd_req_vld && rd_req_rdy; + r_rd_resp_ack = rd_resp_vld && rd_resp_rdy; + // We're accepting new request only if there is no pending response + // or that response is gonna be acknowledged in this cycle + r_rd_req_canhandle = !r_rd_resp_pending || r_rd_resp_ack; + end + + assign rd_req_rdy = r_rd_req_canhandle; + assign rd_resp_vld = r_rd_resp_pending; + + always @(posedge clk) begin + if (rst) begin + r_rd_resp_pending <= 1'b0; + r_rd_resp_f_data <= {DATA_WIDTH{1'bx}}; + end else begin + if (r_rd_resp_ack) begin + r_rd_resp_pending <= 1'b0; + r_rd_resp_f_data <= {DATA_WIDTH{1'bx}}; + end + if (r_rd_req_ack) begin + // read from RAM and signal response availability + if (r_wr_req_ack && r_rd_req_f_addr == r_wr_req_f_addr) begin + // write-before-read logic + r_rd_resp_f_data <= r_wr_req_f_data; + end else begin + // normal read + r_rd_resp_f_data <= mem[r_rd_req_f_addr]; + end + r_rd_resp_pending <= 1'b1; + end + end + end + +endmodule diff --git a/xls/modules/dbe/scripts/BUILD b/xls/modules/dbe/scripts/BUILD new file mode 100644 index 0000000000..cebfaab63e --- /dev/null +++ b/xls/modules/dbe/scripts/BUILD @@ -0,0 +1,47 @@ +# 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 LZ4 Python reference & tests + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//xls:xls_users"], + licenses = ["notice"], +) + +load("@xls_pip_deps//:requirements.bzl", "requirement") + +py_library( + name = "dbe", + imports = [ + ".", + ], + srcs = [ + "dbe/__init__.py", + "dbe/common.py", + "dbe/decoder.py", + "dbe/random.py", + "dbe/utils.py", + ], +) + +py_binary( + name = "randgen_tok", + srcs = [ + "randgen_tok.py" + ], + deps = [ + ":dbe", + ], +) diff --git a/xls/modules/dbe/scripts/dbe/__init__.py b/xls/modules/dbe/scripts/dbe/__init__.py new file mode 100644 index 0000000000..f52f6e17cf --- /dev/null +++ b/xls/modules/dbe/scripts/dbe/__init__.py @@ -0,0 +1,19 @@ +# 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. + + +from .common import Params, Token, TokCP, TokLT, TokMK, Mark, MarkedByte +from .decoder import Decoder +from .random import get_random_tokens +from .utils import prettify diff --git a/xls/modules/dbe/scripts/dbe/common.py b/xls/modules/dbe/scripts/dbe/common.py new file mode 100644 index 0000000000..cce9421c6e --- /dev/null +++ b/xls/modules/dbe/scripts/dbe/common.py @@ -0,0 +1,143 @@ +# 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. + + +from abc import abstractmethod +from enum import IntEnum +from typing import Union, List, Iterable + + +class Params: + def __init__(self, symbits: int, ptrbits: int, cntbits: int): + self.wsym = symbits + self.wptr = ptrbits + self.wcnt = cntbits + self.hb_maxsz = 1 << self.wptr + self.sym_max = (1 << self.wsym) - 1 + self.cnt_max = (1 << self.wcnt) + + def __eq__(self, oth: object) -> bool: + if not isinstance(oth, Params): + return False + ok = self.wsym == oth.wsym + ok = ok and self.wptr == oth.wptr + ok = ok and self.wcnt == oth.wcnt + return ok + + +class Mark(IntEnum): + NONE = 0 + END = 1 + RESET = 2 + + _ERROR_FIRST = 8 + # Only errors have values >= __ERROR_FIRST + ERROR_BAD_MARK = 8 + ERROR_INVAL_CP = 9 + + @classmethod + def is_error(cls, v: 'Mark') -> bool: + return v >= cls._ERROR_FIRST + + +MarkedByte = Union[int, Mark] + + +class Token: + @abstractmethod + def to_dslx(self, cfg: Params) -> str: + ... + + +class TokLT(Token): + """ + TokLT - literal token + + Makes decoder emit a single symbol specified by the token + """ + def __init__(self, sym: int): + super().__init__() + self.sym = sym + + def __repr__(self) -> str: + return f'TokLT(sym={self.sym})' + + def to_dslx(self, cfg: Params) -> str: + pars = [ + f'kind: TokenKind::LT', + f'lt_sym: uN[{cfg.wsym}]:{self.sym:2d}', + f'cp_off: uN[{cfg.wptr}]:0', + f'cp_cnt: uN[{cfg.wcnt}]:0', + f'mark: Mark::NONE', + ] + return f'Token{{{", ".join(pars)}}}' + + +class TokCP(Token): + """ + TokCP - copy pointer token + + Makes decoder emit a sequence of symbols that repeats the continous + sequence of symbols that was emitted in the past. It specifies how far + to go into the past, and how many symbols to copy. + + - 'offset' tells decoder where the beginning of past sequence if located. + It is counted starting from the last written character, so offset of 0 + means "copy beginning with the last output character". + - 'count' is just a number of characters to copy. Count of 0 can be + deemed illegal by some token encoding schemes. + """ + def __init__(self, ofs: int, cnt: int): + super().__init__() + self.ofs = ofs + self.cnt = cnt + + def __repr__(self) -> str: + return f'TokCP(ofs={self.ofs}, cnt={self.cnt})' + + def to_dslx(self, cfg: Params) -> str: + assert self.cnt >= 1 + pars = [ + f'kind: TokenKind::CP', + f'lt_sym: uN[{cfg.wsym}]:0', + f'cp_off: uN[{cfg.wptr}]:{self.ofs:2d}', + f'cp_cnt: uN[{cfg.wcnt}]:{self.cnt-1:2d}', + f'mark: Mark::NONE', + ] + return f'Token{{{", ".join(pars)}}}' + + +class TokMK(Token): + """ + TokMK - control marker token + + Contains 8-bit wide marker control code specified by Marker enum. + """ + def __init__(self, mark: Mark): + super().__init__() + self.mark = mark + + def __repr__(self) -> str: + return f'TokMK({self.mark!r})' + + def to_dslx(self, cfg: Params) -> str: + pars = [ + f'kind: TokenKind::MK', + f'lt_sym: uN[{cfg.wsym}]:0', + f'cp_off: uN[{cfg.wptr}]:0', + f'cp_cnt: uN[{cfg.wcnt}]:0', + f'mark: Mark::{self.mark.name}', + ] + return f'Token{{{", ".join(pars)}}}' + diff --git a/xls/modules/dbe/scripts/dbe/decoder.py b/xls/modules/dbe/scripts/dbe/decoder.py new file mode 100644 index 0000000000..62f2097cd6 --- /dev/null +++ b/xls/modules/dbe/scripts/dbe/decoder.py @@ -0,0 +1,72 @@ +# 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. + + +from .common import ( + Params, + Token, + TokCP, + TokLT, + TokMK, + Mark +) +from typing import Sequence, List + + +class Decoder: + def __init__(self, cfg: Params): + self.cfg = cfg + self.hb = [] + self.toks: List[Token] = [] + self.out: List[int] = [] + + def _pout(self, sym): + self.out.append(sym) + self.hb.append(sym) + if len(self.hb) >= self.cfg.hb_maxsz: + self.hb = self.hb[-self.cfg.hb_maxsz:] + + def feed(self, tok): + assert isinstance(tok, Token) + + self.toks.append(tok) + if isinstance(tok, TokLT): + # Literal + self._pout(tok.sym) + elif isinstance(tok, TokCP): + # CP + for i in range(tok.cnt): + try: + self._pout(self.hb[-tok.ofs-1]) + except IndexError as e: + print(self.out) + print(len(self.out)) + raise e + elif isinstance(tok, TokMK): + if tok.mark == Mark.RESET: + self.hb.clear() + elif tok.mark == Mark.END: + # In Python we do not handle END tokens + ... + else: + raise RuntimeError(f'Unexpected marker: {tok}') + else: + raise RuntimeError(f'Unexpected token type: {type(tok)!r}') + + def decode(self, toks: Sequence[Token]) -> List[int]: + self.toks.clear() + self.out.clear() + for t in toks: + self.feed(t) + return self.out diff --git a/xls/modules/dbe/scripts/dbe/random.py b/xls/modules/dbe/scripts/dbe/random.py new file mode 100644 index 0000000000..8816df81a1 --- /dev/null +++ b/xls/modules/dbe/scripts/dbe/random.py @@ -0,0 +1,46 @@ +# 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. + + +from typing import List +import random +from dbe.common import Params, Token, TokLT, TokCP, TokMK, Mark +from dbe.decoder import Decoder + + +def get_random_tokens(seqlen: int, cfg: Params, ofsslope: float = 2, cntslope: float = 4) -> List[Token]: + d = Decoder(cfg) + d.feed(TokMK(Mark.RESET)) + d.feed(TokLT(random.randint(0, cfg.sym_max))) + # Then we generate CP or LIT with equal probability and fill their contents randomly + while len(d.toks) < seqlen: + kind = random.randint(0, 1) + if kind: + # Literal + d.feed(TokLT(random.randint(0, cfg.sym_max))) + else: + # CP + # exp-like distribution for number of repetitions + rx = random.random() + cnt = int(1.5 + (cfg.cnt_max - 1) * (rx ** cntslope)) + cnt = min(max(cnt, 1), cfg.cnt_max) + # exp-like distribution for offset + assert len(d.hb) >= 1 + rx = random.random() + ofs = int(0.5 + (cfg.hb_maxsz - 1) * (rx ** ofsslope)) + ofs = min(max(ofs, 0), len(d.hb) - 1) + d.feed(TokCP(ofs, cnt)) + d.feed(TokMK(Mark.END)) + + return d.toks diff --git a/xls/modules/dbe/scripts/dbe/utils.py b/xls/modules/dbe/scripts/dbe/utils.py new file mode 100644 index 0000000000..866bf9798f --- /dev/null +++ b/xls/modules/dbe/scripts/dbe/utils.py @@ -0,0 +1,19 @@ +# 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. + + +def prettify(seq, perline): + seq = tuple(seq) + r = ",\n".join([", ".join(seq[i:i+perline]) for i in range(0,len(seq),perline)]) + return r diff --git a/xls/modules/dbe/scripts/randgen_tok.py b/xls/modules/dbe/scripts/randgen_tok.py new file mode 100755 index 0000000000..3d111d66b2 --- /dev/null +++ b/xls/modules/dbe/scripts/randgen_tok.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 + +# 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 random +from dbe import prettify, get_random_tokens, Params, Decoder + + +SYM_BITS = 4 +PTR_BITS = 3 +CNT_BITS = 4 + +OFSSLOPE = 2 +CNTSLOPE = 4 +NTOKS = 32 +NPERLINE = 16 + + +random.seed(0) + +cfg = Params(SYM_BITS, PTR_BITS, CNT_BITS) +toks = get_random_tokens(NTOKS, cfg, OFSSLOPE, CNTSLOPE) +d = Decoder(cfg) +for t in toks: + d.feed(t) + +print(f'Tokens (len={len(d.toks)}):') +print(',\n'.join(repr(t) for t in d.toks)) +print('') +print(f'DSLX array (len={len(d.toks)}):') +print(',\n'.join(t.to_dslx(cfg) for t in d.toks)) +print('') +print(f'Reference decoding (len={len(d.out)}):') +print(prettify((f'{x:2d}' for x in d.out), NPERLINE)) +print('') +print('Done.') diff --git a/xls/simulation/cocotb/BUILD b/xls/simulation/cocotb/BUILD new file mode 100644 index 0000000000..5de39a6641 --- /dev/null +++ b/xls/simulation/cocotb/BUILD @@ -0,0 +1,30 @@ +# Copyright 2020 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@xls_pip_deps//:requirements.bzl", "requirement") + +package( + default_visibility = ["//xls:xls_public"], + licenses = ["notice"], # Apache 2.0 +) + +py_library( + name = "cocotb_xls", + srcs = ["cocotb_xls.py"], + imports = ["."], + deps = [ + requirement("cocotb"), + requirement("cocotb_bus"), + ], +) diff --git a/xls/simulation/cocotb/cocotb_xls.py b/xls/simulation/cocotb/cocotb_xls.py new file mode 100644 index 0000000000..751f324c24 --- /dev/null +++ b/xls/simulation/cocotb/cocotb_xls.py @@ -0,0 +1,68 @@ +# 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 cocotb + +from cocotb.triggers import RisingEdge +from cocotb.binary import BinaryValue +from cocotb.handle import SimHandleBase + +from cocotb_bus.drivers import BusDriver +from cocotb_bus.monitors import BusMonitor + +from typing import Any, List + + +class XLSChannelDriver(BusDriver): + _signals = ["data", "rdy", "vld"] + + def __init__( + self, entity: SimHandleBase, name: str, clock: SimHandleBase, **kwargs: Any + ): + BusDriver.__init__(self, entity, name, clock, **kwargs) + + self.bus.data.setimmediatevalue(0) + self.bus.vld.setimmediatevalue(0) + + async def write(self, data: List[BinaryValue], sync: bool = True) -> None: + if sync: + await RisingEdge(self.clock) + + for word in data: + self.bus.vld.value = 1 + self.bus.data.value = word + + while True: + await RisingEdge(self.clock) + if self.bus.rdy.value: + break + + self.bus.vld.value = 0 + + +class XLSChannelMonitor(BusMonitor): + _signals = ["data", "rdy", "vld"] + + def __init__( + self, entity: SimHandleBase, name: str, clock: SimHandleBase, **kwargs: Any + ): + BusMonitor.__init__(self, entity, name, clock, **kwargs) + + @cocotb.coroutine + async def _monitor_recv(self) -> None: + while True: + await RisingEdge(self.clock) + if self.bus.vld.value and self.bus.rdy.value: + vec = self.bus.data.value + self._recv(vec)