This is a quick introduction to Dataflow Verilog. Dataflow Verilog is a more flexible and powerful style of Verilog coding. Dataflow Verilog will also appear more similar to other programming languages with operators that you've seen before. See the book for more information. 1)
To give wires or ports values in Dataflow Verilog, you must use assign with =
.
Example: assign wire_name = 4'b0101;
will make the wire wire_name hold the value 4'b0101
.
These can include conditions and operations on the right side.
Example: assign wire_name = 4'b0101 + 4'b0011
will make the wire wire_name the value of 4'b0101
added to 4'b0011
.
Dataflow Verilog has operators that you are most likely familiar with from other programming languages! Nearly all of them operate just as you would expect. The following table is a simplified version of the one found in the textbook. 2) Some of these will be explained in detail after the table.
Note that nearly all of these operators are simply shorthand for sometimes large, complex circuits. Behind the scenes, Vivado will convert these operators into the necessary circuits and place them in your design when it is implemented.
Type | Symbol | Operation |
---|---|---|
Arithmetic | *, /, +, - | Multiplication, Division, Addition, Subtraction |
% | Modulo | |
Logical | ! | Not |
&& | And | |
|| | Or | |
Bitwise | ~ | Not |
& | And | |
| | Or | |
^ | Xor | |
~^ | Xnor | |
Comparison | <, >, <=, >= | Greater than, Less than, etc. |
Equality | ==, != | Equal, Not equal |
Reduction | & | And |
~& | Nand | |
| | Or | |
~| | Nor | |
^ | Xor | |
~^ | Xnor | |
Shift | <<, >> | Shift left, Shift right |
Concatenate | { , } | Concatenate items in list |
Replicate | { {}} | Replicate item in inner braces |
Ternary | ?: | Conditional |
4'b0101 && 4'b1010
will evaluate to 1
because both numbers equate to true. This happens because both numbers are not 0. 0 is the only number that is equivalent to false, while any other number equates to true.4'b0101 & 4'b0011
will evaluate to 4'b0001
whereas for a bitwise or 4'b0101 | 4'b0011
will evaluate to 4'b0111
.&(4'b1100)
is the same as performing 1 & 1 & 0 & 0
which evaluates to 0
.4'b1001 << 1
results in the number 4'b0010
. { 4'b0101, 4'b0000, 4'b1111 }
returns the number 12'b010100001111
.{4{4'b1010}}
returns 16'b1010101010101010
.(4'b0001 == 4'b0010) ? 2'b11 : (4'b1110 + 1'b1)
evaluates to (4'b1110 + 1'b1)
which is immediately executed and its result 4'b1111
is returned. (a == b) ? 2'b11 : (a == c) ? 2'b01 : (a == d) ? 2'b10 : 2'b00
assign myOutput = (4'b0001 == 4'b0010) ? 2'b11 : (4'b1110 + 1'b1)
The Dataflow Verilog operators allow you to use several clever tricks to instantiate some complex components very quickly and concisely. You should know these well.
You can build an n:m decoder (meaning, any size of decoder) by using the shift left <<
operator. The following example is functionally equivalent to a 2:4 decoder. (Note that the bit widths of the input, output, and number constant will changed depending on the size of the decoder you are building.) Note that this is not the type of decoder that you need to build in lab 5.
input [1:0] sel; output [3:0] result; assign result = 4'b0001 << sel;
Any size of multiplexer is easy to build using nested ternary operators. The following example is a 4:1 multiplexer.
input [1:0] sel; input a, b, c, d; output result; assign result = (sel == 0) ? a : (sel == 1) ? b : (sel == 2) ? c : d;
Parameterization allows for much more flexible Verilog designs. It allows you to build modules with inputs and outputs that have bit widths that can be set upon instantiation of that module, rather than hard-coded into the module itself. This is an extremely useful concept of Dataflow Verilog.
To use parameterization, you must first build your module with parameterization in mind. The syntax to do so is explained in the example below. This uses the multiplexer shortcut explained above to make a 2:1 multiplexer with bus sizes that are variable.
module pMux(result, sel, a, b); parameter X = 1; input sel; input [X-1:0] a, b; output [X-1:0] result; assign result = sel ? b : a; endmodule
parameter X defines a parameter for this module and sets its name to X. This is the parameter that can be set on instantiation.
X = 1; sets a default value for this parameter. Meaning, if no value is given for the parameter when instantiating this module, it defaults to 1.
input [X-1:0] a, b; sets the size of the inputs a and b to be whatever value X is. Remember that the bus syntax [X-1:0] sets the MSB index and the LSB index. Thus, for a bus of size X, the MSB index must be X-1.
output [X-1:0] result; does the same for the output.
A module with a parameter can be instantiated without setting the parameter value. This will cause the parameter to take its default value, as explained above. The following example will create a 2:1 multiplexer with 1-bit inputs and output.
pMux myMux(result, sel, a, b);
The real power of parameterization is utilized when setting parameter values. The following example also creates a 2:1 multiplexer, but with 16 bit inputs and output!
pMux #(16) myBigMux(result, sel, a, b);
#(16) is the syntax for setting the parameter. The number in parenthesis is what the parameter will be set to which, in this case, is 16.
The following examples show how to use more than one parameter in a parameterized module.
Creating a module.
module myModule(result, sel, a, b); parameter X = 1; parameter Y = 1; input [X-1:0] a, b; output [Y-1:0] result; input sel;
Instantiating that module.
myModule #(4, 5) myName(result, sel, a, b);