Skip to content

Commit 50a61f8

Browse files
rw1nklerlpawelcz
authored andcommitted
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]>
1 parent 835df36 commit 50a61f8

File tree

2 files changed

+394
-0
lines changed

2 files changed

+394
-0
lines changed

xls/modules/zstd/BUILD

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright 2023 The XLS Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Build rules for XLS ZSTD codec implementation.
16+
17+
load(
18+
"//xls/build_rules:xls_build_defs.bzl",
19+
"xls_dslx_library",
20+
"xls_dslx_test",
21+
)
22+
23+
package(
24+
default_applicable_licenses = ["//:license"],
25+
default_visibility = ["//xls:xls_users"],
26+
licenses = ["notice"],
27+
)
28+
29+
xls_dslx_library(
30+
name = "buffer_dslx",
31+
srcs = [
32+
"buffer.x",
33+
],
34+
)
35+
36+
xls_dslx_test(
37+
name = "buffer_dslx_test",
38+
library = ":buffer_dslx",
39+
)

xls/modules/zstd/buffer.x

Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
// Copyright 2023 The XLS Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// This file contains implementation of a Buffer structure that acts as
16+
// a simple FIFO. Additionally, the file provides various functions that
17+
// can simplify access to the stored.
18+
//
19+
// The utility functions containing the `_checked` suffix serve two purposes:
20+
// they perform the actual operation and return information on whether
21+
// the operation was successful. If you are sure that the precondition is
22+
// always true, you can use the function with the same name but without
23+
// the `_checked` suffix.
24+
25+
import std;
26+
27+
// Structure to hold the buffered data
28+
pub struct Buffer<CAPACITY: u32> {
29+
content: bits[CAPACITY],
30+
length: u32
31+
}
32+
33+
// Status values reported by the functions operating on a Buffer
34+
pub enum BufferStatus : u2 {
35+
OK = 0,
36+
NO_ENOUGH_SPACE = 1,
37+
NO_ENOUGH_DATA = 2,
38+
}
39+
40+
// Structure for returning Buffer and BufferStatus together
41+
pub struct BufferResult<CAPACITY: u32> {
42+
buffer: Buffer<CAPACITY>,
43+
status: BufferStatus
44+
}
45+
46+
// Checks whether a `buffer` can fit `data`
47+
pub fn buffer_can_fit<CAPACITY: u32, DSIZE: u32>(buffer: Buffer<CAPACITY>, data: bits[DSIZE]) -> bool {
48+
buffer.length + DSIZE <= CAPACITY
49+
}
50+
51+
#[test]
52+
fn test_buffer_can_fit() {
53+
let buffer = Buffer<u32:32> { content: u32:0, length: u32:0 };
54+
assert_eq(buffer_can_fit(buffer, bits[0]:0), true);
55+
assert_eq(buffer_can_fit(buffer, u16:0), true);
56+
assert_eq(buffer_can_fit(buffer, u32:0), true);
57+
assert_eq(buffer_can_fit(buffer, u33:0), false);
58+
59+
let buffer = Buffer<u32:32> { content: u32:0, length: u32:16 };
60+
assert_eq(buffer_can_fit(buffer, bits[0]:0), true);
61+
assert_eq(buffer_can_fit(buffer, u16:0), true);
62+
assert_eq(buffer_can_fit(buffer, u17:0), false);
63+
assert_eq(buffer_can_fit(buffer, u32:0), false);
64+
65+
let buffer = Buffer<u32:32> { content: u32:0, length: u32:32 };
66+
assert_eq(buffer_can_fit(buffer, bits[0]:0), true);
67+
assert_eq(buffer_can_fit(buffer, u1:0), false);
68+
assert_eq(buffer_can_fit(buffer, u16:0), false);
69+
assert_eq(buffer_can_fit(buffer, u32:0), false);
70+
}
71+
72+
// Checks whether a `buffer` has at least `length` amount of data
73+
pub fn buffer_has_at_least<CAPACITY: u32>(buffer: Buffer<CAPACITY>, length: u32) -> bool {
74+
length <= buffer.length
75+
}
76+
77+
#[test]
78+
fn test_buffer_has_at_least() {
79+
let buffer = Buffer { content: u32:0, length: u32:0 };
80+
assert_eq(buffer_has_at_least(buffer, u32:0), true);
81+
assert_eq(buffer_has_at_least(buffer, u32:16), false);
82+
assert_eq(buffer_has_at_least(buffer, u32:32), false);
83+
assert_eq(buffer_has_at_least(buffer, u32:33), false);
84+
85+
let buffer = Buffer { content: u32:0, length: u32:16 };
86+
assert_eq(buffer_has_at_least(buffer, u32:0), true);
87+
assert_eq(buffer_has_at_least(buffer, u32:16), true);
88+
assert_eq(buffer_has_at_least(buffer, u32:32), false);
89+
assert_eq(buffer_has_at_least(buffer, u32:33), false);
90+
91+
let buffer = Buffer { content: u32:0, length: u32:32 };
92+
assert_eq(buffer_has_at_least(buffer, u32:0), true);
93+
assert_eq(buffer_has_at_least(buffer, u32:16), true);
94+
assert_eq(buffer_has_at_least(buffer, u32:32), true);
95+
assert_eq(buffer_has_at_least(buffer, u32:33), false);
96+
}
97+
98+
// Returns a new buffer with `data` appended to the original `buffer`.
99+
// It will fail if the buffer cannot fit the data. For calls that need better
100+
// error handling, check `buffer_append_checked`
101+
pub fn buffer_append<CAPACITY: u32, DSIZE: u32> (buffer: Buffer<CAPACITY>, data: bits[DSIZE]) -> Buffer<CAPACITY> {
102+
if buffer_can_fit(buffer, data) == false {
103+
trace_fmt!("Not enough space in the buffer! {} + {} <= {}", buffer.length, DSIZE, CAPACITY);
104+
fail!("not_enough_space", buffer)
105+
} else {
106+
Buffer {
107+
content: (data as bits[CAPACITY] << buffer.length) | buffer.content,
108+
length: DSIZE + buffer.length
109+
}
110+
}
111+
}
112+
113+
#[test]
114+
fn test_buffer_append() {
115+
let buffer = Buffer { content: u32:0, length: u32:0 };
116+
let buffer = buffer_append(buffer, u16:0xBEEF);
117+
assert_eq(buffer, Buffer { content: u32:0xBEEF, length: u32:16 });
118+
let buffer = buffer_append(buffer, u16:0xDEAD);
119+
assert_eq(buffer, Buffer { content: u32:0xDEADBEEF, length: u32:32 });
120+
}
121+
122+
// Returns a new buffer with the `data` appended to the original `buffer` if
123+
// the buffer has enough space. Otherwise, it returns an unmodified buffer
124+
// along with an error. The results are stored in the BufferResult structure.
125+
pub fn buffer_append_checked<CAPACITY: u32, DSIZE: u32> (buffer: Buffer<CAPACITY>, data: bits[DSIZE]) -> BufferResult<CAPACITY> {
126+
if buffer_can_fit(buffer, data) == false {
127+
BufferResult { status: BufferStatus::NO_ENOUGH_SPACE, buffer }
128+
} else {
129+
BufferResult {
130+
status: BufferStatus::OK,
131+
buffer: buffer_append(buffer, data)
132+
}
133+
}
134+
}
135+
136+
#[test]
137+
fn test_buffer_append_checked() {
138+
let buffer = Buffer { content: u32:0, length: u32:0 };
139+
140+
let result1 = buffer_append_checked(buffer, u16:0xBEEF);
141+
assert_eq(result1, BufferResult {
142+
status: BufferStatus::OK,
143+
buffer: Buffer { content: u32:0xBEEF, length: u32:16 }
144+
});
145+
146+
let result2 = buffer_append_checked(result1.buffer, u16:0xDEAD);
147+
assert_eq(result2, BufferResult {
148+
status: BufferStatus::OK,
149+
buffer: Buffer { content: u32:0xDEADBEEF, length: u32:32 }
150+
});
151+
152+
let result3 = buffer_append_checked(result2.buffer, u16:0xCAFE);
153+
assert_eq(result3, BufferResult {
154+
status: BufferStatus::NO_ENOUGH_SPACE,
155+
buffer: result2.buffer
156+
});
157+
}
158+
159+
// Returns `length` amount of data from a `buffer` and a new buffer with
160+
// the data removed. Since the Buffer structure acts as a simple FIFO,
161+
// it pops the data in the same order as they were added to the buffer.
162+
// If the buffer does not have enough data to meet the specified length,
163+
// the function will fail. For calls that need better error handling,
164+
// check `buffer_pop_checked`.
165+
pub fn buffer_pop<CAPACITY: u32>(buffer: Buffer<CAPACITY>, length: u32) -> (Buffer<CAPACITY>, bits[CAPACITY]) {
166+
if buffer_has_at_least(buffer, length) == false {
167+
trace_fmt!("Not enough data in the buffer!");
168+
fail!("not_enough_data", (buffer, bits[CAPACITY]:0))
169+
} else {
170+
let mask = (bits[CAPACITY]:1 << length) - bits[CAPACITY]:1;
171+
(
172+
Buffer {
173+
content: buffer.content >> length,
174+
length: buffer.length - length
175+
},
176+
buffer.content & mask
177+
)
178+
}
179+
}
180+
181+
#[test]
182+
fn test_buffer_pop() {
183+
let buffer = Buffer { content: u32:0xDEADBEEF, length: u32:32 };
184+
let (buffer, data) = buffer_pop(buffer, u32:16);
185+
assert_eq(data, u32:0xBEEF);
186+
assert_eq(buffer, Buffer { content: u32:0xDEAD, length: u32:16 });
187+
let (buffer, data) = buffer_pop(buffer, u32:16);
188+
assert_eq(data, u32:0xDEAD);
189+
assert_eq(buffer, Buffer { content: u32:0, length: u32:0 });
190+
}
191+
192+
// Returns `length` amount of data from a `buffer`, a new buffer with
193+
// the data removed and a positive status, if the buffer contains enough data.
194+
// Otherwise, it returns unmodified buffer, zeroed data field and error.
195+
// Since the Buffer structure acts as a simple FIFO, it pops the data in
196+
// the same order as they were added to the buffer.
197+
// The results are stored in the BufferResult structure.
198+
pub fn buffer_pop_checked<CAPACITY: u32> (buffer: Buffer<CAPACITY>, length: u32) -> (BufferResult<CAPACITY>, bits[CAPACITY]) {
199+
if buffer_has_at_least(buffer, length) == false {
200+
(
201+
BufferResult { status: BufferStatus::NO_ENOUGH_DATA, buffer },
202+
bits[CAPACITY]:0
203+
)
204+
} else {
205+
let (buffer_leftover, content) = buffer_pop(buffer, length);
206+
(
207+
BufferResult {
208+
status: BufferStatus::OK,
209+
buffer: buffer_leftover
210+
},
211+
content
212+
)
213+
}
214+
}
215+
216+
#[test]
217+
fn test_buffer_pop_checked() {
218+
let buffer = Buffer { content: u32:0xDEADBEEF, length: u32:32 };
219+
220+
let (result1, data1) = buffer_pop_checked(buffer, u32:16);
221+
assert_eq(result1, BufferResult {
222+
status: BufferStatus::OK,
223+
buffer: Buffer { content: u32:0xDEAD, length: u32:16 }
224+
});
225+
assert_eq(data1, u32:0xBEEF);
226+
227+
let (result2, data2) = buffer_pop_checked(result1.buffer, u32:16);
228+
assert_eq(result2, BufferResult {
229+
status: BufferStatus::OK,
230+
buffer: Buffer { content: u32:0, length: u32:0 }
231+
});
232+
assert_eq(data2, u32:0xDEAD);
233+
234+
let (result3, data3) = buffer_pop_checked(result2.buffer, u32:16);
235+
assert_eq(result3, BufferResult {
236+
status: BufferStatus::NO_ENOUGH_DATA,
237+
buffer: result2.buffer
238+
});
239+
assert_eq(data3, u32:0);
240+
}
241+
242+
// Behaves like `buffer_pop` except that the length of the popped data can be
243+
// set using a DSIZE function parameter. For calls that need better error
244+
// handling, check `buffer_fixed_pop_checked`.
245+
pub fn buffer_fixed_pop<CAPACITY: u32, DSIZE: u32> (buffer: Buffer<CAPACITY>) -> (Buffer<CAPACITY>, bits[DSIZE]) {
246+
let (buffer, value) = buffer_pop(buffer, DSIZE);
247+
(buffer, value as bits[DSIZE])
248+
}
249+
250+
#[test]
251+
fn test_buffer_fixed_pop() {
252+
let buffer = Buffer { content: u32:0xDEADBEEF, length: u32:32 };
253+
let (buffer, data) = buffer_fixed_pop<u32:32, u32:16>(buffer);
254+
assert_eq(data, u16:0xBEEF);
255+
assert_eq(buffer, Buffer { content: u32:0xDEAD, length: u32:16 });
256+
let (buffer, data) = buffer_fixed_pop<u32:32, u32:16>(buffer);
257+
assert_eq(data, u16:0xDEAD);
258+
assert_eq(buffer, Buffer { content: u32:0, length: u32:0 });
259+
}
260+
261+
// Behaves like `buffer_pop_checked` except that the length of the popped data
262+
// can be set using a DSIZE function parameter.
263+
pub fn buffer_fixed_pop_checked<CAPACITY: u32, DSIZE: u32> (buffer: Buffer<CAPACITY>) -> (BufferResult<CAPACITY>, bits[DSIZE]) {
264+
let (result, value) = buffer_pop_checked(buffer, DSIZE);
265+
(result, value as bits[DSIZE])
266+
}
267+
268+
#[test]
269+
fn test_buffer_fixed_pop_checked() {
270+
let buffer = Buffer { content: u32:0xDEADBEEF, length: u32:32 };
271+
let (result1, data1) = buffer_fixed_pop_checked<u32:32, u32:16>(buffer);
272+
assert_eq(result1, BufferResult {
273+
status: BufferStatus::OK,
274+
buffer: Buffer { content: u32:0xDEAD, length: u32:16 }
275+
});
276+
assert_eq(data1, u16:0xBEEF);
277+
278+
let (result2, data2) = buffer_fixed_pop_checked<u32:32, u32:16>(result1.buffer);
279+
assert_eq(result2, BufferResult {
280+
status: BufferStatus::OK,
281+
buffer: Buffer { content: u32:0, length: u32:0 }
282+
});
283+
assert_eq(data2, u16:0xDEAD);
284+
285+
let (result3, data3) = buffer_fixed_pop_checked<u32:32, u32:16>(result2.buffer);
286+
assert_eq(result3, BufferResult {
287+
status: BufferStatus::NO_ENOUGH_DATA,
288+
buffer: result2.buffer
289+
});
290+
assert_eq(data3, u16:0);
291+
}
292+
293+
// Returns `length` amount of data from a `buffer`.
294+
// It will fail if the buffer has no sufficient amount of data.
295+
// For calls that need better error handling, check `buffer_peek_checked`.
296+
pub fn buffer_peek<CAPACITY: u32>(buffer: Buffer<CAPACITY>, length: u32) -> bits[CAPACITY] {
297+
if buffer_has_at_least(buffer, length) == false {
298+
trace_fmt!("Not enough data in the buffer!");
299+
fail!("not_enough_data", bits[CAPACITY]:0)
300+
} else {
301+
let mask = (bits[CAPACITY]:1 << length) - bits[CAPACITY]:1;
302+
buffer.content & mask
303+
}
304+
}
305+
306+
#[test]
307+
fn test_buffer_peek() {
308+
let buffer = Buffer { content: u32:0xDEADBEEF, length: u32:32 };
309+
assert_eq(buffer_peek(buffer, u32:0), u32:0);
310+
assert_eq(buffer_peek(buffer, u32:16), u32:0xBEEF);
311+
assert_eq(buffer_peek(buffer, u32:32), u32:0xDEADBEEF);
312+
}
313+
314+
// Returns a new buffer with the `data` and a positive status if
315+
// the buffer has enough data. Otherwise, it returns a zeroed-data and error.
316+
// The results are stored in the BufferResult structure.
317+
pub fn buffer_peek_checked<CAPACITY: u32> (buffer: Buffer<CAPACITY>, length: u32) -> (BufferStatus, bits[CAPACITY]) {
318+
if buffer_has_at_least(buffer, length) == false {
319+
(BufferStatus::NO_ENOUGH_DATA, bits[CAPACITY]:0)
320+
} else {
321+
let mask = (bits[CAPACITY]:1 << length) - bits[CAPACITY]:1;
322+
(BufferStatus::OK, buffer.content & mask)
323+
}
324+
}
325+
326+
#[test]
327+
fn test_buffer_peek_checked() {
328+
let buffer = Buffer { content: u32:0xDEADBEEF, length: u32:32 };
329+
330+
let (status1, data1) = buffer_peek_checked(buffer, u32:0);
331+
assert_eq(status1, BufferStatus::OK);
332+
assert_eq(data1, u32:0);
333+
334+
let (status2, data2) = buffer_peek_checked(buffer, u32:16);
335+
assert_eq(status2, BufferStatus::OK);
336+
assert_eq(data2, u32:0xBEEF);
337+
338+
let (status3, data3) = buffer_peek_checked(buffer, u32:32);
339+
assert_eq(status3, BufferStatus::OK);
340+
assert_eq(data3, u32:0xDEADBEEF);
341+
342+
let (status4, data4) = buffer_peek_checked(buffer, u32:64);
343+
assert_eq(status4, BufferStatus::NO_ENOUGH_DATA);
344+
assert_eq(data4, u32:0);
345+
}
346+
347+
// Creates a new buffer
348+
pub fn buffer_new<CAPACITY: u32>() -> Buffer<CAPACITY> {
349+
Buffer { content: bits[CAPACITY]:0, length: u32:0 }
350+
}
351+
352+
#[test]
353+
fn test_buffer_new() {
354+
assert_eq(buffer_new<u32:32>(), Buffer { content: u32:0, length: u32:0 });
355+
}

0 commit comments

Comments
 (0)