Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

modules/zstd: add buffer module #1167

Closed
wants to merge 3 commits into from
Closed

Conversation

rdob-ant
Copy link
Contributor

This commit adds a DSLX Buffer library that provides the Buffer struct, and helper functions that can be used to operate on it. The Buffer is meant to be a storage for data coming from the channel. It acts like a FIFO containing bits, allowing data of any length to be put in or popped out of it. Includes DSLX tests to verify the behavior of the library.

This bitwise nature of the buffer is expected to be useful later when dealing with FSE-encoded data.

NOTE: this is based on top of #1166 , please ignore commits from that branch when reviewing.

xls/modules/zstd/buffer.x Outdated Show resolved Hide resolved
xls/modules/zstd/buffer.x Outdated Show resolved Hide resolved
@rw1nkler rw1nkler force-pushed the 50221-buffer branch 2 times, most recently from 504368a to 0c17a99 Compare October 31, 2023 12:54
@proppy proppy changed the title modules/zstd: add buffer module (part 2) modules/zstd: add buffer module Nov 9, 2023
xls/modules/zstd/buffer.x Outdated Show resolved Hide resolved
xls/modules/zstd/buffer.x Outdated Show resolved Hide resolved
xls/modules/zstd/buffer.x Outdated Show resolved Hide resolved
xls/modules/zstd/buffer.x Outdated Show resolved Hide resolved
xls/modules/zstd/buffer.x Outdated Show resolved Hide resolved
()
};

let mask = (bits[BSIZE]:1 << length) - bits[BSIZE]:1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could use bitslice instead.

buffer.contents[0:length]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same problem as in #1167 (comment)

