diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD new file mode 100644 index 0000000000..275c064033 --- /dev/null +++ b/xls/modules/zstd/BUILD @@ -0,0 +1,121 @@ +# Copyright 2023 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Build rules for XLS ZSTD codec implementation. + +load("@rules_hdl//place_and_route:build_defs.bzl", "place_and_route") +load("@rules_hdl//synthesis:build_defs.bzl", "benchmark_synth", "synthesize_rtl") +load("@rules_hdl//verilog:providers.bzl", "verilog_library") +load( + "//xls/build_rules:xls_build_defs.bzl", + "xls_benchmark_ir", + "xls_dslx_library", + "xls_dslx_test", + "xls_dslx_verilog", +) + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//xls:xls_users"], + licenses = ["notice"], +) + +xls_dslx_library( + name = "buffer_dslx", + srcs = [ + "buffer.x", + ], +) + +xls_dslx_test( + name = "buffer_dslx_test", + library = ":buffer_dslx", +) + +xls_dslx_library( + name = "window_buffer_dslx", + srcs = [ + "window_buffer.x", + ], + deps = [ + ":buffer_dslx", + ], +) + +xls_dslx_test( + name = "window_buffer_dslx_test", + library = ":window_buffer_dslx", +) + +xls_dslx_verilog( + name = "window_buffer_verilog", + codegen_args = { + "module_name": "WindowBuffer64", + "delay_model": "asap7", + "pipeline_stages": "2", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "WindowBuffer64", + library = ":window_buffer_dslx", + # TODO: 2024-01-25: Workaround for https://github.com/google/xls/issues/869 + # Force proc inlining and set last internal proc as top proc for IR optimization + opt_ir_args = { + "inline_procs": "true", + "top": "__window_buffer__WindowBuffer64__WindowBuffer_0__64_32_48_next", + }, + verilog_file = "window_buffer.v", +) + +xls_benchmark_ir( + name = "window_buffer_opt_ir_benchmark", + src = ":window_buffer_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "2", + "delay_model": "asap7", + }, +) + +verilog_library( + name = "window_buffer_verilog_lib", + srcs = [ + ":window_buffer.v", + ], +) + +synthesize_rtl( + name = "window_buffer_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "WindowBuffer64", + deps = [ + ":window_buffer_verilog_lib", + ], +) + +benchmark_synth( + name = "window_buffer_benchmark_synth", + synth_target = ":window_buffer_synth_asap7", +) + +place_and_route( + name = "window_buffer_place_and_route", + clock_period = "750", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + skip_detailed_routing = True, + synthesized_rtl = ":window_buffer_synth_asap7", + target_die_utilization_percentage = "10", +) + diff --git a/xls/modules/zstd/buffer.x b/xls/modules/zstd/buffer.x new file mode 100644 index 0000000000..9d050e96ee --- /dev/null +++ b/xls/modules/zstd/buffer.x @@ -0,0 +1,355 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains implementation of a Buffer structure that acts as +// a simple FIFO. Additionally, the file provides various functions that +// can simplify access to the stored. +// +// The utility functions containing the `_checked` suffix serve two purposes: +// they perform the actual operation and return information on whether +// the operation was successful. If you are sure that the precondition is +// always true, you can use the function with the same name but without +// the `_checked` suffix. + +import std; + +// Structure to hold the buffered data +pub struct Buffer { + content: bits[CAPACITY], + length: u32 +} + +// Status values reported by the functions operating on a Buffer +pub enum BufferStatus : u2 { + OK = 0, + NO_ENOUGH_SPACE = 1, + NO_ENOUGH_DATA = 2, +} + +// Structure for returning Buffer and BufferStatus together +pub struct BufferResult { + buffer: Buffer, + status: BufferStatus +} + +// Checks whether a `buffer` can fit `data` +pub fn buffer_can_fit(buffer: Buffer, data: bits[DSIZE]) -> bool { + buffer.length + DSIZE <= CAPACITY +} + +#[test] +fn test_buffer_can_fit() { + let buffer = Buffer { content: u32:0, length: u32:0 }; + assert_eq(buffer_can_fit(buffer, bits[0]:0), true); + assert_eq(buffer_can_fit(buffer, u16:0), true); + assert_eq(buffer_can_fit(buffer, u32:0), true); + assert_eq(buffer_can_fit(buffer, u33:0), false); + + let buffer = Buffer { content: u32:0, length: u32:16 }; + assert_eq(buffer_can_fit(buffer, bits[0]:0), true); + assert_eq(buffer_can_fit(buffer, u16:0), true); + assert_eq(buffer_can_fit(buffer, u17:0), false); + assert_eq(buffer_can_fit(buffer, u32:0), false); + + let buffer = Buffer { content: u32:0, length: u32:32 }; + assert_eq(buffer_can_fit(buffer, bits[0]:0), true); + assert_eq(buffer_can_fit(buffer, u1:0), false); + assert_eq(buffer_can_fit(buffer, u16:0), false); + assert_eq(buffer_can_fit(buffer, u32:0), false); +} + +// Checks whether a `buffer` has at least `length` amount of data +pub fn buffer_has_at_least(buffer: Buffer, length: u32) -> bool { + length <= buffer.length +} + +#[test] +fn test_buffer_has_at_least() { + let buffer = Buffer { content: u32:0, length: u32:0 }; + assert_eq(buffer_has_at_least(buffer, u32:0), true); + assert_eq(buffer_has_at_least(buffer, u32:16), false); + assert_eq(buffer_has_at_least(buffer, u32:32), false); + assert_eq(buffer_has_at_least(buffer, u32:33), false); + + let buffer = Buffer { content: u32:0, length: u32:16 }; + assert_eq(buffer_has_at_least(buffer, u32:0), true); + assert_eq(buffer_has_at_least(buffer, u32:16), true); + assert_eq(buffer_has_at_least(buffer, u32:32), false); + assert_eq(buffer_has_at_least(buffer, u32:33), false); + + let buffer = Buffer { content: u32:0, length: u32:32 }; + assert_eq(buffer_has_at_least(buffer, u32:0), true); + assert_eq(buffer_has_at_least(buffer, u32:16), true); + assert_eq(buffer_has_at_least(buffer, u32:32), true); + assert_eq(buffer_has_at_least(buffer, u32:33), false); +} + +// Returns a new buffer with `data` appended to the original `buffer`. +// It will fail if the buffer cannot fit the data. For calls that need better +// error handling, check `buffer_append_checked` +pub fn buffer_append (buffer: Buffer, data: bits[DSIZE]) -> Buffer { + if buffer_can_fit(buffer, data) == false { + trace_fmt!("Not enough space in the buffer! {} + {} <= {}", buffer.length, DSIZE, CAPACITY); + fail!("not_enough_space", buffer) + } else { + Buffer { + content: (data as bits[CAPACITY] << buffer.length) | buffer.content, + length: DSIZE + buffer.length + } + } +} + +#[test] +fn test_buffer_append() { + let buffer = Buffer { content: u32:0, length: u32:0 }; + let buffer = buffer_append(buffer, u16:0xBEEF); + assert_eq(buffer, Buffer { content: u32:0xBEEF, length: u32:16 }); + let buffer = buffer_append(buffer, u16:0xDEAD); + assert_eq(buffer, Buffer { content: u32:0xDEADBEEF, length: u32:32 }); +} + +// Returns a new buffer with the `data` appended to the original `buffer` if +// the buffer has enough space. Otherwise, it returns an unmodified buffer +// along with an error. The results are stored in the BufferResult structure. +pub fn buffer_append_checked (buffer: Buffer, data: bits[DSIZE]) -> BufferResult { + if buffer_can_fit(buffer, data) == false { + BufferResult { status: BufferStatus::NO_ENOUGH_SPACE, buffer } + } else { + BufferResult { + status: BufferStatus::OK, + buffer: buffer_append(buffer, data) + } + } +} + +#[test] +fn test_buffer_append_checked() { + let buffer = Buffer { content: u32:0, length: u32:0 }; + + let result1 = buffer_append_checked(buffer, u16:0xBEEF); + assert_eq(result1, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0xBEEF, length: u32:16 } + }); + + let result2 = buffer_append_checked(result1.buffer, u16:0xDEAD); + assert_eq(result2, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0xDEADBEEF, length: u32:32 } + }); + + let result3 = buffer_append_checked(result2.buffer, u16:0xCAFE); + assert_eq(result3, BufferResult { + status: BufferStatus::NO_ENOUGH_SPACE, + buffer: result2.buffer + }); +} + +// Returns `length` amount of data from a `buffer` and a new buffer with +// the data removed. Since the Buffer structure acts as a simple FIFO, +// it pops the data in the same order as they were added to the buffer. +// If the buffer does not have enough data to meet the specified length, +// the function will fail. For calls that need better error handling, +// check `buffer_pop_checked`. +pub fn buffer_pop(buffer: Buffer, length: u32) -> (Buffer, bits[CAPACITY]) { + if buffer_has_at_least(buffer, length) == false { + trace_fmt!("Not enough data in the buffer!"); + fail!("not_enough_data", (buffer, bits[CAPACITY]:0)) + } else { + let mask = (bits[CAPACITY]:1 << length) - bits[CAPACITY]:1; + ( + Buffer { + content: buffer.content >> length, + length: buffer.length - length + }, + buffer.content & mask + ) + } +} + +#[test] +fn test_buffer_pop() { + let buffer = Buffer { content: u32:0xDEADBEEF, length: u32:32 }; + let (buffer, data) = buffer_pop(buffer, u32:16); + assert_eq(data, u32:0xBEEF); + assert_eq(buffer, Buffer { content: u32:0xDEAD, length: u32:16 }); + let (buffer, data) = buffer_pop(buffer, u32:16); + assert_eq(data, u32:0xDEAD); + assert_eq(buffer, Buffer { content: u32:0, length: u32:0 }); +} + +// Returns `length` amount of data from a `buffer`, a new buffer with +// the data removed and a positive status, if the buffer contains enough data. +// Otherwise, it returns unmodified buffer, zeroed data field and error. +// Since the Buffer structure acts as a simple FIFO, it pops the data in +// the same order as they were added to the buffer. +// The results are stored in the BufferResult structure. +pub fn buffer_pop_checked (buffer: Buffer, length: u32) -> (BufferResult, bits[CAPACITY]) { + if buffer_has_at_least(buffer, length) == false { + ( + BufferResult { status: BufferStatus::NO_ENOUGH_DATA, buffer }, + bits[CAPACITY]:0 + ) + } else { + let (buffer_leftover, content) = buffer_pop(buffer, length); + ( + BufferResult { + status: BufferStatus::OK, + buffer: buffer_leftover + }, + content + ) + } +} + +#[test] +fn test_buffer_pop_checked() { + let buffer = Buffer { content: u32:0xDEADBEEF, length: u32:32 }; + + let (result1, data1) = buffer_pop_checked(buffer, u32:16); + assert_eq(result1, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0xDEAD, length: u32:16 } + }); + assert_eq(data1, u32:0xBEEF); + + let (result2, data2) = buffer_pop_checked(result1.buffer, u32:16); + assert_eq(result2, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0, length: u32:0 } + }); + assert_eq(data2, u32:0xDEAD); + + let (result3, data3) = buffer_pop_checked(result2.buffer, u32:16); + assert_eq(result3, BufferResult { + status: BufferStatus::NO_ENOUGH_DATA, + buffer: result2.buffer + }); + assert_eq(data3, u32:0); +} + +// Behaves like `buffer_pop` except that the length of the popped data can be +// set using a DSIZE function parameter. For calls that need better error +// handling, check `buffer_fixed_pop_checked`. +pub fn buffer_fixed_pop (buffer: Buffer) -> (Buffer, bits[DSIZE]) { + let (buffer, value) = buffer_pop(buffer, DSIZE); + (buffer, value as bits[DSIZE]) +} + +#[test] +fn test_buffer_fixed_pop() { + let buffer = Buffer { content: u32:0xDEADBEEF, length: u32:32 }; + let (buffer, data) = buffer_fixed_pop(buffer); + assert_eq(data, u16:0xBEEF); + assert_eq(buffer, Buffer { content: u32:0xDEAD, length: u32:16 }); + let (buffer, data) = buffer_fixed_pop(buffer); + assert_eq(data, u16:0xDEAD); + assert_eq(buffer, Buffer { content: u32:0, length: u32:0 }); +} + +// Behaves like `buffer_pop_checked` except that the length of the popped data +// can be set using a DSIZE function parameter. +pub fn buffer_fixed_pop_checked (buffer: Buffer) -> (BufferResult, bits[DSIZE]) { + let (result, value) = buffer_pop_checked(buffer, DSIZE); + (result, value as bits[DSIZE]) +} + +#[test] +fn test_buffer_fixed_pop_checked() { + let buffer = Buffer { content: u32:0xDEADBEEF, length: u32:32 }; + let (result1, data1) = buffer_fixed_pop_checked(buffer); + assert_eq(result1, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0xDEAD, length: u32:16 } + }); + assert_eq(data1, u16:0xBEEF); + + let (result2, data2) = buffer_fixed_pop_checked(result1.buffer); + assert_eq(result2, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0, length: u32:0 } + }); + assert_eq(data2, u16:0xDEAD); + + let (result3, data3) = buffer_fixed_pop_checked(result2.buffer); + assert_eq(result3, BufferResult { + status: BufferStatus::NO_ENOUGH_DATA, + buffer: result2.buffer + }); + assert_eq(data3, u16:0); +} + +// Returns `length` amount of data from a `buffer`. +// It will fail if the buffer has no sufficient amount of data. +// For calls that need better error handling, check `buffer_peek_checked`. +pub fn buffer_peek(buffer: Buffer, length: u32) -> bits[CAPACITY] { + if buffer_has_at_least(buffer, length) == false { + trace_fmt!("Not enough data in the buffer!"); + fail!("not_enough_data", bits[CAPACITY]:0) + } else { + let mask = (bits[CAPACITY]:1 << length) - bits[CAPACITY]:1; + buffer.content & mask + } +} + +#[test] +fn test_buffer_peek() { + let buffer = Buffer { content: u32:0xDEADBEEF, length: u32:32 }; + assert_eq(buffer_peek(buffer, u32:0), u32:0); + assert_eq(buffer_peek(buffer, u32:16), u32:0xBEEF); + assert_eq(buffer_peek(buffer, u32:32), u32:0xDEADBEEF); +} + +// Returns a new buffer with the `data` and a positive status if +// the buffer has enough data. Otherwise, it returns a zeroed-data and error. +// The results are stored in the BufferResult structure. +pub fn buffer_peek_checked (buffer: Buffer, length: u32) -> (BufferStatus, bits[CAPACITY]) { + if buffer_has_at_least(buffer, length) == false { + (BufferStatus::NO_ENOUGH_DATA, bits[CAPACITY]:0) + } else { + let mask = (bits[CAPACITY]:1 << length) - bits[CAPACITY]:1; + (BufferStatus::OK, buffer.content & mask) + } +} + +#[test] +fn test_buffer_peek_checked() { + let buffer = Buffer { content: u32:0xDEADBEEF, length: u32:32 }; + + let (status1, data1) = buffer_peek_checked(buffer, u32:0); + assert_eq(status1, BufferStatus::OK); + assert_eq(data1, u32:0); + + let (status2, data2) = buffer_peek_checked(buffer, u32:16); + assert_eq(status2, BufferStatus::OK); + assert_eq(data2, u32:0xBEEF); + + let (status3, data3) = buffer_peek_checked(buffer, u32:32); + assert_eq(status3, BufferStatus::OK); + assert_eq(data3, u32:0xDEADBEEF); + + let (status4, data4) = buffer_peek_checked(buffer, u32:64); + assert_eq(status4, BufferStatus::NO_ENOUGH_DATA); + assert_eq(data4, u32:0); +} + +// Creates a new buffer +pub fn buffer_new() -> Buffer { + Buffer { content: bits[CAPACITY]:0, length: u32:0 } +} + +#[test] +fn test_buffer_new() { + assert_eq(buffer_new(), Buffer { content: u32:0, length: u32:0 }); +} diff --git a/xls/modules/zstd/window_buffer.x b/xls/modules/zstd/window_buffer.x new file mode 100644 index 0000000000..36d4f1dc48 --- /dev/null +++ b/xls/modules/zstd/window_buffer.x @@ -0,0 +1,134 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains the implementation of a Proc which can be used to +// receive data through transactions of one width and output that data +// in transactions of other width. + +import std; +import xls.modules.zstd.buffer as buff; + +type Buffer = buff::Buffer; + +// WindowBuffer is a simple Proc that uses the Buffer structure to aggregate data +// in transactions of length and output it in transactions of +// length. defines the maximal size of the buffer. + +proc WindowBuffer { + input_r: chan in; + output_s: chan out; + + config( + input_r: chan in, + output_s: chan out + ) { (input_r, output_s) } + + init { buff::buffer_new() } + + next(tok: token, buffer: Buffer) { + const_assert!(BUFFER_SIZE >= INPUT_WIDTH); + const_assert!(BUFFER_SIZE >= OUTPUT_WIDTH); + let (tok, recv_data, valid) = recv_non_blocking(tok, input_r, uN[INPUT_WIDTH]:0); + let buffer = if (valid) { + buff::buffer_append(buffer, recv_data) + } else { + buffer + }; + + if buffer.length >= OUTPUT_WIDTH { + let (buffer, data_to_send) = buff::buffer_fixed_pop(buffer); + let tok = send(tok, output_s, data_to_send); + buffer + } else { + buffer + } + } +} + +#[test_proc] +proc WindowBufferTest { + terminator: chan out; + data32_s: chan out; + data48_r: chan in; + + config(terminator: chan out) { + let (data32_s, data32_r) = chan; + let (data48_s, data48_r) = chan; + spawn WindowBuffer(data32_r, data48_s); + (terminator, data32_s, data48_r) + } + + init {} + + next(tok: token, state: ()) { + let tok = send(tok, data32_s, u32:0xDEADBEEF); + let tok = send(tok, data32_s, u32:0xBEEFCAFE); + let tok = send(tok, data32_s, u32:0xCAFEDEAD); + + let (tok, received_data) = recv(tok, data48_r); + assert_eq(received_data, u48:0xCAFE_DEAD_BEEF); + let (tok, received_data) = recv(tok, data48_r); + assert_eq(received_data, u48:0xCAFE_DEAD_BEEF); + + send(tok, terminator, true); + } +} + +#[test_proc] +proc WindowBufferReverseTest { + terminator: chan out; + data48_s: chan out; + data32_r: chan in; + + config(terminator: chan out) { + let (data48_s, data48_r) = chan; + let (data32_s, data32_r) = chan; + spawn WindowBuffer(data48_r, data32_s); + (terminator, data48_s, data32_r) + } + + init {} + + next(tok: token, state: ()) { + let tok = send(tok, data48_s, u48:0xCAFEDEADBEEF); + let tok = send(tok, data48_s, u48:0xCAFEDEADBEEF); + + let (tok, received_data) = recv(tok, data32_r); + assert_eq(received_data, u32:0xDEADBEEF); + let (tok, received_data) = recv(tok, data32_r); + assert_eq(received_data, u32:0xBEEFCAFE); + let (tok, received_data) = recv(tok, data32_r); + assert_eq(received_data, u32:0xCAFEDEAD); + + send(tok, terminator, true); + } +} + +// Sample for codegen +proc WindowBuffer64 { + input_r: chan in; + output_s: chan out; + + config( + input_r: chan in, + output_s: chan out + ) { + spawn WindowBuffer(input_r, output_s); + (input_r, output_s) + } + + init {} + + next(tok: token, state: ()) {} +}