This post was created for people who are already familiar with Lucid V1 to get up to speed with what's new/different in Lucid V2.
Style Changes
Let's start off with the most basic changes, those to style.
Optional Semicolons
First off, the new Lucid grammar now accepts a new line as a semicolon.
This means that as long as each expression is on a separate line, you don't need to use semicolons at all.
This change was motivated by many modern programming languages.
Trailing Commas
Again taking inspiration from many other languages, trailing commas in lists are now supported.
module trailingComma (
input clk, // clock
input rst, // reset
output out, // <- LOOK A COMMA
) ...
By making trailing commas optional, it helps make it easier to re-arrange items in a list or add new items to the end without modifying previous lines.
Camel Case
This is fairly subtle, but camel case is now the preferred naming format for all basic names. The libraries and examples in Lucid V1 were fairly inconsistent between camelCase
and snake_case
.
This is just to make things more consistent.
Struct Position
The first breaking change is that struct sizes are now specified after the array size (or name if there isn't an array size) instead of after the type.
For example, before when declaring a sig
with an array of 8 structs with type myStruct
you would write the following.
sig<myStruct> mySig[8];
This is now written as the following.
sig mySig[8]<myStruct>
This change was made to better match how the elements in the signal are structured.
For example, mySig[1].structElement
is how you access the structElement
of the second element in the array.
In both cases, the sig
is an array of structs so the array index always comes first.
This also makes a bit more sense for the cases when you need to specify the width of a signal that doesn't have a type.
For example, a struct declaration could look like this.
struct myStruct {
structElement<otherStruct>,
structArray[3]<otherStruct>,
}
No More .WIDTH
Something that always felt kind of out of place to me was the .WIDTH
attribute attached to "all" signals in Lucid V1.
While it worked for the most part, it had two issues I wanted to address.
First, it just didn't seem to fit. There was nothing else like it in the language.
Second, it could only be used on full signals, not expressions or anything else.
Both of these are fixed by replacing it with the new $width()
function.
Simply pass in whatever you want and it'll spit out a constant value representing the width of the signal/expression.
This fits much better as there are plenty of other functions to calculate constant values.
The use of .WIDTH
was in Lucid before there were custom Lucid functions. Otherwise, it probably would've been a function from the beginning.
Simplification
There are a few changes made to make things a bit simpler.
repeat
Loops
For loops were replaced with the new repeat
loop.
Lucid V1 used C style for loop that were easy to write in a way that would be impossible to implement in hardware.
The new repeat
loop has the simple syntax of repeat(i, count, start = 0, step = 1) {}
where i
is an optional loop variable.
count
is a constant expression.
start
and step
are also constant expressions but are optional.
The loop will repeat count
times and i
will be set from start
to start + step * (count - 1)
.
This syntax makes it impossible to write a loop with a variable number of iterations (which hardware can't accommodate).
No More var
The var
type was always a bit weird. It was basically just a 32 bit sig
that you would use with for loops.
No for loops means you doubly don't need it.
Anywhere you had a var
before could be replaced with a sig
.
Bye fsm
, Hello enum
The type fsm
was also kind of a weird type. It was a dff
with a list of constants attached.
Nothing kept you from using these constants in other places in your designs and a few times I did just that. It always felt a little wrong.
Now the enum
type lets you declare a list of constants that will have their values assigned by the tools just like fsm
but it is separate from any storage type.
Something like
fsm state { IDLE, START_BIT, DATA, STOP_BIT };
...
state.d = state.START_BIT;
is now replaced with
enum States { IDLE, START_BIT, DATA, STOP_BIT }
dff state[$width(States)]
...
state.d = States.START_BIT
This makes the dff
type the only storage type.
Parameter Test Values
In Lucid V1 you had to choose between better error checking and making a parameter optional.
Lucid V2 adds the ~
operator during parameter declaration to allow you to specify a test value.
Here's an example.
module uartTx #(
CLK_FREQ ~ 100000000 : CLK_FREQ > 0, // clock frequency
BAUD ~ 1000000 : BAUD > 0 && BAUD <= CLK_FREQ/2 // desired baud rate
)(
Both the parameter CLK_FREQ
and BAUD
have test values specified. These values are used when the module is being checked for errors but hasn't been instantiated.
If these values were omitted, the error checking code doesn't know what they could be so it has to do its best to check for potential errors.
You could provided a value with =
instead of ~
but this allows the parameter to be omitted when it is instantiated.
The ~
version provides a value for the error checker to use but still requires a value to be explicitly presented when instantiated.
Test Benches
A huge reason for the whole Alchitry Labs/Lucid V2 rewrite was to add simulations.
These have been covered in a previous post