diff --git a/docs/pyrope/00-hwdesign.md b/docs/pyrope/00-hwdesign.md index 8882ffd..26072b1 100644 --- a/docs/pyrope/00-hwdesign.md +++ b/docs/pyrope/00-hwdesign.md @@ -224,7 +224,7 @@ iterate in loops, and the program finishes execution when the main finishes. In contrast, most HDLs differ from software languages in that they specify an instantiation tree hierarchy of modules, and then provide some syntax on how -each module executes independently of the other modules. +each module executes independently of the other modules. In HDLs, the execution never ends, and the modules run independently. It @@ -330,7 +330,7 @@ be expected in a software API. If actors execution resembles concurrent module instantiation execution, async/await resembles pipelining. In async/await, the results of a function are not available at the function return. In HDLs, there is no await, and the -results from previous cycles are output by the module instance. +results from previous cycles are output by the module instance. @@ -604,7 +604,7 @@ not be recycled. As a result, the destructor may not make sense in hardware. Most software languages support passing function arguments either by value or reference. This is done to avoid copying the object that may reside in memory. -Again, HLS has no memory. Therefore it is not as problematic. +Again, HLS has no memory. Therefore it is not as problematic. Most HDLs only support passing by value. This is not a drawback but avoid another source of bugs without the cost overhead that it will represent in a diff --git a/docs/pyrope/02-basics.md b/docs/pyrope/02-basics.md index 0bc6923..dbb36e2 100644 --- a/docs/pyrope/02-basics.md +++ b/docs/pyrope/02-basics.md @@ -304,7 +304,7 @@ can have side effects. Pyrope constrains the expressions so that no matter the evaluation order, the synthesis result is the same. -As a reference, languages like C++11 do not have a defined order of evaluation for +Languages like C++11 do not have a defined order of evaluation for all types of expressions. Calling `call1() + call2()` is not defined. Either `call1()` first or `call2()` first. diff --git a/docs/pyrope/03-bundle.md b/docs/pyrope/03-bundle.md index 798edee..d3bbea3 100644 --- a/docs/pyrope/03-bundle.md +++ b/docs/pyrope/03-bundle.md @@ -132,7 +132,7 @@ cassert a!=b cassert b == c == d ``` -A tuple with a single entry element is called a scalar. +A tuple with a single entry element is called a scalar. Tuples are used in many places: @@ -173,7 +173,7 @@ can be used for name or default value during the tuple declaration. ``` var b = 100 -var a = (b:u8, b, b:u8 = _, let c=4) // a.0 and a.1 are unnamed, a.2==a.b +var a = (b:u8, b, b:u8 = _, let c=4) // a.0 and a.1 are unnamed, a.2==a.b a.b = 200 assert a == (100, 100, 200, 4) @@ -233,7 +233,7 @@ the same entry already exists. ## Field access Since everything is a tuple, any variable can do `variable.0.0.0.0` because it -literaly means, return the tuple first entry for four times. +literaly means, return the tuple first entry for four times. Another useful shortcut is when a tuple has a single field or entry, the tuple @@ -253,6 +253,17 @@ assert x.0 == 3 ``` +Tuples can also use structural binding to unpack a tuple multiple fields into separate variables. + +``` +let x = (f1=(f1a=1,f1b=3), f2=4) + +let (y,z) = x +assert y == (1,3) and z == 4 +assert y.f1a == 1 and y.f1b == 3 +assert y == (f1a=1,f1b=3) +``` + ## Tuples vs arrays Tuples are ordered, as such, it is possible to use them as arrays. Tuples and @@ -262,6 +273,8 @@ unnamed with the same type for all the entries. ``` var bund1 = (0,1,2,3,4) // ordered and can be used as an array +var array1 = [0,1,2,3,4] // [] force array, so all the entries have same type + var bund2 = (bund1,bund1,((10,20),30)) assert bund2[0][1] == 1 assert bund2[1][1] == 1 @@ -290,7 +303,7 @@ if runtime { } // Index can be 2 or 4 -var res1 = array[index] // compile error, out of bounds access +var res1 = array[index] // compile error, out of bounds access var res2 = 0sb? // Possible code to be compatible with Verilog if index<3 { @@ -347,7 +360,7 @@ b = xx[1,2] // same as: xx[(1,2)] for a in 1,2,3 { // same as: for a in (1,2,3) { x = a } -y = match z { +y = match z { in 1,2 { 4 } // same as: in (1,2) { 4 } else { 5 } } @@ -355,7 +368,7 @@ y2 = match var one=1 ; one ++ z { // same as: y2 = match (1,z) { == (1,2) { 4 } } -let addb = fun(a,b:u32)-> a:u32 { // same as: letaddb = fun(a,b:u32)->(a:u32) +let addb = fun(a,b:u32)-> a:u32 { // same as: letaddb = fun(a,b:u32)->(a:u32) a = a + b } ``` @@ -387,7 +400,7 @@ let c = 4 let (x,b) = (true, c:u3) // assign x=true, b=4 AND check that c is type u3 cassert x == true -cassert b == 4 +cassert b == 4 ``` ## Enumerate (`enum`) diff --git a/docs/pyrope/04-variables.md b/docs/pyrope/04-variables.md index da08677..3396322 100644 --- a/docs/pyrope/04-variables.md +++ b/docs/pyrope/04-variables.md @@ -257,13 +257,13 @@ assert b@[0] == false ``` -A range is a separate tuple. As such it can not directly compare with +A range is a separate tuple. As such it can not directly compare with tupes. It requires an explicit conversion. If the range does not contain negative values, it can be converted to an integer back and forth which corresponds to a one-hot encoding. Range type cast from integers use the same one-hot encoding. It is not possible -to type cast from tuple to range, but it is possible from range to tuple. +to type cast from tuple to range, but it is possible from range to tuple. ``` let c = 1..=3 @@ -340,7 +340,7 @@ assert "h" ++ "ell" == ('h','e','l','l') == "hell" ## Type declarations Each variable has a type, either implicit or explicit, and as such, it can be -used to declare a new type. +used to declare a new type. Pyrope does not have a `type` keyword. Instead it leverages the tuples for type creation. The difference is that a type should be an immutable variable, and @@ -371,7 +371,7 @@ assert x equals z // same type structure assert y is typ assert typ is typ -assert z !is bund3 +assert z !is bund3 assert z !is typ assert z !is bund1 ``` @@ -405,11 +405,17 @@ number of bits in an assertion, or placement hints, or even interact with the synthesis flow to read timing delays. -Pyrope does not specify the attributes, the compiler flow specifies them. +A key difference from attribute and tuple fields is that attributes are always +compile time and the compiler flow has special meaning functionality for them. + + +Pyrope does not specify all the attributes, the compiler flow specifies them. +There are some built-in required attributes like checking the number of bits. + Reading attributes should not affect a logical equivalence check. Writing attributes can have a side-effect because it can change bits use for wrap/saturate or change pins like reset/clock in registers. Additionally, -attributes can affect assertions, so they can stop/abort the compilation. +attributes can affect assertions, so they can stop/abort the compilation. The are three operations that can be done with attributes: set, check, read. @@ -418,7 +424,7 @@ The are three operations that can be done with attributes: set, check, read. assignment or directly accessed. If a variable definition, this binds the attribute with all the use cases of the variable. If the variable just changes attribute value, a direct assignment is possible E.g: `foo::[max=300] - = 4` or `baz.[attr] = 10` + = 4` or `baz.[attr] = 10` * Check: when associated to a type property in the right-hand-side of an assignment. The attribute is a comma separated list of boolean expression @@ -430,7 +436,7 @@ The are three operations that can be done with attributes: set, check, read. The attribute set, writes a value to the attribute. If no value is given a boolean `true` is set. The attribute checks are expressions that must evaluate -true. +true. Since conditional code can depend on an attribute, which results in executing a @@ -764,8 +770,8 @@ tuple or file the `private` attribute must be set. The private has different meaning depending on when it is applied: -* When applied to a tuple entry (`(field::[private] = 3)`), it means that the - entry can not be accessed outside the tuple. +* When applied to a tuple entry (`(field::[private] = 3)`), it means that the + entry can not be accessed outside the tuple. * When applied to a `pipestage` variable (`var foo::[private] = 3`), it means that the variable is not pipelined to the next type stage. Section @@ -833,7 +839,7 @@ cassert 1<<(1,4,3) == 0b01_1010 string, or already a tuple Most operations behave as expected when applied to signed unlimited precision -integers. +integers. The `a in b` checks if values of `a` are in `b`. Notice that both can be tuples. If `a` is a named tuple, the entries in `b` match by name, and then @@ -1091,11 +1097,14 @@ i = a == 3 <= b == d assert i == (a==3 and 3<=b and b == d) ``` -Comparators can be chained, but only when they follow the same type. +Comparators can be chained, but only when they follow the same type or the +direction is the same. ``` assert a <= b <= c // same as a<=b and b<=c +assert a < b <= c // same as a< b and b<=c assert a == b <= c // compile error, chained only allowed with same comparator +assert a <= b > c // compile error, not same direction ``` ## Optional @@ -1315,7 +1324,7 @@ The same rules apply when a tuple or a type is declared. let a = "foo" var at1 = ( - ,a:string + ,a:string ) cassert at1[0] == "foo" cassert at1 !has "a" // at1.a undefined @@ -1364,3 +1373,27 @@ assert rand implies z == 5 assert !rand implies z == 6 ``` +It is also possible to assign to an underscore, it behaves like a sink. It may be useful when dealing +with structured bindings ()multiple return values) with unused arguments. + + +``` +let weird_pick_bits = fun(b:u32) -> (x:u1, _:u4) { + return (x=n@[2..<3], b@[5]) +} + +fun fcall_returns_2_values()->(xx,yy) { + xx = 3 + yy = 7 +} + +let (a,_) = fcall_returns_2_values() +assert a == 3 + +var b:u8 = 3 +_ = a // legal, but it is just a "read" to 'a' +_ = 3 +b = _ +assert b == 0 and not b.[valid] +``` + diff --git a/docs/pyrope/06-functions.md b/docs/pyrope/06-functions.md index 1b9307b..caffc3c 100644 --- a/docs/pyrope/06-functions.md +++ b/docs/pyrope/06-functions.md @@ -142,14 +142,14 @@ The lambda definition has the following fields: ``` var add:fun(...x) = _ -add = fun (...x) { x.0+x.1+x.2 } // no IO specified -add = fun (a,b,c){ a+b+c } // constrain inputs to a,b,c -add = fun (a,b,c){ a+b+c } // same -add = fun (a:u32,b:s3,c){ a+b+c } // constrain some input types -add = fun (a,b,c) -> (x:u32){ a+b+c } // constrain result to u32 -add = fun (a,b,c) -> (res){ a+b+c } // constrain result to be named res -add = fun (a,b:a,c:a){ a+b+c } // constrain inputs to have same type -add = fun (a:T,b:T,c:T){ a+b+c } // same +add = fun(...x) { x.0+x.1+x.2 } // no IO specified +add = fun(a,b,c){ a+b+c } // constrain inputs to a,b,c +add = fun(a,b,c){ a+b+c } // same +add = fun(a:u32,b:s3,c){ a+b+c } // constrain some input types +add = fun(a,b,c) -> (x:u32){ a+b+c } // constrain result to u32 +add = fun(a,b,c) -> (res){ a+b+c } // constrain result to be named res +add = fun(a,b:a,c:a){ a+b+c } // constrain inputs to have same type +add = fun(a:T,b:T,c:T){ a+b+c } // same x = 2 var add2:fun2(a) = _ @@ -201,8 +201,7 @@ There are several rules on how to handle arguments. * Function calls with arguments do not need parenthesis after newline or a variable assignment: `a = f(x,y)` is the same as `a = f x,y` -* Functions explicitly declared without arguments, do not need parenthesis in - function call. +* Functions without arguments, need explicit parenthesis in function call. Pyrope uses a Uniform Function Call Syntax (UFCS) when the first argument is `self`. It resembles Nim or D UFCS but it can be different from the order in @@ -215,9 +214,9 @@ let div2 = fun (...x){ x.0 / x.1 } // unnamed input tuple let noarg = fun () { 33 } // explicit no args -assert 33 == noarg() +assert 33 == noarg() // () needed to call -assert noarg == 33 // compile error, `noarg()` needed for calls without arguments +assert noarg // compile error, `noarg()` needed for calls without arguments a=div(3 , 4 , 3) // compile error, div has 2 inputs b=div(self=8, b=4) // OK, 2 @@ -252,7 +251,7 @@ var tup = ( ) let f1 = fun (self){ 2 } // compile error, f1 shadows tup.f1 -let f2 = fun (self){ 3 } +let f1 = fun (){ 3 } // OK, no assert f1() != 0 // compile error, missing argument assert f1(tup) != 0 // compile error, f1 shadowing (tup.f1 and f1) @@ -340,13 +339,16 @@ inc1(ref y) assert y == 4 let banner = fun() { puts "hello" } -let execute_method = fun(fn) { +let execute_method = fun(fn:fun()->()) { // example with explicit type for fn fn() // prints hello when banner passed as argument } execute_method(banner) // OK ``` +In Pyrope, to call a method, parenthesis are needed only when the method has arguments. +This is needed to distinguish for higher order functions that need to distinguish between +a function call and a pass of the lambda. ## Output tuple diff --git a/docs/pyrope/10b-vslang.md b/docs/pyrope/10b-vslang.md index 531b062..8ec0377 100644 --- a/docs/pyrope/10b-vslang.md +++ b/docs/pyrope/10b-vslang.md @@ -126,16 +126,19 @@ let max_gap_count = fun(nums) { } ``` -## Swift +A significant difference is that Pyrope everything is by value. In C++, you could do code with undefined behaviour very easily by mistake when dealing with pointers. + +``` +const T& f2(T t) { return t; } // returns pointer to local +``` -There are many diffirences with Swift, but this section just highlights a couple because it helps -to understand the Pyrope semantics. +## Swift +There are many diffirences with Swift, but this section just highlights a couple because it helps to understand the Pyrope semantics. ### Protocol vs Pyrope constrains -Swift protocols resemble type classes. As such require consent for implementing -a functionality. Pyrope resembles C++ concepts that constraint functionality. +Swift protocols resemble type classes. As such require consent for implementing a functionality. Pyrope resembles C++ concepts that constraint functionality. ```swift func add(a:T, b:T) -> T { a + b } // compile error @@ -269,3 +272,38 @@ assert x[0,1] == 2 == x[0][1] assert x[1,0] == 3 == x[1][0] ``` +## Go + +Pyrope and go have several similarities but with slightly different syntax. For example, functions capacity to have multiple name return values is quite similar. + +Some significant difference is the built-in and imports. + +In Go: +``` +func larger(a, b []string) []string { + len := len(a) + if len > len(b) { // Error: invalid operation: cannot call non-function len (variable of type int) + return a + } + return b +} +``` + +In Pyrope: +``` +import std as std + +fun larger(a:[]string, b:[]string) -> (:[]string) { + let strlen := std.strlen(a) + if strlen > std.strlen(b) { + return a + } + return b +} + +// Using attributes (bits != strlen, but works too) +fun larger(a:[]string, b:[]string) -> (:[]string) { + if a.[bits] > b.[bits] { a }else{ b } +} +``` +