xls/modules/zstd/buffer.x Outdated Show resolved Hide resolved
xls/modules/zstd/buffer.x Outdated Show resolved Hide resolved
xls/modules/zstd/buffer.x Outdated Show resolved Hide resolved
xls/modules/zstd/buffer.x Outdated Show resolved Hide resolved
xls/modules/zstd/buffer.x Outdated Show resolved Hide resolved
xls/modules/zstd/buffer.x Outdated Show resolved Hide resolved
xls/modules/zstd/buffer.x Outdated Show resolved Hide resolved
xls/modules/zstd/buffer.x Outdated Show resolved Hide resolved
@rw1nkler rw1nkler force-pushed the 50221-buffer branch 2 times, most recently from 9382127 to 37ca6a2 Compare November 14, 2023 15:42
@lpawelcz lpawelcz force-pushed the 50221-buffer branch 2 times, most recently from 4326a46 to 01f2dcd Compare January 15, 2024 14:50
// 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<CAPACITY: u32, DSIZE: u32> (buffer: Buffer<CAPACITY>, data: bits[DSIZE]) -> Buffer<CAPACITY> {
if buffer_can_fit(buffer, data) == false {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: should we get rid of the check here, since it is supposed to be the unchecked version?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked/unchecked variants refer to the way the failure is reported in resulting verilog.
For the unchecked variant this check only indicates that something went wrong at the IR simulation level. Codegen verilog for that won't have any logic for that.
I think it is good idea to keep this check to signal the user potential issues in his/hers DSLX logic

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since the fail! built-in emit as $fatal in verilog I do think the associated logic for compute the condition will also get codegen'ed (but maybe optimized away by synthesis?)

rather than having two variant of the function, maybe we could only keep the unchecked one (w/ the fail!) and have the caller rely on calling can_fit if they need to do something dynamic?

wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I remember $fatal is available only in SystemVerilog. For our benchmarks we do codegen with Verilog which does not generate any logic for this. On the other hand, it generates $display() for trace_fmt!() and those are then removed in the synthesis process so it is possible that logic for $fatal would also be removed.

rather than having two variant of the function, maybe we could only keep the unchecked one (w/ the fail!) and have the caller rely on calling can_fit if they need to do something dynamic?

This is good idea, we can probably do that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the other hand when I looked closely at the implementation after removing _checked variants of the functions it does not look that good. For example, we use _checked functions in frame header decoding in 4 places in single frame_header.x file. Removing _checked functions would require placing checks straight in the logic of frame header decoder. It would look something like that for each usage of _checked variant of the functions:

diff --git a/xls/modules/zstd/frame_header.x b/xls/modules/zstd/frame_header.x
index 785586396..01cd927cd 100644
--- a/xls/modules/zstd/frame_header.x
+++ b/xls/modules/zstd/frame_header.x
@@ -115,7 +115,24 @@ fn test_extract_frame_header_descriptor() {
 // returns BufferResult with the outcome of the operations on the buffer and
 // information extracted from the Frame_Header_Descriptor
 fn parse_frame_header_descriptor<CAPACITY: u32>(buffer: Buffer<CAPACITY>) -> (BufferResult<CAPACITY>, FrameHeaderDescriptor) {
-    let (result, data) = buff::buffer_fixed_pop_checked<CAPACITY, u32:8>(buffer);
+    let (result, data) = if (buff:buffer_has_at_least(buffer, u32:8)) {
+        let (buffer_leftover, content) = buff::buffer_fixed_pop<CAPACITY, u32:8>(buffer);
+        (
+            buff::BufferResult {
+                status: buff:BufferStatus::OK,
+                buffer: buffer_leftover
+            },
+            content
+        )
+    } else {
+        (
+            buff::BufferResult {
+                status: buff::BufferStatus:NO_ENOUGH_DATA,
+                buffer: buffer
+            },
+            bits[CAPACITY]:0
+        )
+    };
     match result.status {
         BufferStatus::OK => {
             let frame_header_desc = extract_frame_header_descriptor(data);

It is possible to put the code from match straight under the if but this will still lead to unnecessary code duplication. Maybe it would be better to keep those _checked functions?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be curious to see what the calling logic does when handling buff::BufferStatus:NO_ENOUGH_DATA, can you point me to it?

My reasoning is that if you're gonna act on something related to the size of the buffer, you'd rather do this early on to make it explicit how you handle this case from the protocol pov, rather than relying on propagating the status back from the buffer call.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so the part that is responsible for handling NO_ENOUGH_DATA is the top level proc of the ZstdDecoder. The code for this is not a part of any open PR yet as we still work on fixing bugs found in our C++ tests where we compare our decoder against libzstd.
Top level proc has states which mark the stage of frame decoding, e.g.:

enum ZstdDecoderStatus : u8 {
  DECODE_MAGIC_NUMBER = 0,
  DECODE_FRAME_HEADER = 1,
  DECODE_BLOCK_HEADER = 2,
  FEED_BLOCK_DECODER = 3,
  DECODE_CHECKSUM = 4,
  ERROR = 255,
}

When NO_ENOUGH_DATA occurs we just stay in the current state for the next evaluation where we receive data from decoder input and append it to the buffer. Then we can try again to decode the part specified by the state.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've opened a PR which contains the mentioned code. It's #1315. You can check the example of handling NO_ENOUGH_DATA here.

BufferResult {
status: BufferStatus::OK,
buffer: Buffer {
content: (data as bits[CAPACITY] << buffer.length) | buffer.content,
Copy link
Member

@proppy proppy Jan 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: should we try to reuse buffer_append_unchecked here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, thanks!

// the function will fail. For calls that need better error handling,
// check `buffer_pop_checked`.
pub fn buffer_pop<CAPACITY: u32>(buffer: Buffer<CAPACITY>, length: u32) -> (Buffer<CAPACITY>, bits[CAPACITY]) {
if buffer_has_at_least(buffer, length) == false {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: should we get rid of the check here, since it is supposed to be the unchecked version?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly as with buffer_append() this only serves the purpose of informing the user about potential issues at the stage of IR simulation. I think it is useful to have this.

// can be used is presented below. It uses the structure to combine several
// smaller transactions into bigger ones.

proc BufferExampleUsage {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe that should go a in separate file?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, would you like to have it here under xls/modules/zstd or in xls/examples (see my comment bellow)?

Copy link
Member

@proppy proppy Jan 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

under zstd is fine for now I think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, no problem

// can be used is presented below. It uses the structure to combine several
// smaller transactions into bigger ones.

proc BufferExampleUsage {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: sounds like this could be a generally useful parametric proc (and not just a sample) to window channel data differently?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to be a good idea. If we want to have this example as parametric proc, then it would be reasonable to have it here in xls/modules/zstd and a specialization of it under xls/examples

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could keep the proc and the example in zstd for now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem, I will update the PR after I check if my changes didn't affect other zstd procs.

@lpawelcz lpawelcz force-pushed the 50221-buffer branch 2 times, most recently from a45b143 to c512a76 Compare February 15, 2024 11:09
@lpawelcz
Copy link
Contributor

@proppy the example is now placed in a separate file and the proc is renamed to WindowBuffer. It is parameterized with buffer size, input and output width. There is also a specialized version of this proc available in the same file for the purpose of verilog generation and benchmarking.


// 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update the comment?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

// 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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update the comment?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

// in transactions of <INPUT_WIDTH> length and output it in transactions of
// <OUTPUT_WIDTH> length. <BUFFER_SIZE> defines the maximal size of the buffer.

proc WindowBuffer<BUFFER_SIZE: u32, INPUT_WIDTH: u32, OUTPUT_WIDTH: u32> {
Copy link
Member

@proppy proppy Feb 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a relation between BUFFER_SIZE, INPUT_WIDTH and OUTPUT_WIDTH that we can const_assert?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can for sure assert BUFFER_SIZE >= INPUT WIDTH and BUFFER_SIZE >= OUTPUT_WIDTH.


next(tok: token, buffer: Buffer<BUFFER_SIZE>) {
let (tok, recv_data) = recv(tok, input_r);
let buffer = buff::buffer_append<BUFFER_SIZE>(buffer, recv_data);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't BUFFER_SIZE be inferred from the buffer arg?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, fixed

let buffer = buff::buffer_append<BUFFER_SIZE>(buffer, recv_data);

if buffer.length >= OUTPUT_WIDTH {
let (buffer, data_to_send) = buff::buffer_fixed_pop<BUFFER_SIZE, OUTPUT_WIDTH>(buffer);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't BUFFER_SIZE be inferred from the buffer arg?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to change the ordering of the parameters for buffer functions so that BUFFER_SIZE is now the last on the list. Fixed.

}

#[test_proc]
proc WindowBufferTest {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add a test with INPUT_WIDTH > OUTPUT_WIDTH for completeness?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

// Sample for codegen
proc WindowBuffer64 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for adding this!

rw1nkler and others added 3 commits February 21, 2024 14:57
This commit adds a DSLX Buffer library that provides the Buffer struct,
and helper functions that can be used to operate on it. The Buffer
is meant to be a storage for data coming from the channel. It acts like
a FIFO, allowing data of any length to be put in or popped out of it.
Provided DSLX tests verify the correct behaviour of the library.

Internal-tag: [#50221]
Signed-off-by: Robert Winkler <[email protected]>
This commit adds a simple test that shows, how one can use the Buffer
struct inside a Proc.

Internal-tag: [#50221]
Signed-off-by: Robert Winkler <[email protected]>
lpawelcz pushed a commit to antmicro/xls that referenced this pull request Feb 21, 2024
google#1167

modules/zstd: Add buffer library

This commit adds a DSLX Buffer library that provides the Buffer struct,
and helper functions that can be used to operate on it. The Buffer
is meant to be a storage for data coming from the channel. It acts like
a FIFO, allowing data of any length to be put in or popped out of it.
Provided DSLX tests verify the correct behaviour of the library.

Internal-tag: [#50221]
Signed-off-by: Robert Winkler <[email protected]>

modules/zstd: Add Buffer use-case example

This commit adds a simple test that shows, how one can use the Buffer
struct inside a Proc.

Internal-tag: [#50221]
Signed-off-by: Robert Winkler <[email protected]>
lpawelcz pushed a commit to antmicro/xls that referenced this pull request Mar 7, 2024
google#1167

modules/zstd: Add buffer library

This commit adds a DSLX Buffer library that provides the Buffer struct,
and helper functions that can be used to operate on it. The Buffer
is meant to be a storage for data coming from the channel. It acts like
a FIFO, allowing data of any length to be put in or popped out of it.
Provided DSLX tests verify the correct behaviour of the library.

Internal-tag: [#50221]
Signed-off-by: Robert Winkler <[email protected]>

modules/zstd: Add Buffer use-case example

This commit adds a simple test that shows, how one can use the Buffer
struct inside a Proc.

Internal-tag: [#50221]
Signed-off-by: Robert Winkler <[email protected]>

modules/zstd/buffer: Add benchmarking rules

Signed-off-by: Pawel Czarnecki <[email protected]>
@cdleary cdleary added the app Application level functionality (examples, uses of XLS stack) label Mar 27, 2024
@lpawelcz
Copy link
Contributor

The review will take place in #1315

@proppy this PR can be closed

@proppy proppy closed this Mar 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
app Application level functionality (examples, uses of XLS stack) Reviewing Internally
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants