diff --git a/docs_src/dslx_ffi.md b/docs_src/dslx_ffi.md index f3ac035f24..a4ebe389c2 100644 --- a/docs_src/dslx_ffi.md +++ b/docs_src/dslx_ffi.md @@ -39,9 +39,7 @@ as well as a implementation that is functionally equivalent. Here, there is one mapped to the return of the DSLX function: ```dslx -fn foo(a: u32) -> u32 { - a + u32:1 -} +fn foo(a: u32) -> u32 { a + u32:1 } ``` You can now add an annotation `#[extern_verilog("...")]` to the DSLX function @@ -57,7 +55,7 @@ myfoo {fn} ( // Placeholder for the instantiation name. ); // Semicolon optional, code-generation will always add one. ")] fn foo(a: u32) -> u32 { - a + u32:1 + a + u32:1 } ``` @@ -96,8 +94,8 @@ myfoo {fn} #( .out({return}) ) ")] -fn foo(a:bits[WIDTH]) -> bits[WIDTH] { - a + uN[WIDTH]:1 +fn foo(a: bits[WIDTH]) -> bits[WIDTH] { + a + uN[WIDTH]:1 } ``` @@ -121,8 +119,8 @@ mybar {fn} ( .otherout({return.1}) ) ")] -fn bar(a:(s32, s32), b:s32) -> (s32, s32) { - (a.0 + a.1, b) +fn bar(a: (s32, s32), b: s32) -> (s32, s32) { + (a.0 + a.1, b) } ``` @@ -160,8 +158,8 @@ mybaz {fn} #( .out({return}) ) ")] -fn baz(a:u32, b:u32, c:u32) -> u32 { - u32:42 // local implementation +fn baz(a: u32, b: u32, c: u32) -> u32 { + u32:42 // local implementation } ``` @@ -190,8 +188,8 @@ myquux {fn} ( assign {return} = ({RESULT_BITS})'({return}_adapted_to_module); ")] -fn quux(a:u32) -> uN[RESULT_BITS] { - a as uN[RESULT_BITS] +fn quux(a: u32) -> uN[RESULT_BITS] { + a as uN[RESULT_BITS] } ``` diff --git a/docs_src/dslx_reference.md b/docs_src/dslx_reference.md index 0fd9098cdd..1503557086 100644 --- a/docs_src/dslx_reference.md +++ b/docs_src/dslx_reference.md @@ -135,11 +135,11 @@ Simple examples: ```dslx fn ret3() -> u32 { - u32:3 // This function always returns 3. + u32:3 // This function always returns 3. } fn add1(x: u32) -> u32 { - x + u32:1 // Returns x + 1, but you knew that! + x + u32:1 // Returns x + 1, but you knew that! } ``` @@ -176,17 +176,11 @@ DSLX functions can be parameterized in terms of the types of its arguments and in terms of types derived from other parametric values. For instance: ```dslx -fn double(n: u32) -> u32 { - n * u32:2 -} +fn double(n: u32) -> u32 { n * u32:2 } -fn self_append(x: bits[A]) -> bits[B] { - x++x -} +fn self_append(x: bits[A]) -> bits[B] { x ++ x } -fn main() -> bits[10] { - self_append(u5:1) -} +fn main() -> bits[10] { self_append(u5:1) } ``` In `self_append(bits[5]:1)`, we see that `A = 5` based off of formal argument @@ -206,8 +200,8 @@ such as in the [`explicit_parametric_simple.x`](https://github.com/google/xls/tree/main/xls/dslx/tests/explicit_parametric_simple.x) test: -``` -fn add_one(lhs: bits[E]) -> bits[G] { ... } +```dslx-snippet +fn add_one(lhs: bits[E]) -> bits[G] { ... } ``` For this call to instantiable, both `E` and `F` must be specified. Since `F` @@ -251,12 +245,9 @@ Function calls are expressions and look and feel just like one would expect from other languages. For example: ```dslx -fn callee(x: bits[32], y: bits[32]) -> bits[32] { - x + y -} -fn caller() -> u32 { - callee(u32:2, u32:3) -} +fn callee(x: bits[32], y: bits[32]) -> bits[32] { x + y } + +fn caller() -> u32 { callee(u32:2, u32:3) } ``` If more than one value should be returned by a function, a tuple type should be @@ -324,9 +315,9 @@ fn p() -> xN[S][N] { xN[S][N]:0 } #[test] fn test_parametric_signedness() { - assert_eq(p(), u32:0); - assert_eq(p(), s32:0); - assert_eq(p(), s64:0); + assert_eq(p(), u32:0); + assert_eq(p(), s32:0); + assert_eq(p(), s64:0); } ``` @@ -361,14 +352,12 @@ Characters can be used just as traditional bits: ```dslx fn add_to_null(input: u8) -> u8 { - let null:u8 = '\0'; - input + null + let null: u8 = '\0'; + input + null } #[test] -fn test_main() { - assert_eq('a', add_to_null('a')) -} +fn test_main() { assert_eq('a', add_to_null('a')) } ``` DSLX character constants support the @@ -382,14 +371,12 @@ named constants that do not pollute the module namespace. For example: ```dslx enum Opcode : u3 { - FIRE_THE_MISSILES = 0, - BE_TIRED = 1, - TAKE_A_NAP = 2, + FIRE_THE_MISSILES = 0, + BE_TIRED = 1, + TAKE_A_NAP = 2, } -fn get_my_favorite_opcode() -> Opcode { - Opcode::FIRE_THE_MISSILES -} +fn get_my_favorite_opcode() -> Opcode { Opcode::FIRE_THE_MISSILES } ``` Note the use of the double-colon to reference the enum value. This code @@ -399,7 +386,7 @@ value outside of the representable `u3` range will produce a compile time error. ```dslx-bad enum Opcode : u3 { - FOO = 8 // Causes compile time error! + FOO = 8 // Causes compile time error! } ``` @@ -407,20 +394,20 @@ Enums can be compared for equality/inequality, but they do not permit arithmetic operations, they must be cast to numerical types in order to perform arithmetic: ```dslx -enum Opcode: u3 { - NOP = 0, - ADD = 1, - SUB = 2, - MUL = 3, +enum Opcode : u3 { + NOP = 0, + ADD = 1, + SUB = 2, + MUL = 3, } fn same_opcode(x: Opcode, y: Opcode) -> bool { - x == y // ok + x == y // ok } fn next_in_sequence(x: Opcode, y: Opcode) -> bool { - // x+1 == y // does not work, arithmetic! - x as u3 + u3:1 == (y as u3) // ok, casted first + // x+1 == y // does not work, arithmetic! + x as u3 + u3:1 == (y as u3) // ok, casted first } ``` @@ -432,19 +419,17 @@ extension/truncation behavior.) ```dslx enum MySignedEnum : s3 { - LOW = -1, - ZERO = 0, - HIGH = 1, + LOW = -1, + ZERO = 0, + HIGH = 1, } fn extend_to_32b(x: MySignedEnum) -> u32 { - x as u32 // Sign-extends because the source type is signed. + x as u32 // Sign-extends because the source type is signed. } #[test] -fn test_extend_to_32b() { - assert_eq(extend_to_32b(MySignedEnum::LOW), u32:0xffffffff) -} +fn test_extend_to_32b() { assert_eq(extend_to_32b(MySignedEnum::LOW), u32:0xffffffff) } ``` Casting *to* an enum is also permitted. However, in most cases errors from @@ -478,7 +463,7 @@ Example of a tuple type: // the 1st element is of type u8 // the 2nd element is another tuple with 1 element of type u16 // the 3rd element is of type u8 -type MyTuple = (u32, (u8, (u16,), u8)); +type MyTuple = (u32, (u8, (u16), u8)); ``` To access individual tuple elements use simple indices, starting at 0. For @@ -487,8 +472,8 @@ example, to access the second element of a tuple (index 1): ```dslx #[test] fn test_tuple_access() { - let t = (u32:2, u8:3); - assert_eq(u8:3, t.1) + let t = (u32:2, u8:3); + assert_eq(u8:3, t.1) } ``` @@ -547,26 +532,20 @@ positions), and we introduce a new type. The following syntax is used to define a struct: ```dslx -struct Point { - x: u32, - y: u32 -} +struct Point { x: u32, y: u32 } ``` Once a struct is defined it can be constructed by naming the fields in any order: ```dslx -struct Point { - x: u32, - y: u32, -} +struct Point { x: u32, y: u32 } #[test] fn test_struct_equality() { - let p0 = Point { x: u32:42, y: u32:64 }; - let p1 = Point { y: u32:64, x: u32:42 }; - assert_eq(p0, p1) + let p0 = Point { x: u32:42, y: u32:64 }; + let p1 = Point { y: u32:64, x: u32:42 }; + assert_eq(p0, p1) } ``` @@ -574,38 +553,29 @@ There is a simple syntax when creating a struct whose field names match the names of in-scope values: ```dslx -struct Point { x: u32, y: u32, } +struct Point { x: u32, y: u32 } #[test] fn test_struct_equality() { - let x = u32:42; - let y = u32:64; - let p0 = Point { x, y }; - let p1 = Point { y, x }; - assert_eq(p0, p1) + let x = u32:42; + let y = u32:64; + let p0 = Point { x, y }; + let p1 = Point { y, x }; + assert_eq(p0, p1) } ``` Struct fields can also be accessed with "dot" syntax: ```dslx -struct Point { - x: u32, - y: u32, -} +struct Point { x: u32, y: u32 } -fn f(p: Point) -> u32 { - p.x + p.y -} +fn f(p: Point) -> u32 { p.x + p.y } -fn main() -> u32 { - f(Point { x: u32:42, y: u32:64 }) -} +fn main() -> u32 { f(Point { x: u32:42, y: u32:64 }) } #[test] -fn test_main() { - assert_eq(u32:106, main()) -} +fn test_main() { assert_eq(u32:106, main()) } ``` Note that structs cannot be mutated "in place", the user must construct new @@ -613,25 +583,19 @@ values by extracting the fields of the original struct mixed together with new field values, as in the following: ```dslx -struct Point3 { - x: u32, - y: u32, - z: u32, -} +struct Point3 { x: u32, y: u32, z: u32 } -fn update_y(p: Point3, new_y: u32) -> Point3 { - Point3 { x: p.x, y: new_y, z: p.z } -} +fn update_y(p: Point3, new_y: u32) -> Point3 { Point3 { x: p.x, y: new_y, z: p.z } } fn main() -> Point3 { - let p = Point3 { x: u32:42, y: u32:64, z: u32:256 }; - update_y(p, u32:128) + let p = Point3 { x: u32:42, y: u32:64, z: u32:256 }; + update_y(p, u32:128) } #[test] fn test_main() { - let want = Point3 { x: u32:42, y: u32:128, z: u32:256 }; - assert_eq(want, main()) + let want = Point3 { x: u32:42, y: u32:128, z: u32:256 }; + assert_eq(want, main()) } ``` @@ -641,19 +605,11 @@ The DSL has syntax for conveniently producing a new value with a subset of fields updated to reduce verbosity. The "struct update" syntax is: ```dslx -struct Point3 { - x: u32, - y: u32, - z: u32, -} +struct Point3 { x: u32, y: u32, z: u32 } -fn update_y(p: Point3) -> Point3 { - Point3 { y: u32:42, ..p } -} +fn update_y(p: Point3) -> Point3 { Point3 { y: u32:42, ..p } } -fn update_x_and_y(p: Point3) -> Point3 { - Point3 { x: u32:42, y: u32:42, ..p } -} +fn update_x_and_y(p: Point3) -> Point3 { Point3 { x: u32:42, y: u32:42, ..p } } ``` #### Parametric Structs @@ -665,19 +621,14 @@ section. ```dslx fn double(n: u32) -> u32 { n * u32:2 } -struct Point { - x: bits[N], - y: bits[M], -} +struct Point { x: bits[N], y: bits[M] } -fn make_point(x: bits[A], y: bits[B]) -> Point { - Point{ x, y } -} +fn make_point(x: bits[A], y: bits[B]) -> Point { Point { x, y } } #[test] fn test_struct_construction() { - let p = make_point(u16:42, u32:42); - assert_eq(u16:42, p.x) + let p = make_point(u16:42, u32:42); + assert_eq(u16:42, p.x) } ``` @@ -691,30 +642,20 @@ when all the fields of two structures are identical, those structures are a different type when they have a different name). ```dslx -struct Point { - x: u32, - y: u32, -} +struct Point { x: u32, y: u32 } -struct Coordinate { - x: u32, - y: u32, -} +struct Coordinate { x: u32, y: u32 } -fn f(p: Point) -> u32 { - p.x + p.y -} +fn f(p: Point) -> u32 { p.x + p.y } #[test] -fn test_ok() { - assert_eq(f(Point { x: u32:42, y: u32:64 }), u32:106) -} +fn test_ok() { assert_eq(f(Point { x: u32:42, y: u32:64 }), u32:106) } ``` ```dslx-bad #[test] fn test_type_checker_error() { - assert_eq(f(Coordinate { x: u32:42, y: u32:64 }), u32:106) + assert_eq(f(Coordinate { x: u32:42, y: u32:64 }), u32:106) } ``` @@ -725,18 +666,16 @@ array must have the same type. Arrays can be indexed with indexing notation (`a[i]`) to retrieve a single element. ```dslx -fn main(a: u32[2], i: u1) -> u32 { - a[i] -} +fn main(a: u32[2], i: u1) -> u32 { a[i] } #[test] fn test_main() { - let x = u32:42; - let y = u32:64; - // Make an array with "bracket notation". - let my_array: u32[2] = [x, y]; - assert_eq(main(my_array, u1:0), x); - assert_eq(main(my_array, u1:1), y); + let x = u32:42; + let y = u32:64; + // Make an array with "bracket notation". + let my_array: u32[2] = [x, y]; + assert_eq(main(my_array, u1:0), x); + assert_eq(main(my_array, u1:1), y); } ``` @@ -747,14 +686,12 @@ fill, in order to use the ellipsis the type must be annotated explicitly as shown. ```dslx -fn make_array(x: u32) -> u32[3] { - u32[3]:[u32:42, x, ...] -} +fn make_array(x: u32) -> u32[3] { u32[3]:[u32:42, x, ...] } #[test] fn test_make_array() { - assert_eq(u32[3]:[u32:42, u32:42, u32:42], make_array(u32:42)); - assert_eq(u32[3]:[u32:42, u32:64, u32:64], make_array(u32:64)); + assert_eq(u32[3]:[u32:42, u32:42, u32:42], make_array(u32:42)); + assert_eq(u32[3]:[u32:42, u32:64, u32:64], make_array(u32:64)); } ``` @@ -774,15 +711,13 @@ arrays of u8 elements. String constants can be used just as traditional arrays: ```dslx fn add_one(input: u8[N]) -> u8[N] { - for (i, result) : (u32, u8[N]) in u32:0..N { - update(result, i, result[i] + u8:1) - }(input) + for (i, result): (u32, u8[N]) in u32:0..N { + update(result, i, result[i] + u8:1) + }(input) } #[test] -fn test_main() { - assert_eq("bcdef", add_one("abcde")) -} +fn test_main() { assert_eq("bcdef", add_one("abcde")) } ``` DSLX string constants support the @@ -794,14 +729,10 @@ words, the sequence `\u{10CB2F}` will result in an array with hexadecimal values Moreover, string can be composed of [characters](#character-constants). ```dslx -fn string_composed_characters() -> u8[10] { - ['X', 'L', 'S', ' ', 'r', 'o', 'c', 'k', 's', '!'] -} +fn string_composed_characters() -> u8[10] { ['X', 'L', 'S', ' ', 'r', 'o', 'c', 'k', 's', '!'] } #[test] -fn test_main() { - assert_eq("XLS rocks!", string_composed_characters()) -} +fn test_main() { assert_eq("XLS rocks!", string_composed_characters()) } ``` ### Type Aliases @@ -823,14 +754,12 @@ import xls.dslx.tests.mod_imported; type MyEnum = mod_imported::MyEnum; -fn main(x: u8) -> MyEnum { - x as MyEnum -} +fn main(x: u8) -> MyEnum { x as MyEnum } #[test] fn test_main() { - assert_eq(main(u8:42), MyEnum::FOO); - assert_eq(main(u8:64), MyEnum::BAR); + assert_eq(main(u8:42), MyEnum::FOO); + assert_eq(main(u8:64), MyEnum::BAR); } ``` @@ -861,38 +790,38 @@ semantics. ```dslx #[test] fn test_narrow_cast() { - let twelve = u4:0b1100; - assert_eq(twelve as u2, u2:0) + let twelve = u4:0b1100; + assert_eq(twelve as u2, u2:0) } #[test] fn test_widen_cast() { - let three = u2:0b11; - assert_eq(three as u4, u4:3) + let three = u2:0b11; + assert_eq(three as u4, u4:3) } #[test] fn test_narrow_signed_cast() { - let negative_seven = s4:0b1001; - assert_eq(negative_seven as u2, u2:1) + let negative_seven = s4:0b1001; + assert_eq(negative_seven as u2, u2:1) } #[test] fn test_widen_signed_cast() { - let negative_one = s2:0b11; - assert_eq(negative_one as s4, s4:-1) + let negative_one = s2:0b11; + assert_eq(negative_one as s4, s4:-1) } #[test] fn test_widen_to_unsigned() { - let negative_one = s2:0b11; - assert_eq(negative_one as u3, u3:0b111) + let negative_one = s2:0b11; + assert_eq(negative_one as u3, u3:0b111) } #[test] fn test_widen_to_signed() { - let three = u2:0b11; - assert_eq(three as u3, u3:0b011) + let three = u2:0b11; + assert_eq(three as u3, u3:0b011) } ``` @@ -965,9 +894,7 @@ same type as its operands, `T`. Let's instantiate this rule in a function: ```dslx -fn add_wrapper(x: bits[2], y: bits[2]) -> bits[2] { - x + y -} +fn add_wrapper(x: bits[2], y: bits[2]) -> bits[2] { x + y } ``` This function wraps the '+' operator. It presents two arguments to the '+' @@ -996,9 +923,7 @@ is also `bits[2]`. Qed. A **type error** would occur in the following: ```dslx-bad -fn add_wrapper(x: bits[2], y: bits[3]) -> bits[2] { - x + y -} +fn add_wrapper(x: bits[2], y: bits[3]) -> bits[2] { x + y } ``` Applying the type deduction rule for '+' finds an inconsistency. The left hand @@ -1027,8 +952,8 @@ In this example, the result of the `let` expression is the return value -- ```dslx fn main(y: u32) -> u64 { - let x: u64 = y as u64; - x + x + let x: u64 = y as u64; + x + x } ``` @@ -1106,14 +1031,10 @@ same module: import xls.dslx.tests.mod_imported; import xls.dslx.tests.mod_imported as mi; -fn main(x: u3) -> u1 { - mod_imported::my_lsb(x) || mi::my_lsb(x) -} +fn main(x: u3) -> u1 { mod_imported::my_lsb(x) || mi::my_lsb(x) } #[test] -fn test_main() { - assert_eq(u1:0b1, main(u3:0b001)) -} +fn test_main() { assert_eq(u1:0b1, main(u3:0b001)) } ``` ### Public module members @@ -1123,7 +1044,7 @@ module. To make a member public/visible to importing modules, the `pub` keyword must be added as a prefix; e.g. ```dslx -const FOO = u32:42; // Not accessible to importing modules. +const FOO = u32:42; // Not accessible to importing modules. pub const BAR = u32:64; // Accessible to importing modules. ``` @@ -1135,13 +1056,12 @@ import xls.dslx.tests.mod_imported; import xls.dslx.tests.mod_imported as mi; fn main(x: u3) -> u1 { - mod_imported::my_lsb(x) || mi::my_lsb(x) + // Use function via both module names. + mod_imported::my_lsb(x) || mi::my_lsb(x) } #[test] -fn test_main() { - assert_eq(u1:0b1, main(u3:0b001)) -} +fn test_main() { assert_eq(u1:0b1, main(u3:0b001)) } ``` ### Module attributes @@ -1171,32 +1091,32 @@ constants are usable anywhere a literal value can be used: const FOO = u8:42; fn match_const(x: u8) -> u8 { - match x { - FOO => u8:0, - _ => u8:42, - } + match x { + FOO => u8:0, + _ => u8:42, + } } #[test] fn test_match_const_not_binding() { - assert_eq(u8:42, match_const(u8:0)); - assert_eq(u8:42, match_const(u8:1)); - assert_eq(u8:0, match_const(u8:42)); + assert_eq(u8:42, match_const(u8:0)); + assert_eq(u8:42, match_const(u8:1)); + assert_eq(u8:0, match_const(u8:42)); } fn h(t: (u8, (u16, u32))) -> u32 { - match t { - (FOO, (x, y)) => (x as u32) + y, - (_, (y, u32:42)) => y as u32, - _ => u32:7, - } + match t { + (FOO, (x, y)) => (x as u32) + y, + (_, (y, u32:42)) => y as u32, + _ => u32:7, + } } #[test] fn test_match_nested() { - assert_eq(u32:3, h((u8:42, (u16:1, u32:2)))); - assert_eq(u32:1, h((u8:0, (u16:1, u32:42)))); - assert_eq(u32:7, h((u8:0, (u16:1, u32:0)))); + assert_eq(u32:3, h((u8:42, (u16:1, u32:2)))); + assert_eq(u32:1, h((u8:0, (u16:1, u32:42)))); + assert_eq(u32:7, h((u8:0, (u16:1, u32:0)))); } ``` @@ -1214,8 +1134,8 @@ DSLX supports initializing using binary, hex or decimal syntax: ```dslx #[test] fn test_literal_initialization() { - assert_eq(u8:12, u8:0b00001100); - assert_eq(u8:12, u8:0x0c); + assert_eq(u8:12, u8:0b00001100); + assert_eq(u8:12, u8:0x0c); } ``` @@ -1232,8 +1152,8 @@ integer cannot represent it. The following code offers a clue. ```dslx #[test] fn test_signed_literal_initialization() { - assert_eq(s8:128, s8:-128); - assert_eq(s8:128, s8:0b10000000); + assert_eq(s8:128, s8:-128); + assert_eq(s8:128, s8:0b10000000); } ``` @@ -1310,9 +1230,7 @@ operations is: `(xN[M], uN[N]) -> xN[M]`. If the right hand side is a literal value it does not need to be type annotated. For example: ```dslx -fn shr_two(x: s32) -> s32 { - x >> 2 -} +fn shr_two(x: s32) -> s32 { x >> 2 } ``` Note that, as in Rust, the semantics of the shift-right (`>>`) operation depends @@ -1346,11 +1264,11 @@ Concatenation operations may be chained together as shown: ```dslx #[test] fn test_bits_concat() { - assert_eq(u8:0b11000000, u2:0b11 ++ u6:0b000000); - assert_eq(u8:0b00000111, u2:0b00 ++ u6:0b000111); - assert_eq(u6:0b100111, u1:1 ++ u2:0b00 ++ u3:0b111); - assert_eq(u6:0b001000, u1:0 ++ u2:0b01 ++ u3:0b000); - assert_eq(u32:0xdeadbeef, u16:0xdead ++ u16:0xbeef); + assert_eq(u8:0b11000000, u2:0b11 ++ u6:0b000000); + assert_eq(u8:0b00000111, u2:0b00 ++ u6:0b000111); + assert_eq(u6:0b100111, u1:1 ++ u2:0b00 ++ u3:0b111); + assert_eq(u6:0b001000, u1:0 ++ u2:0b01 ++ u3:0b000); + assert_eq(u32:0xdeadbeef, u16:0xdead ++ u16:0xbeef); } ``` @@ -1358,10 +1276,10 @@ fn test_bits_concat() { Block expressions enable subordinate scopes to be defined, e.g.,: -``` +```dslx-snippet let a = { - let b = u32:1; - b + u32:3 + let b = u32:1; + b + u32:3 }; ``` @@ -1370,7 +1288,7 @@ Above, `a` is equal to `4`. The value of a block expression is that of its last contained expression, or `()` if a final expression is omitted: -``` +```dslx-snippet let a = { let b = u32:1; }; ``` @@ -1392,10 +1310,10 @@ values to identifiers for subsequent use. For example: ```dslx fn f(t: (u8, u32)) -> u32 { - match t { - (u8:42, y) => y, - (_, y) => y+u32:77 - } + match t { + (u8:42, y) => y, + (_, y) => y + u32:77, + } } ``` @@ -1408,11 +1326,12 @@ constants. For example, consider this variation on the above: ```dslx const MY_FAVORITE_NUMBER = u8:42; + fn f(t: (u8, u32)) -> u32 { - match t { - (MY_FAVORITE_NUMBER, y) => y, - (_, y) => y+u32:77 - } + match t { + (MY_FAVORITE_NUMBER, y) => y, + (_, y) => y + u32:77, + } } ``` @@ -1420,12 +1339,13 @@ This also works with nested tuples; for example: ```dslx const MY_FAVORITE_NUMBER = u8:42; + fn f(t: (u8, (u16, u32))) -> u32 { - match t { - (MY_FAVORITE_NUMBER, (y, z)) => y as u32 + z, - (_, (y, u32:42)) => y as u32, - _ => u32:7 - } + match t { + (MY_FAVORITE_NUMBER, (y, z)) => y as u32 + z, + (_, (y, u32:42)) => y as u32, + _ => u32:7, + } } ``` @@ -1443,18 +1363,18 @@ We can also `match` on ranges of values using the "range" syntax: ```dslx fn f(x: u32) -> u32 { - match x { - u32:1..u32:3 => u32:0, - _ => x - } + match x { + u32:1..u32:3 => u32:0, + _ => x, + } } #[test] fn test_f() { - assert_eq(f(u32:1), u32:0); - assert_eq(f(u32:2), u32:0); - // Note: the limit of the range syntax is exclusive. - assert_eq(f(u32:3), u32:3); + assert_eq(f(u32:1), u32:0); + assert_eq(f(u32:2), u32:0); + // Note: the limit of the range syntax is exclusive. + assert_eq(f(u32:3), u32:3); } ``` @@ -1462,17 +1382,17 @@ Or on multiple patterns per arm using the `|` operator: ```dslx fn f(x: u32) -> u32 { - match x { - u32:1 | u32:3 => u32:0, - _ => x - } + match x { + u32:1 | u32:3 => u32:0, + _ => x, + } } #[test] fn test_f() { - assert_eq(f(u32:1), u32:0); - assert_eq(f(u32:2), u32:2); - assert_eq(f(u32:3), u32:0); + assert_eq(f(u32:1), u32:0); + assert_eq(f(u32:2), u32:2); + assert_eq(f(u32:3), u32:0); } ``` @@ -1484,11 +1404,11 @@ twice; e.g., ```dslx-bad const FOO = u32:42; fn f(x: u32) -> u2 { - match x { - FOO => u2:0, - FOO => u2:1, // Identical pattern! - _ => u2:2, - } + match x { + FOO => u2:0, + FOO => u2:1, // Identical pattern! + _ => u2:2, + } } ``` @@ -1499,12 +1419,13 @@ not *syntactically identical* patterns will not be flagged in this way. ```dslx const FOO = u32:42; const BAR = u32:42; // Compares `==` to `FOO`. + fn f(x: u32) -> u2 { - match x { - FOO => u2:0, - BAR => u2:1, // _Equivalent_ pattern, but not syntactically identical. - _ => u2:2, - } + match x { + FOO => u2:0, + BAR => u2:1, // _Equivalent_ pattern, but not syntactically identical. + _ => u2:2, + } } ``` @@ -1712,24 +1633,24 @@ for semantics of numeric casts: ```dslx #[test] fn test_numerical_conversions() { - let s8_m2 = s8:-2; - let u8_m2 = u8:0xfe; - - // Sign extension (source type is signed, and we widen it). - assert_eq(s32:-2, s8_m2 as s32); - assert_eq(u32:0xfffffffe, s8_m2 as u32); - assert_eq(s16:-2, s8_m2 as s16); - assert_eq(u16:0xfffe, s8_m2 as u16); - - // Zero extension (source type is unsigned, and we widen it). - assert_eq(u32:0xfe, u8_m2 as u32); - assert_eq(s32:0xfe, u8_m2 as s32); - - // Nop (bitwidth is unchanged). - assert_eq(s8:-2, s8_m2 as s8); - assert_eq(u8:0xfe, s8_m2 as u8); - assert_eq(u8:0xfe, u8_m2 as u8); - assert_eq(s8:-2, u8_m2 as s8); + let s8_m2 = s8:-2; + let u8_m2 = u8:0xfe; + + // Sign extension (source type is signed, and we widen it). + assert_eq(s32:-2, s8_m2 as s32); + assert_eq(u32:0xfffffffe, s8_m2 as u32); + assert_eq(s16:-2, s8_m2 as s16); + assert_eq(u16:0xfffe, s8_m2 as u16); + + // Zero extension (source type is unsigned, and we widen it). + assert_eq(u32:0xfe, u8_m2 as u32); + assert_eq(s32:0xfe, u8_m2 as s32); + + // Nop (bitwidth is unchanged). + assert_eq(s8:-2, s8_m2 as s8); + assert_eq(u8:0xfe, s8_m2 as u8); + assert_eq(u8:0xfe, u8_m2 as u8); + assert_eq(s8:-2, u8_m2 as s8); } ``` @@ -1745,46 +1666,40 @@ the MSbs of the resulting value. All casts between arrays and bits must have the same total bit count. ```dslx -fn cast_to_array(x: u6) -> u2[3] { - x as u2[3] -} +fn cast_to_array(x: u6) -> u2[3] { x as u2[3] } -fn cast_from_array(a: u2[3]) -> u6 { - a as u6 -} +fn cast_from_array(a: u2[3]) -> u6 { a as u6 } -fn concat_arrays(a: u2[3], b: u2[3]) -> u2[6] { - a ++ b -} +fn concat_arrays(a: u2[3], b: u2[3]) -> u2[6] { a ++ b } #[test] fn test_cast_to_array() { - let a_value: u6 = u6:0b011011; - let a: u2[3] = cast_to_array(a_value); - let a_array = u2[3]:[1, 2, 3]; - assert_eq(a, a_array); - // Note: converting back from array to bits gives the original value. - assert_eq(a_value, cast_from_array(a)); - - let b_value: u6 = u6:0b111001; - let b_array: u2[3] = u2[3]:[3, 2, 1]; - let b: u2[3] = cast_to_array(b_value); - assert_eq(b, b_array); - assert_eq(b_value, cast_from_array(b)); - - // Concatenation of bits is analogous to concatenation of their converted - // arrays. That is: - // - // convert(concat(a, b)) == concat(convert(a), convert(b)) - let concat_value: u12 = a_value ++ b_value; - let concat_array: u2[6] = concat_value as u2[6]; - assert_eq(concat_array, concat_arrays(a_array, b_array)); - - // Show a few classic "endianness" example using 8-bit array values. - let x = u32:0xdeadbeef; - assert_eq(x as u8[4], u8[4]:[0xde, 0xad, 0xbe, 0xef]); - let y = u16:0xbeef; - assert_eq(y as u8[2], u8[2]:[0xbe, 0xef]); + let a_value: u6 = u6:0b011011; + let a: u2[3] = cast_to_array(a_value); + let a_array = u2[3]:[1, 2, 3]; + assert_eq(a, a_array); + // Note: converting back from array to bits gives the original value. + assert_eq(a_value, cast_from_array(a)); + + let b_value: u6 = u6:0b111001; + let b_array: u2[3] = u2[3]:[3, 2, 1]; + let b: u2[3] = cast_to_array(b_value); + assert_eq(b, b_array); + assert_eq(b_value, cast_from_array(b)); + + // Concatenation of bits is analogous to concatenation of their converted + // arrays. That is: + // + // convert(concat(a, b)) == concat(convert(a), convert(b)) + let concat_value: u12 = a_value ++ b_value; + let concat_array: u2[6] = concat_value as u2[6]; + assert_eq(concat_array, concat_arrays(a_array, b_array)); + + // Show a few classic "endianness" example using 8-bit array values. + let x = u32:0xdeadbeef; + assert_eq(x as u8[4], u8[4]:[0xde, 0xad, 0xbe, 0xef]); + let y = u16:0xbeef; + assert_eq(y as u8[2], u8[2]:[0xbe, 0xef]); } ``` @@ -1849,10 +1764,10 @@ bits) and `[-2:]` (the two most significant bits): ```dslx #[test] fn slice_into_two_pieces() { - let x = u5:0b11000; - let (lo, hi): (u3, u2) = (x[:-2], x[-2:]); - assert_eq(hi, u2:0b11); - assert_eq(lo, u3:0b000); + let x = u5:0b11000; + let (lo, hi): (u3, u2) = (x[:-2], x[-2:]); + assert_eq(hi, u2:0b11); + assert_eq(lo, u3:0b000); } ``` @@ -1875,59 +1790,59 @@ fn id(x: bits[N]) -> bits[N] { x } #[test] fn test_bit_slice_syntax() { - let x = u6:0b100111; - // Slice out two bits. - assert_eq(u2:0b11, x[0:2]); - assert_eq(u2:0b11, x[1:3]); - assert_eq(u2:0b01, x[2:4]); - assert_eq(u2:0b00, x[3:5]); - - // Slice out three bits. - assert_eq(u3:0b111, x[0:3]); - assert_eq(u3:0b011, x[1:4]); - assert_eq(u3:0b001, x[2:5]); - assert_eq(u3:0b100, x[3:6]); - - // Slice out from the end. - assert_eq(u1:0b1, x[-1:]); - assert_eq(u1:0b1, x[-1:6]); - assert_eq(u2:0b10, x[-2:]); - assert_eq(u2:0b10, x[-2:6]); - assert_eq(u3:0b100, x[-3:]); - assert_eq(u3:0b100, x[-3:6]); - assert_eq(u4:0b1001, x[-4:]); - assert_eq(u4:0b1001, x[-4:6]); - - // Slice both relative to the end (MSb). - assert_eq(u2:0b01, x[-4:-2]); - assert_eq(u2:0b11, x[-6:-4]); - - // Slice out from the beginning (LSb). - assert_eq(u5:0b00111, x[:-1]); - assert_eq(u4:0b0111, x[:-2]); - assert_eq(u3:0b111, x[:-3]); - assert_eq(u2:0b11, x[:-4]); - assert_eq(u1:0b1, x[:-5]); - - // Slicing past the end just means we hit the end (as in Python). - assert_eq(u1:0b1, x[5:7]); - assert_eq(u1:0b1, x[-7:1]); - assert_eq(bits[0]:0, x[-7:-6]); - assert_eq(bits[0]:0, x[-6:-6]); - assert_eq(bits[0]:0, x[6:6]); - assert_eq(bits[0]:0, x[6:7]); - assert_eq(u1:1, x[-6:-5]); - - // Slice of a slice. - assert_eq(u2:0b11, x[:4][1:3]); - - // Slice of an invocation. - assert_eq(u2:0b01, id(x)[2:4]); - - // Explicit-width slices. - assert_eq(u2:0b01, x[2+:u2]); - assert_eq(s3:0b100, x[3+:s3]); - assert_eq(u3:0b001, x[5+:u3]); + let x = u6:0b100111; + // Slice out two bits. + assert_eq(u2:0b11, x[0:2]); + assert_eq(u2:0b11, x[1:3]); + assert_eq(u2:0b01, x[2:4]); + assert_eq(u2:0b00, x[3:5]); + + // Slice out three bits. + assert_eq(u3:0b111, x[0:3]); + assert_eq(u3:0b011, x[1:4]); + assert_eq(u3:0b001, x[2:5]); + assert_eq(u3:0b100, x[3:6]); + + // Slice out from the end. + assert_eq(u1:0b1, x[-1:]); + assert_eq(u1:0b1, x[-1:6]); + assert_eq(u2:0b10, x[-2:]); + assert_eq(u2:0b10, x[-2:6]); + assert_eq(u3:0b100, x[-3:]); + assert_eq(u3:0b100, x[-3:6]); + assert_eq(u4:0b1001, x[-4:]); + assert_eq(u4:0b1001, x[-4:6]); + + // Slice both relative to the end (MSb). + assert_eq(u2:0b01, x[-4:-2]); + assert_eq(u2:0b11, x[-6:-4]); + + // Slice out from the beginning (LSb). + assert_eq(u5:0b00111, x[:-1]); + assert_eq(u4:0b0111, x[:-2]); + assert_eq(u3:0b111, x[:-3]); + assert_eq(u2:0b11, x[:-4]); + assert_eq(u1:0b1, x[:-5]); + + // Slicing past the end just means we hit the end (as in Python). + assert_eq(u1:0b1, x[5:7]); + assert_eq(u1:0b1, x[-7:1]); + assert_eq(bits[0]:0, x[-7:-6]); + assert_eq(bits[0]:0, x[-6:-6]); + assert_eq(bits[0]:0, x[6:6]); + assert_eq(bits[0]:0, x[6:7]); + assert_eq(u1:1, x[-6:-5]); + + // Slice of a slice. + assert_eq(u2:0b11, x[:4][1:3]); + + // Slice of an invocation. + assert_eq(u2:0b01, id(x)[2:4]); + + // Explicit-width slices. + assert_eq(u2:0b01, x[2+:u2]); + assert_eq(s3:0b100, x[3+:s3]); + assert_eq(u3:0b001, x[5+:u3]); } ``` @@ -1998,9 +1913,9 @@ As in Rust, unit tests are specified by the `test` directive, as seen below: ```dslx #[test] fn test_reverse() { - assert_eq(u1:1, rev(u1:1)); - assert_eq(u2:0b10, rev(u2:0b01)); - assert_eq(u2:0b00, rev(u2:0b00)); + assert_eq(u1:1, rev(u1:1)); + assert_eq(u2:0b10, rev(u2:0b01)); + assert_eq(u2:0b00, rev(u2:0b00)); } ``` @@ -2049,11 +1964,10 @@ framework. Here is an example that complements the unit testing of DSLX's `rev` implementation from above: ```dslx -// Reversing a value twice gets you the original value. - #[quickcheck] fn prop_double_bitreverse(x: u32) -> bool { - x == rev(rev(x)) + // Check that when we reverse the bits twice we get the original value. + x == rev(rev(x)) } ``` @@ -2067,7 +1981,7 @@ By default, the framework will run the function against 1000 sets of randomized inputs. This default may be changed by specifying the `test_count` key in the `quickcheck` directive before a particular test: -``` +```dslx-snippet #[quickcheck(test_count=50000)] ``` @@ -2086,7 +2000,8 @@ For small domains the quickcheck directive can also be placed in exhaustive mode // `u8` space is small enough to check exhaustively. #[quickcheck(exhaustive)] fn prop_double_bitreverse(x: u8) -> bool { - x == rev(rev(x)) + // Check that when we reverse the bits twice we get the original value. + x == rev(rev(x)) } ``` @@ -2118,15 +2033,15 @@ This is a simple `proc`: proc CountUp { output_channel: chan out; - // Initial value for the state. - init { u32:0 } - // Configuration -- we get the output channel when we're configured by an external `spawn`. // The last statement in a `config` should be a tuple that is used to initialize the proc // members; e.g. we give a single value in a tuple that initializes the `output_channel` // declared above. config(output_channel: chan out) { (output_channel,) } + // Initial value for the state. + init { u32:0 } + // "Iterate in time" -- takes a state, does some work, and produces a new state. next(state: u32) { // Send our state to the outside world via the channel. diff --git a/docs_src/dslx_std.md b/docs_src/dslx_std.md index be0fcb007f..4547b3e7c8 100644 --- a/docs_src/dslx_std.md +++ b/docs_src/dslx_std.md @@ -96,14 +96,14 @@ taking the absolute value of each element in an input array: import std; fn main(x: s3[3]) -> s3[3] { - let y: s3[3] = map(x, std::abs); - y + let y: s3[3] = map(x, std::abs); + y } #[test] fn main_test() { - let got: s3[3] = main(s3[3]:[-1, 1, 0]); - assert_eq(s3[3]:[1, 1, 0], got) + let got: s3[3] = main(s3[3]:[-1, 1, 0]); + assert_eq(s3[3]:[1, 1, 0], got) } ``` @@ -146,11 +146,11 @@ fn test_zip_array_size_2() { ```dslx #[test] fn test_array_rev() { - assert_eq(array_rev(u8[1]:[42]), u8[1]:[42]); - assert_eq(array_rev(u3[2]:[1, 2]), u3[2]:[2, 1]); - assert_eq(array_rev(u3[3]:[2, 3, 4]), u3[3]:[4, 3, 2]); - assert_eq(array_rev(u4[3]:[0xf, 0, 0]), u4[3]:[0, 0, 0xf]); - assert_eq(array_rev(u3[0]:[]), u3[0]:[]); + assert_eq(array_rev(u8[1]:[42]), u8[1]:[42]); + assert_eq(array_rev(u3[2]:[1, 2]), u3[2]:[2, 1]); + assert_eq(array_rev(u3[3]:[2, 3, 4]), u3[3]:[4, 3, 2]); + assert_eq(array_rev(u4[3]:[0xf, 0, 0]), u4[3]:[0, 0, 0xf]); + assert_eq(array_rev(u3[0]:[]), u3[0]:[]); } ``` @@ -164,8 +164,8 @@ Note that this can only be applied to compile-time-constant expressions. ```dslx #[test] fn test_const_assert() { - const N = u32:42; - const_assert!(N >= u32:1<<5); + const N = u32:42; + const_assert!(N >= u32:1 << 5); } ``` @@ -175,9 +175,9 @@ cannot suppress them by putting them in a conditional: ```dslx-snippet fn f() { - if false { - const_assert!(false); // <-- still fails even inside the "if false" - } + if false { + const_assert!(false); // <-- still fails even inside the "if false" + } } ``` @@ -186,12 +186,15 @@ fn f() { DSLX provides the common "count leading zeroes" and "count trailing zeroes" functions: -```dslx-snippet - let x0 = u32:0x0FFFFFF8; - let x1 = clz(x0); - let x2 = ctz(x0); - assert_eq(u32:4, x1); - assert_eq(u32:3, x2) +```dslx +#[test] +fn test_clz_ctz() { + let x0 = u32:0x0FFFFFF8; + let x1 = clz(x0); + let x2 = ctz(x0); + assert_eq(u32:4, x1); + assert_eq(u32:3, x2) +} ``` ### `decode` @@ -326,10 +329,10 @@ only its type is used to determine the result type of the sign extension. ```dslx #[test] fn test_signex() { - let x = u8:0xff; - let s: s32 = signex(x, s32:0); - let u: u32 = signex(x, u32:0); - assert_eq(s as u32, u) + let x = u8:0xff; + let s: s32 = signex(x, s32:0); + let u: u32 = signex(x, u32:0); + assert_eq(s as u32, u) } ``` @@ -354,24 +357,20 @@ result, and so on. ```dslx // (Dummy) wrapper around reverse. -fn wrapper(x: bits[N]) -> bits[N] { - rev(x) -} +fn wrapper(x: bits[N]) -> bits[N] { rev(x) } // Target for IR conversion that works on u3s. -fn main(x: u3) -> u3 { - wrapper(x) -} +fn main(x: u3) -> u3 { wrapper(x) } // Reverse examples. #[test] fn test_reverse() { - assert_eq(u3:0b100, main(u3:0b001)); - assert_eq(u3:0b001, main(u3:0b100)); - assert_eq(bits[0]:0, rev(bits[0]:0)); - assert_eq(u1:1, rev(u1:1)); - assert_eq(u2:0b10, rev(u2:0b01)); - assert_eq(u2:0b00, rev(u2:0b00)); + assert_eq(u3:0b100, main(u3:0b001)); + assert_eq(u3:0b001, main(u3:0b100)); + assert_eq(bits[0]:0, rev(bits[0]:0)); + assert_eq(u1:1, rev(u1:1)); + assert_eq(u2:0b10, rev(u2:0b01)); + assert_eq(u2:0b00, rev(u2:0b00)); } ``` @@ -404,9 +403,9 @@ trivial (0 bit wide) inputs: ```dslx #[test] fn test_trivial_reduce() { - assert_eq(and_reduce(bits[0]:0), true); - assert_eq(or_reduce(bits[0]:0), false); - assert_eq(xor_reduce(bits[0]:0), false); + assert_eq(and_reduce(bits[0]:0), true); + assert_eq(or_reduce(bits[0]:0), false); + assert_eq(xor_reduce(bits[0]:0), false); } ``` @@ -424,9 +423,9 @@ always made. ```dslx #[test] fn test_update() { - assert_eq(update([u8:1, u8:2, u8:3], u2:2, u8:42), [u8:1, u8:2, u8:42]); - assert_eq(update([[u8:1, u8:2], [u8:3, u8:4]], - (u1:1, u1:0), u8:42), [[u8:1, u8:2], [u8:42, u8:4]]); + assert_eq(update([u8:1, u8:2, u8:3], u2:2, u8:42), [u8:1, u8:2, u8:42]); + assert_eq( + update([[u8:1, u8:2], [u8:3, u8:4]], (u1:1, u1:0), u8:42), [[u8:1, u8:2], [u8:42, u8:4]]); } ``` @@ -438,16 +437,14 @@ future). Here is an example of a `divceil` implementation with its corresponding tests: ```dslx -fn divceil(x: u32, y: u32) -> u32 { - (x-u32:1) / y + u32:1 -} +fn divceil(x: u32, y: u32) -> u32 { (x - u32:1) / y + u32:1 } #[test] fn test_divceil() { - assert_eq(u32:3, divceil(u32:5, u32:2)); - assert_eq(u32:2, divceil(u32:4, u32:2)); - assert_eq(u32:2, divceil(u32:3, u32:2)); - assert_eq(u32:1, divceil(u32:2, u32:2)); + assert_eq(u32:3, divceil(u32:5, u32:2)); + assert_eq(u32:2, divceil(u32:4, u32:2)); + assert_eq(u32:2, divceil(u32:3, u32:2)); + assert_eq(u32:1, divceil(u32:2, u32:2)); } ``` @@ -460,21 +457,18 @@ DSLX has a macro for easy creation of zero values, even from aggregate types. Invoke the macro with the type parameter as follows: ```dslx -struct MyPoint { - x: u32, - y: u32, -} +struct MyPoint { x: u32, y: u32 } enum MyEnum : u2 { - ZERO = u2:0, - ONE = u2:1, + ZERO = u2:0, + ONE = u2:1, } #[test] fn test_zero_macro() { - assert_eq(zero!(), u32:0); - assert_eq(zero!(), MyPoint{x: u32:0, y: u32:0}); - assert_eq(zero!(), MyEnum::ZERO); + assert_eq(zero!(), u32:0); + assert_eq(zero!(), MyPoint { x: u32:0, y: u32:0 }); + assert_eq(zero!(), MyEnum::ZERO); } ``` @@ -483,16 +477,9 @@ initialize a subset of fields to zero. In the example below all fields except `foo` are initialized to zero in the struct returned by `f`. ```dslx -struct MyStruct { - foo: u1, - bar: u2, - baz: u3, - bat: u4, -} +struct MyStruct { foo: u1, bar: u2, baz: u3, bat: u4 } -fn f() -> MyStruct { - MyStruct{foo: u1:1, ..zero!()} -} +fn f() -> MyStruct { MyStruct { foo: u1:1, ..zero!() } } ``` ### `all_ones!` @@ -501,22 +488,19 @@ Similar to `zero!`, DSLX has a macro for easy creation of all-ones values, even from aggregate types. Invoke the macro with the type parameter as follows: ```dslx -struct MyPoint { - x: u32, - y: u32, -} +struct MyPoint { x: u32, y: u32 } enum MyEnum : u2 { - ZERO = u2:0, - ONE = u2:1, - THREE = u2:3, + ZERO = u2:0, + ONE = u2:1, + THREE = u2:3, } #[test] fn test_all_ones_macro() { - assert_eq(all_ones!(), u32:0xFFFFFFFF); - assert_eq(all_ones!(), MyPoint{x: u32:0xFFFFFFFF, y: u32:0xFFFFFFFF}); - assert_eq(all_ones!(), MyEnum::THREE); + assert_eq(all_ones!(), u32:0xFFFFFFFF); + assert_eq(all_ones!(), MyPoint { x: u32:0xFFFFFFFF, y: u32:0xFFFFFFFF }); + assert_eq(all_ones!(), MyEnum::THREE); } ``` @@ -525,40 +509,38 @@ initialize a subset of fields to zero. In the example below all fields except `foo` are initialized to zero in the struct returned by `f`. ```dslx -struct MyStruct { - foo: u1, - bar: u2, - baz: u3, - bat: u4, -} +struct MyStruct { foo: u1, bar: u2, baz: u3, bat: u4 } -fn f() -> MyStruct { - MyStruct{foo: u1:1, ..all_ones!()} -} +fn f() -> MyStruct { MyStruct { foo: u1:1, ..all_ones!() } } ``` ### `trace_fmt!` DSLX supports printf-style debugging via the `trace_fmt!` builtin, which allows -dumping values to stdout. For example: +dumping values to stdout. -```dslx -// Note: to see `trace_fmt!` output you need to be seeing `INFO` level logging, -// enabled by adding the '--alsologtostderr' flag to the command line (among -// other means). For example: -// bazel run -c opt //xls/dslx:interpreter_main /path/to/dslx/file.x -- --alsologtostderr +Note: to see `trace_fmt!` output you need to be seeing `INFO` level logging, +which can be enabled by adding the `--alsologtostderr` flag to the command line. +For example, running: +``` +bazel run -c opt //xls/dslx:interpreter_main /path/to/dslx/file.x -- --alsologtostderr +``` + +with this sample code: + +```dslx fn shifty(x: u8, y: u3) -> u8 { - trace_fmt!("x: {:x} y: {}", x, y); - // Note: y looks different as a negative number when the high bit is set. - trace_fmt!("y as s8: {}", y as s2); - x << y + trace_fmt!("x: {:x} y: {}", x, y); + // Note: y looks different as a negative number when the high bit is set. + trace_fmt!("y as s8: {}", y as s2); + x << y } #[test] fn test_shifty() { - assert_eq(shifty(u8:0x42, u3:4), u8:0x20); - assert_eq(shifty(u8:0x42, u3:7), u8:0); + assert_eq(shifty(u8:0x42, u3:4), u8:0x20); + assert_eq(shifty(u8:0x42, u3:7), u8:0); } ``` @@ -638,8 +620,8 @@ the value `42` as a precondition: ```dslx fn main(x: u32) -> u32 { - assert!(x != u32:42, "x_never_forty_two"); - x - u32:42 + assert!(x != u32:42, "x_never_forty_two"); + x - u32:42 } ``` @@ -648,20 +630,20 @@ fn main(x: u32) -> u32 { for `main`): ```dslx -enum EnumType: u2 { - FIRST = 0, - SECOND = 1, +enum EnumType : u2 { + FIRST = 0, + SECOND = 1, } fn main(x: EnumType) -> u32 { - match x { - EnumType::FIRST => u32:0, - EnumType::SECOND => u32:1, - // This should not be reachable. - // But, if we synthesize hardware, under this condition the function is - // well-defined to give back zero. - _ => fail!("unknown_EnumType", u32:0), - } + match x { + EnumType::FIRST => u32:0, + EnumType::SECOND => u32:1, + // This should not be reachable. + // But, if we synthesize hardware, under this condition the function is + // well-defined to give back zero. + _ => fail!("unknown_EnumType", u32:0), + } } ``` @@ -891,8 +873,8 @@ import std; #[test] fn convert_to_bits_test() { - assert_eq(u3:0b001, std::convert_to_bits_msb0(bool[3]:[false, false, true])); - assert_eq(u3:0b100, std::convert_to_bits_msb0(bool[3]:[true, false, false])); + assert_eq(u3:0b001, std::convert_to_bits_msb0(bool[3]:[false, false, true])); + assert_eq(u3:0b100, std::convert_to_bits_msb0(bool[3]:[true, false, false])); } ``` diff --git a/docs_src/dslx_type_system.md b/docs_src/dslx_type_system.md index f58498d5c1..77612044f7 100644 --- a/docs_src/dslx_type_system.md +++ b/docs_src/dslx_type_system.md @@ -38,9 +38,7 @@ fn main() -> u8 { } #[test] -fn test_main() { - assert_eq(main(), u8:42) -} +fn test_main() { assert_eq(main(), u8:42) } ``` This allows us to write more generic code as library-style functions, which @@ -85,6 +83,7 @@ fn p() -> bits[N] { const_assert!(N == u32:42); u42:64 } + fn main() -> u42 { p() // this is fine } @@ -109,13 +108,9 @@ The three components are performed in that order. In this example: ```dslx -fn p() -> (bits[A], bits[B]) { - (bits[A]:42, bits[B]:64) -} +fn p() -> (bits[A], bits[B]) { (bits[A]:42, bits[B]:64) } -fn main() -> (u8, u16) { - p() -} +fn main() -> (u8, u16) { p() } ``` The caller `main` explicitly binds the parametrics `A` and `B` by supplying @@ -126,17 +121,12 @@ arguments in the angle brackets. In this example: ```dslx -fn p(x: bits[A]) -> bits[A] { - x + bits[A]:1 -} -fn main() -> u13 { - p(u13:42) -} +fn p(x: bits[A]) -> bits[A] { x + bits[A]:1 } + +fn main() -> u13 { p(u13:42) } #[test] -fn test_main() { - assert_eq(main(), u13:43) -} +fn test_main() { assert_eq(main(), u13:43) } ``` `main` is implicitly saying what `A` must be by passing a `u13` -- we know that @@ -158,18 +148,12 @@ fn main() -> u13 { In this example: ```dslx -fn p(x: bits[A]) -> bits[B] { - x as bits[B] -} +fn p(x: bits[A]) -> bits[B] { x as bits[B] } -fn main() -> u32 { - p(u16:42) -} +fn main() -> u32 { p(u16:42) } #[test] -fn test_main() { - assert_eq(main(), u32:42); -} +fn test_main() { assert_eq(main(), u32:42); } ``` `main` is implicitly saying what `A` must be by passing a `u16`; however, `B` is @@ -188,16 +172,12 @@ One consequence of the ordering defined is that earlier parametric bindings can be used to define the types of later parametric bindings; e.g. ```dslx -fn p() -> bits[A] { - B -} +fn p() -> bits[A] { B } fn main() -> u8 { p() } #[test] -fn test_main() { - assert_eq(main(), u8:0) -} +fn test_main() { assert_eq(main(), u8:0) } ``` Note that `main` uses an explicit parametric to define `A` as `8`. Then the diff --git a/docs_src/tutorials/float_to_int.md b/docs_src/tutorials/float_to_int.md index 5c17cb3269..1b6b3ff6b0 100644 --- a/docs_src/tutorials/float_to_int.md +++ b/docs_src/tutorials/float_to_int.md @@ -19,7 +19,7 @@ the entry function. If you recall, a IEEE binary32 (the C `float` type) has 1 sign bit, 8 [biased] exponent bits, and 23 fractional bits. These values can be packed into a tuple, and so, the signature of our function can be defined as: -```dslx +```dslx-snippet pub fn float_to_int( x: (u1, u8, u23)) -> s32 { @@ -66,15 +66,9 @@ Anyway...the tuple representation of our input is a bit cumbersome, so let's define our floating-point number as a struct instead: ```dslx -pub struct float32 { - sign: u1, - bexp: u8, - fraction: u23, -} +pub struct float32 { sign: u1, bexp: u8, fraction: u23 } -pub fn float_to_int(x: float32) -> s32 { - s32:0xbeef -} +pub fn float_to_int(x: float32) -> s32 { s32:0xbeef } ``` Finally, let's write a quick test to make sure things work. Add the following @@ -83,13 +77,13 @@ code to your file. ```dslx-snippet #[test] fn float_to_int_test() { - // 0xbeef in float32. - let test_input = float32 { - sign: u1:0x0, - bexp: u8:0x8e, - fraction: u23:0x3eef00 - }; - assert_eq(s32:0xbeef, float_to_int(test_input)) + // 0xbeef in float32. + let test_input = float32 { + sign: u1:0x0, + bexp: u8:0x8e, + fraction: u23:0x3eef00 + }; + assert_eq(s32:0xbeef, float_to_int(test_input)) } ``` @@ -126,9 +120,7 @@ we need to subtract 127 from that value to get the actual exponent. Let's write a function to do just that: ```dslx -fn unbias_exponent(exp: u8) -> s9 { - exp as s9 - s9:127 -} +fn unbias_exponent(exp: u8) -> s9 { exp as s9 - s9:127 } ``` Notice that we need to expand the exponent to add on the sign bit before the @@ -144,36 +136,26 @@ fractional part into its proper location in the final integer. Here's what that looks like when we add that to our original function: ```dslx -pub struct float32 { - sign: u1, - bexp: u8, - fraction: u23, -} +pub struct float32 { sign: u1, bexp: u8, fraction: u23 } -fn unbias_exponent(exp: u8) -> s9 { - exp as s9 - s9:127 -} +fn unbias_exponent(exp: u8) -> s9 { exp as s9 - s9:127 } pub fn float_to_int(x: float32) -> s32 { - let exp = unbias_exponent(x.bexp); + let exp = unbias_exponent(x.bexp); - // Add the implicit leading one. - // Note that we need to add one bit to the fraction to hold it. - let fraction = u33:1 << 23 | (x.fraction as u33); + // Add the implicit leading one. + // Note that we need to add one bit to the fraction to hold it. + let fraction = u33:1 << 23 | (x.fraction as u33); - // Shift the result to the right if the exponent is less than 23. - let fraction = - if (exp as u8) < u8:23 { fraction >> (u8:23 - (exp as u8)) } - else { fraction }; + // Shift the result to the right if the exponent is less than 23. + let fraction = if (exp as u8) < u8:23 { fraction >> (u8:23 - (exp as u8)) } else { fraction }; - // Shift the result to the left if the exponent is greater than 23. - let fraction = - if (exp as u8) > u8:23 { fraction << ((exp as u8) - u8:23) } - else { fraction }; + // Shift the result to the left if the exponent is greater than 23. + let fraction = if (exp as u8) > u8:23 { fraction << ((exp as u8) - u8:23) } else { fraction }; - let result = fraction as s32; - let result = if x.sign { -result } else { result }; - result + let result = fraction as s32; + let result = if x.sign { -result } else { result }; + result } ``` diff --git a/docs_src/tutorials/hello_xls.md b/docs_src/tutorials/hello_xls.md index 07ad1890f1..31790492fd 100644 --- a/docs_src/tutorials/hello_xls.md +++ b/docs_src/tutorials/hello_xls.md @@ -44,7 +44,8 @@ the following: ```dslx fn hello_xls(hello_string: u8[11]) { - trace!(hello_string); + // Trace the value to stderr as output. + trace!(hello_string); } ``` @@ -55,7 +56,8 @@ Let's go over this, line-by-line: returns no value (the return type would be specified after the argument list's closing parenthesis and before the function-opening curly brace, if the function returned a value). -2. This second line invokes the built-in `trace!` directive, passing it the +2. A comment line. +3. This second line invokes the built-in `trace!` directive, passing it the function's input string, and throws away the result. ## 3. Say hello, XLS! diff --git a/docs_src/tutorials/how_to_use_procs.md b/docs_src/tutorials/how_to_use_procs.md index 07299666df..c227a5fa55 100644 --- a/docs_src/tutorials/how_to_use_procs.md +++ b/docs_src/tutorials/how_to_use_procs.md @@ -27,28 +27,26 @@ import float32; type F32 = float32::F32; proc Fmac { - input_a_consumer: chan in; - input_b_consumer: chan in; - output_producer: chan out; - - init { - float32::zero(false) - } - - config(input_a_consumer: chan in, input_b_consumer: chan in, - output_producer: chan out) { - (input_a_consumer, input_b_consumer, output_producer) - } - - next(state: F32) { - let tok = join(); - let (tok_a, input_a) = recv(tok, input_a_consumer); - let (tok_b, input_b) = recv(tok, input_b_consumer); - let result = float32::fma(input_a, input_b, state); - let tok = join(tok_a, tok_b); - let tok = send(tok, output_producer, result); - result - } + input_a_consumer: chan in; + input_b_consumer: chan in; + output_producer: chan out; + + config(input_a_consumer: chan in, input_b_consumer: chan in, + output_producer: chan out) { + (input_a_consumer, input_b_consumer, output_producer) + } + + init { zero!() } + + next(state: F32) { + let tok = join(); + let (tok_a, input_a) = recv(tok, input_a_consumer); + let (tok_b, input_b) = recv(tok, input_b_consumer); + let result = float32::fma(input_a, input_b, state); + let tok = join(tok_a, tok_b); + let tok = send(tok, output_producer, result); + result + } } ``` diff --git a/docs_src/tutorials/intro_to_parametrics.md b/docs_src/tutorials/intro_to_parametrics.md index b458f21f07..99f5c84d05 100644 --- a/docs_src/tutorials/intro_to_parametrics.md +++ b/docs_src/tutorials/intro_to_parametrics.md @@ -16,9 +16,7 @@ Consider the simple example of a `umax` function -- similar to the `max` functio [in the DSLX standard library](https://github.com/google/xls/tree/main/xls/dslx/stdlib/std.x): ```dslx -pub fn umax(x: uN[N], y: uN[N]) -> uN[N] { - if x > y { x } else { y } -} +pub fn umax(x: uN[N], y: uN[N]) -> uN[N] { if x > y { x } else { y } } ``` Most of this function looks like other DSLX functions you may have seen, except @@ -41,13 +39,9 @@ infer them: Explicit specification: ```dslx -fn umax(x: uN[N], y: uN[N]) -> uN[N] { - if x > y { x } else { y } -} +fn umax(x: uN[N], y: uN[N]) -> uN[N] { if x > y { x } else { y } } -fn foo(a: u32, b: u16) -> u64 { - umax(a as u64, b as u64) -} +fn foo(a: u32, b: u16) -> u64 { umax(a as u64, b as u64) } ``` Here, the user has directly told the language what the values of all parametrics @@ -56,13 +50,9 @@ are. Parametric inference: ```dslx -fn umax(x: uN[N], y: uN[N]) -> uN[N] { - if x > y { x } else { y } -} +fn umax(x: uN[N], y: uN[N]) -> uN[N] { if x > y { x } else { y } } -fn foo(a: u32, b: u16) -> u64 { - umax(a as u64, b as u64) -} +fn foo(a: u32, b: u16) -> u64 { umax(a as u64, b as u64) } ``` Here, though, the language is able to determine that `N` is 64, since that @@ -72,9 +62,9 @@ exist parametrics that aren't referenced in the argument list: ```dslx fn my_parametric_sum(a: u32, b: u32) -> uN[N] { - let a_mod = a as uN[N]; - let b_mod = a as uN[N]; - a_mod + b_mod + let a_mod = a as uN[N]; + let b_mod = a as uN[N]; + a_mod + b_mod } ``` @@ -92,8 +82,8 @@ done as follows: ```dslx-snippet fn unbias_exponent( - exp: uN[EXP_SZ]) -> sN[SIGNED_EXP_SZ] { - exp as sN[SIGNED_EXP_SZ] - sN[SIGNED_EXP_SZ]:??? + exp: uN[EXP_SZ]) -> sN[SIGNED_EXP_SZ] { + exp as sN[SIGNED_EXP_SZ] - sN[SIGNED_EXP_SZ]:??? } ``` @@ -111,12 +101,12 @@ This is a bit unwieldy in practice, so we can wrap it in a function: ```dslx fn bias_scaler() -> sN[WIDE_N] { - (sN[WIDE_N]:1 << (N - u32:1)) - sN[WIDE_N]:1 + (sN[WIDE_N]:1 << (N - u32:1)) - sN[WIDE_N]:1 } -fn unbias_exponent( - exp: uN[EXP_SZ]) -> sN[SIGNED_EXP_SZ] { - exp as sN[SIGNED_EXP_SZ] - bias_scaler() +fn unbias_exponent + (exp: uN[EXP_SZ]) -> sN[SIGNED_EXP_SZ] { + exp as sN[SIGNED_EXP_SZ] - bias_scaler() } ``` @@ -133,16 +123,10 @@ such conversions - even to floating-point formats we haven't considered! The first step in such a parameterization is to have a working single-typed example, which we take from the previous codelab: -```dslx -pub struct float32 { - sign: u1, - bexp: u8, - fraction: u23, -} +```dslx-snippet +pub struct float32 { sign: u1, bexp: u8, fraction: u23 } -fn unbias_exponent(exp: u8) -> s9 { - exp as s9 - s9:127 -} +fn unbias_exponent(exp: u8) -> s9 { exp as s9 - s9:127 } pub fn float_to_int(x: float32) -> s32 { let exp = unbias_exponent(x.bexp); @@ -178,9 +162,9 @@ Thus, the struct declaration and function signature will be: ```dslx-snippet pub struct float { - sign: u1, - bexp: uN[EXP_SZ], - fraction: uN[FRACTION_SZ], + sign: u1, + bexp: uN[EXP_SZ], + fraction: uN[FRACTION_SZ], } pub fn float_to_int( @@ -194,41 +178,43 @@ the original implementation with the parameterized ones in the signature: ```dslx pub struct float { - sign: u1, - bexp: uN[EXP_SZ], - fraction: uN[FRACTION_SZ], + sign: u1, + bexp: uN[EXP_SZ], + fraction: uN[FRACTION_SZ], } fn bias_scaler() -> sN[WIDE_N] { - (sN[WIDE_N]:1 << (N - u32:1)) - sN[WIDE_N]:1 + (sN[WIDE_N]:1 << (N - u32:1)) - sN[WIDE_N]:1 } -fn unbias_exponent( - exp: uN[EXP_SZ]) -> sN[SIGNED_EXP_SZ] { - exp as sN[SIGNED_EXP_SZ] - bias_scaler() +fn unbias_exponent + (exp: uN[EXP_SZ]) -> sN[SIGNED_EXP_SZ] { + exp as sN[SIGNED_EXP_SZ] - bias_scaler() } -pub fn float_to_int< - EXP_SZ: u32, FRACTION_SZ: u32, RESULT_SZ: u32, - WIDE_EXP_SZ: u32 = {EXP_SZ + u32:1}, - WIDE_FRACTION_SZ: u32 = {FRACTION_SZ + u32:1}>( - x: float) -> sN[RESULT_SZ] { - let exp = unbias_exponent(x.bexp); +pub fn float_to_int + + (x: float) -> sN[RESULT_SZ] { + let exp = unbias_exponent(x.bexp); - let fraction = uN[WIDE_FRACTION_SZ]:1 << FRACTION_SZ | - (x.fraction as uN[WIDE_FRACTION_SZ]); + let fraction = uN[WIDE_FRACTION_SZ]:1 << FRACTION_SZ | (x.fraction as uN[WIDE_FRACTION_SZ]); - let fraction = - if (exp as u32) < FRACTION_SZ { fraction >> (FRACTION_SZ - (exp as u32)) } - else { fraction }; + let fraction = if (exp as u32) < FRACTION_SZ { + fraction >> (FRACTION_SZ - (exp as u32)) + } else { + fraction + }; - let fraction = - if (exp as u32) > FRACTION_SZ { fraction << ((exp as u32) - FRACTION_SZ) } - else { fraction }; + let fraction = if (exp as u32) > FRACTION_SZ { + fraction << ((exp as u32) - FRACTION_SZ) + } else { + fraction + }; - let result = fraction as sN[RESULT_SZ]; - let result = if x.sign { -result } else { result }; - result + let result = fraction as sN[RESULT_SZ]; + let result = if x.sign { -result } else { result }; + result } ``` diff --git a/docs_src/tutorials/what_is_a_proc.md b/docs_src/tutorials/what_is_a_proc.md index 8725652ed3..74a8b7721d 100644 --- a/docs_src/tutorials/what_is_a_proc.md +++ b/docs_src/tutorials/what_is_a_proc.md @@ -94,27 +94,25 @@ and writes the sum to a third: ```dslx pub proc adder { - A: chan in; - B: chan in; - C: chan out; - - // The initial value of the proc's state (empty in this case). - init { () } - - // The interface used by anything that spawns this proc, which will need to - // configure its inputs & outputs. - config (A: chan in, B: chan in, C: chan out) { - (A, B, C) - } - - // The description of how this proc actually acts when running. - next(st: ()) { - let (tok_A, data_A) = recv(join(), A); - let (tok_B, data_B) = recv(join(), B); - let sum = data_A + data_B; - let tok = join(tok_A, tok_B); - send(tok, C, sum); - } + A: chan in; + B: chan in; + C: chan out; + + // The interface used by anything that spawns this proc, which will need to + // configure its inputs & outputs. + config(A: chan in, B: chan in, C: chan out) { (A, B, C) } + + // The initial value of the proc's state (empty in this case). + init { () } + + // The description of how this proc actually acts when running. + next(st: ()) { + let (tok_A, data_A) = recv(join(), A); + let (tok_B, data_B) = recv(join(), B); + let sum = data_A + data_B; + let tok = join(tok_A, tok_B); + send(tok, C, sum); + } } ``` @@ -146,26 +144,20 @@ this as follows: ```dslx pub proc fallback { - A: chan in; - B: chan in; - C: chan out; + A: chan in; + B: chan in; + C: chan out; - init { () } + config(A: chan in, B: chan in, C: chan out) { (A, B, C) } - config (A: chan in, B: chan in, C: chan out) { - (A, B, C) - } + init { () } - next(st: ()) { - let (tok, x) = recv(join(), A); - let (tok, y) = recv_if(tok, B, x == u32:0, u32:0); - let val = if x != u32:0 { - x - } else { - y - }; - send(tok, C, val); - } + next(st: ()) { + let (tok, x) = recv(join(), A); + let (tok, y) = recv_if(tok, B, x == u32:0, u32:0); + let val = if x != u32:0 { x } else { y }; + send(tok, C, val); + } } ``` @@ -175,25 +167,19 @@ receive will only trigger if that branch is taken. ```dslx pub proc fallback { - A: chan in; - B: chan in; - C: chan out; + A: chan in; + B: chan in; + C: chan out; - init { () } + config(A: chan in, B: chan in, C: chan out) { (A, B, C) } - config (A: chan in, B: chan in, C: chan out) { - (A, B, C) - } + init { () } - next(st: ()) { - let (tok, x) = recv(join(), A); - let (tok, val) = if x != u32:0 { - (tok, x) - } else { - recv(tok, B) - }; - send(tok, C, val); - } + next(st: ()) { + let (tok, x) = recv(join(), A); + let (tok, val) = if x != u32:0 { (tok, x) } else { recv(tok, B) }; + send(tok, C, val); + } } ``` @@ -219,29 +205,23 @@ the result would overflow), and returns the updated accumulator value: ```dslx pub proc saturating_accumulator { - ch_in: chan in; - result: chan out; + ch_in: chan in; + result: chan out; - init { u32:0 } + config(ch_in: chan in, result: chan out) { (ch_in, result) } - config (ch_in: chan in, result: chan out) { - (ch_in, result) - } + init { u32:0 } - next(accumulated: u32) { - let (tok, data) = recv(join(), ch_in); - let sum = (data as u33) + (accumulated as u33); - let new_val = if sum > all_ones!() as u33 { - all_ones!() - } else { - sum as u32 - }; - send(tok, result, new_val); - - // The last expression is the value the next activation will receive as its - // state. - new_val - } + next(accumulated: u32) { + let (tok, data) = recv(join(), ch_in); + let sum = (data as u33) + (accumulated as u33); + let new_val = if sum > all_ones!() as u33 { all_ones!() } else { sum as u32 }; + send(tok, result, new_val); + + // The last expression is the value the next activation will receive as its + // state. + new_val + } } ``` @@ -279,28 +259,26 @@ sends the clamped difference: ```dslx pub proc clamped_diff { - ch_in: chan in; - result: chan out; - - init { s32:0 } - - config (ch_in: chan in, result: chan out) { - (ch_in, result) - } - - next(prev: s32) { - let (tok, val) = recv(join(), ch_in); - let diff = (val as s33) - (prev as s33); - let clamped_diff = if diff > s33:5 { - s32:5 - } else if diff < s33:-5 { - s32:-5 - } else { - diff as s32 - }; - send(tok, result, clamped_diff); - val - } + ch_in: chan in; + result: chan out; + + config(ch_in: chan in, result: chan out) { (ch_in, result) } + + init { s32:0 } + + next(prev: s32) { + let (tok, val) = recv(join(), ch_in); + let diff = (val as s33) - (prev as s33); + let clamped_diff = if diff > s33:5 { + s32:5 + } else if diff < s33:-5 { + s32:-5 + } else { + diff as s32 + }; + send(tok, result, clamped_diff); + val + } } ``` @@ -391,7 +369,7 @@ pub proc serialize { init { join() } - config (ch_in: chan in, result: chan out) { + config(ch_in: chan in, result: chan out) { (ch_in, result) } @@ -448,32 +426,25 @@ the lower-priority data (from `data1_in`) only when there's no higher-priority message to send: ```dslx -struct Message { - value1: u32, - value2: u64, - value3: bool -} +struct Message { value1: u32, value2: u64, value3: bool } pub proc priority_arbiter { - data0_in: chan in; - data1_in: chan in; - result: chan<(u1, Message)> out; - - init { () } - - config (data0_in: chan in, data1_in: chan in, result: chan<(u1, Message)> out) { - (data0_in, data1_in, result) - } - - next (st: ()) { - let (tok, data0_msg, data0_valid) = recv_non_blocking(join(), data0_in, zero!()); - let (tok, data1_msg, data1_valid) = recv_if_non_blocking(tok, data1_in, !data0_valid, zero!()); - let (source, to_send) = if (data0_valid) { - (u1:0, data0_msg) - } else { - (u1:1, data1_msg) - }; - send_if(tok, result, data0_valid || data1_valid, (source, to_send)); - } + data0_in: chan in; + data1_in: chan in; + result: chan<(u1, Message)> out; + + config(data0_in: chan in, data1_in: chan in, result: chan<(u1, Message)> out) { + (data0_in, data1_in, result) + } + + init { () } + + next(st: ()) { + let (tok, data0_msg, data0_valid) = recv_non_blocking(join(), data0_in, zero!()); + let (tok, data1_msg, data1_valid) = + recv_if_non_blocking(tok, data1_in, !data0_valid, zero!()); + let (source, to_send) = if data0_valid { (u1:0, data0_msg) } else { (u1:1, data1_msg) }; + send_if(tok, result, data0_valid || data1_valid, (source, to_send)); + } } ``` diff --git a/xls/dslx/dslx_fmt.cc b/xls/dslx/dslx_fmt.cc index 25716b81dd..664e921413 100644 --- a/xls/dslx/dslx_fmt.cc +++ b/xls/dslx/dslx_fmt.cc @@ -46,6 +46,9 @@ // Note: we attempt to keep our command line interface similar to clang-format. ABSL_FLAG(bool, i, false, "whether to modify the given path argument in-place"); +ABSL_FLAG(bool, error_on_changes, false, + "whether to error if the formatting changes the file contents"); + ABSL_FLAG(std::string, dslx_path, "", "Additional paths to search for modules (colon delimited)."); ABSL_FLAG(std::string, mode, "autofmt", @@ -129,6 +132,11 @@ absl::Status RealMain(std::string_view input_path, } else { std::cout << formatted << std::flush; } + + if (absl::GetFlag(FLAGS_error_on_changes) && formatted != contents) { + return absl::InternalError("Formatting changed the file contents."); + } + return absl::OkStatus(); } diff --git a/xls/dslx/fmt/ast_fmt_test.cc b/xls/dslx/fmt/ast_fmt_test.cc index 953637329a..7a0194eb41 100644 --- a/xls/dslx/fmt/ast_fmt_test.cc +++ b/xls/dslx/fmt/ast_fmt_test.cc @@ -2027,6 +2027,13 @@ fn f() -> bool { true } )"); } +TEST_F(ModuleFmtTest, QuickCheckExhaustive) { + DoFmt( + R"(#[quickcheck(exhaustive)] +fn f(x: u1) -> bool { true } +)"); +} + TEST_F(ModuleFmtTest, SimplePublicFunction) { DoFmt( R"(pub fn id(x: u32) -> u32 { x } diff --git a/xls/tests/BUILD b/xls/tests/BUILD index 2ab807a689..348787f715 100644 --- a/xls/tests/BUILD +++ b/xls/tests/BUILD @@ -90,6 +90,7 @@ py_test( "//docs_src:dslx_documentation_files", "//docs_src/tutorials:all_files", "//xls/dslx:interpreter_main", + "//xls/dslx:dslx_fmt", "//xls/dslx/tests:mod_imported_file", ], python_version = "PY3", diff --git a/xls/tests/documentation_test.py b/xls/tests/documentation_test.py index 708ac45bb2..17b169b2da 100644 --- a/xls/tests/documentation_test.py +++ b/xls/tests/documentation_test.py @@ -14,6 +14,7 @@ """Tests that DSLX blocks in reference documentation are valid.""" import dataclasses +import difflib import re import subprocess as subp import sys @@ -29,6 +30,7 @@ _DSLX_RE = re.compile('^```dslx$(.*?)^```$', re.MULTILINE | re.DOTALL) _INTERP_PATH = runfiles.get_path('xls/dslx/interpreter_main') +_FMT_PATH = runfiles.get_path('xls/dslx/dslx_fmt') _INPUT_FILES = [ 'docs_src/dslx_ffi.md', @@ -122,6 +124,29 @@ def test_dslx_blocks(self, i: int, dslx_block: str) -> None: print(p.stderr) self.assertEqual(p.returncode, 0) + @parameterized.named_parameters(get_examples()) + def test_dslx_blocks_fmt(self, i: int, dslx_block: str) -> None: + """Checks a DSLX block is canonically formatted.""" + example = strip_attributes(dslx_block) + input_contents = example.dslx.lstrip() + x_file = self.create_tempfile( + file_path=f'doctest_fmt_{i}.x', content=input_contents + ) + cmd = [_FMT_PATH, "--error_on_changes"] + example.flags + [x_file.full_path] + print('Running command:', subp.list2cmdline(cmd), file=sys.stderr) + p = subp.run(cmd, check=False, stdout=subp.PIPE, stderr=subp.PIPE, encoding='utf-8') + if p.returncode != 0: + print('== diff:') + diff = difflib.unified_diff(input_contents.splitlines(), p.stdout.splitlines()) + print('\n'.join(diff)) + print('== DSLX block:') + print(input_contents) + print('== stdout:') + print(p.stdout) + print('== stderr:') + print(p.stderr) + self.assertEqual(p.returncode, 0) + if __name__ == '__main__': absltest.main()