Dataflow modeling provides a higher level of abstraction than structural (gate-level) modeling. Instead of describing a circuit by connecting individual gates, dataflow modeling describes it in terms of how data flows through the circuit and how it is processed. This style focuses on the function of the circuit rather than its specific gate implementation, making it a more efficient and intuitive way to design combinational logic.
The core of dataflow modeling is the continuous assignment, which uses the `assign` keyword. These assignments model how a value is continuously driven onto a net (like a `wire`), similar to how a physical wire in a circuit is always driven by the output of a gate.
Continuous Assignments (`assign`)
The `assign` statement is the fundamental construct in dataflow modeling. It is used to drive a value onto a net variable.
Key Characteristics:
- Continuous Evaluation: An `assign` statement is always active. The expression on the right-hand side is continuously evaluated, and whenever any of its operands change, the result is immediately propagated to the net on the left-hand side.
- Target Must Be a Net: The left-hand side of an `assign` statement must be a net data type, most commonly a `wire`. It cannot be a `reg` data type.
- Concurrent Execution: All `assign` statements in a module execute concurrently (in parallel), reflecting the parallel nature of hardware.
Syntax
assign [delay] net_variable = expression;
net_variable
is typically a `wire`.expression
is a combination of other signals and operators.- The optional
delay
is used for simulation purposes to model gate delays.
Practical Examples of Dataflow Modeling
Example 1: Simple Logic Gates
Here’s how you can model basic logic gates using a single `assign` statement for each output.
module basic_gates (
input wire a, b,
output wire y_and, y_or, y_xor, y_nand, y_not_a
);
assign y_and = a & b;
assign y_or = a | b;
assign y_xor = a ^ b;
assign y_nand = ~(a & b);
assign y_not_a = ~a;
endmodule
Example 2: 2-to-4 Decoder
A 2-to-4 decoder takes a 2-bit input and activates one of four output lines. We can model this with four separate `assign` statements.
module decoder_2_to_4 (
input wire [1:0] in,
input wire en,
output wire [3:0] out
);
assign out[0] = en & ~in[1] & ~in[0];
assign out[1] = en & ~in[1] & in[0];
assign out[2] = en & in[1] & ~in[0];
assign out[3] = en & in[1] & in[0];
endmodule
Example 3: 4-bit Full Adder
A 4-bit adder can be modeled concisely using dataflow assignments. The concatenation operator `{}` is used to combine the final carry-out with the 4-bit sum.
module full_adder_4bit (
input wire [3:0] a, b,
input wire c_in,
output wire [3:0] sum,
output wire c_out
);
// An intermediate 5-bit wire to hold the full result
wire [4:0] result;
// The addition is performed in a single expression
assign result = a + b + c_in;
// The lower 4 bits are the sum, the 5th bit is the carry-out
assign sum = result[3:0];
assign c_out = result[4];
// Alternative one-line assignment using concatenation:
// assign {c_out, sum} = a + b + c_in;
endmodule