What logic is inferred when there are multiple assign statements targeting the same wire for synthesis ?

The synthesis tool will give a syntax error for a wire that is an output port of a module if it is driven by more than one source.

wire out;

assign out = a & b; 

// Elsewhere in the code, another assign to 
// the same wire will cause multiple driver error
assign out = a | b;

However, it is okay to drive a 3-state wire by multiple assign statements.

wire out;

// sel1 and sel2 cannot be 1 at the same time
assign out = sel1 ? a & b : 1'bz;
assign out = sel2 ? a | b : 1'bz;

What do conditional assignments get inferred into?

Conditional assignments ? : in Verilog get inferred into multiplexers during synthesis. A multiplexer selects one of a number of inputs based on the values of the select inputs.

	// Assign in0 to out if sel = 1 else in1
    assign out = sel  ? in0 : in1;

What is the logic that gets synthesized when conditional operators in a single continuous assignment are nested?

In Verilog, when conditional operators in a single continuous assignment are nested, the synthesis tool will infer a hierarchy of multiplexers.

assign out = sel1 ? (sel2 ? in3 : in4) : (sel3 ? in5 : in6);

// Which is the same as
wire net1, net2;

assign net1 = sel2 ? in3 : in4;
assign net2 = sel3 ? in5 : in6;
assign out  = sel1 ? net1 : net2;

What value is inferred when multiple procedural assignments made to the same reg variable in an always block?

When multiple procedural assignments are made to the same reg variable in an always block, the last assignment will be inferred as the final value of the reg variable.

reg [3:0] data;

always @ (posedge clk) begin
    data <= 4'hA;
    data <= 4'h2;

During simulation, the data variable will be initialized to an unknown value. When a positive edge of clk occurs, both the assignments will occur in the order they are written. However, since both assignments use non-blocking assignment, the value of data after the clock edge will be the value assigned last, in this case 2.

Why should a nonblocking assignment be used for sequential logic, and what would happen if a blocking assignment were used?

A nonblocking assignment should be used for sequential logic in Verilog because it models a flip-flop's behavior more accurately. Nonblocking assignments infer a level-sensitive behavior where the assigned value is used in the next cycle, representing the clock-to-Q delay of the flip-flop.

reg a, b, c, out;

always @ (posedge clk) begin
	a 	<= in1;
	b 	<= a;
	c 	<= b;
	out <= c;

The code above will get synthesized into a single flip-flop with the d input of in1 and q output of c . This is because the intermediate results were stored in a blocking format, and the final result didn't require waiting for these results to be assigned. Since out is influenced only by the posedge of clock, it turned out to be a FF.

reg a, b, c, out;

always @ (in1) begin
	a 	= in1;
	b 	= a;
	c 	= b;
	out = c;

Assignments above are made in a combinatorial block using blocking statements and the logic synthesized will be a simple direct connection between in1 and out .

What does the logic in a function get synthesized into? What are the area and timing implications of calling functions in RTL?

The logic in a function in Verilog typically gets synthesized into combinational logic in the generated hardware since it does not have any constructs that advance time. The function definition essentially defines a block of combinational logic that takes some input values and produces an output value based on those input values.

The area and timing implications of calling functions depend on the complexity of the function implementation, the number of input and output ports, and the frequency and timing constraints of the system. If the calls to a function are used in different paths, the logic gets replicated, else it may get multiplexed.

module des (input [3:0] a, b, opc, output [3:0] out1, out2, out3);

	function [3:0] compute(input [3:0] a, b, opc);
		case (opc)
			2'b00 	: alu = a & b;
			2'b01 	: alu = a | b;
			2'b10 	: alu = a ^ b;
			default : alu = ~(a & b);

	assign out1 = compute(a, b, 0);
	assign out2 = compute(a, b, 1);
	assign out3 = compute(a, b, 2);


In the example given above, all outputs use has a different use of the function and would get independent logic for each function call. But say if out1 and out2 both required AND gate functionality, then it would use common logic.

What are a few important considerations while writing a Verilog function?

  1. Function interface: The inputs and outputs of the function should be carefully defined, along with their data types and bit widths. If the width of hte return value is not defined, it will end up with a default of 1 bit.
  2. Function scope: Verilog functions are typically defined within a module, and their scope is limited to that module. As such, it is important to ensure that the function is not trying to access variables or signals outside its scope.
  3. Function complexity: The complexity of the function implementation should be kept in check, as this can have a significant impact on the area and timing of the generated hardware. Functions should be designed to be as simple and straightforward as possible, avoiding any unnecessary complexity.
  4. Combinational vs Sequential logic: Verilog functions are intended to be used for combinational logic, however, it is important to ensure that the function does not create sequential logic by introducing latches or flip-flops in the generated hardware.
    function oh_latch(input in, sel);
      // else part is missing so output will be
      // latched to "in"
      if (sel)
      	oh_latch = in;
  6. Parameters: Parameters and integers are local only to that function and cannot be outside its scope.

What does the logic in a task get synthesized into? Explain with an example.

Synthesis tools ignore all timing constructs within a task like @. A task may be used to synthesize basic combinatorial logic, however if the output of the task is assigned to a storage element, it will synthesize a sequential element.

task add_two_inputs(input [7:0] a, input [7:0] b, output [7:0] sum) begin
   sum = a + b;

module top;
   reg  [7:0] a, b;
   wire [7:0] sum;
   always @(a, b)
      add_two_inputs(a, b, sum);


What are the differences between using a task, and defining a module for implementing reusable logic?

  1. Hierarchical design: Modules can be used to implement hierarchical designs by instantiating other modules inside them. Tasks, however, cannot be reused in this way, because they are not self-contained and require a parent module to be called from.
  2. Complexity: Modules can be used to implement complex designs with significant functionality, and can include sub-modules to make the design more modular and easier to manage. Tasks, on the other hand, are generally used for simpler calculations or tasks that are performed repeatedly throughout the design.
  3. Floorplanning: Can be placed as a block during floorplanning because it has a hierarchy that is fully defined. But logic inside a task cannot be moved around because it will be just a part in the sea of gates.
  4. Testing: Modules are often easier to test than tasks because they can be connected to test benches that generate inputs and check outputs. Tasks can be tested, but it requires creating a parent module to call the task and then testing that module.

Can tasks and functions be declared external to the scope of module-endmodule?

Yes, tasks and functions can be declared external to the scope of module endmodule in Verilog for versions after 2001.

Read more on Verilog Functions and Tasks.