Skip to content

1.4 Combinational and sequential logic, and a first SystemVerilog module

Why this matters

This section draws the line that organizes the rest of the book: circuits that only compute, and circuits that remember. The next two chapters stay on the computing side, and Chapter 4 adds memory. It also puts the first working hardware description in your hands. A module in SystemVerilog is how a drawing of gates becomes text that a simulator can run and a synthesis tool can build.

  • Decide whether a circuit is combinational or sequential by asking whether it stores anything about earlier inputs.
  • Write a module with its ports declared as input logic and output logic.
  • Describe combinational logic with assign and the operators ~, & and |.
  • Write the same circuit with gate primitives and with named intermediate signals.
  • Recognize the syntax mistakes that a first module most often has.

A pure function in software returns the same value whenever you call it with the same arguments. An object whose methods update a field does not: ask it the same question twice and the answers can differ, because it remembers what happened in between. Circuits split in the same way. Some circuits are pure functions of their inputs, and some carry a memory along.

The software picture breaks in one place. Nobody calls a circuit. Its outputs follow its inputs continuously, and the memory of a circuit is a physical part that holds a value, not a variable that code assigns.

Definition 1.4.1(combinational logic).

Combinational logic (CL) is a network of gates with no feedback: no gate's output leads back, directly or through other gates, to one of its own inputs. Its outputs are functions of its current inputs only. For a network of gates the two halves of this definition go together: with no feedback, the outputs can depend only on the current inputs.

Definition 1.4.2(sequential logic).

Sequential logic (SL) is a circuit that contains state: stored information about earlier inputs that can affect its outputs. Its outputs depend on that memory as well as on its current inputs. The clocked designs of this book keep their state in registers, parts that hold a value from one clock edge to the next, which Chapter 4 introduces. A register's output may feed logic that computes the register's own next value, which is a feedback path, or it may feed only later registers, as in a delay line or a pipeline. Both are sequential.

Illustration a block diagram that explains an idea; its behavior is not checked

Schematic ch01/cl-sketchlogic networkabcdx1x2z1z2

A block diagram with inputs x1 and x2 and outputs z1 and z2.

Block "logic network" net: a is x1, b is x2, c is z1, d is z2.

This diagram is illustrative: its behavior is not checked.

Illustration a block diagram that explains an idea; its behavior is not checked

Schematic ch01/sl-sketchdqlogic networkabcdxclkzregister

A block diagram with inputs x and clk and output z.

Block "logic network" net: a is x, b is n2, c is z, d is n1.

Flip-flop state: d is n1, q is n2, clocked by clk.

This diagram is illustrative: its behavior is not checked.

Description: Two sketches side by side. In the first, inputs enter a box of combinational logic and outputs leave it. In the second, one output of the logic box goes into a register box, and the register's output loops back to the other input of the logic box. The register also has a clock input.

Figure 1.4.1. A combinational block, and one common kind of sequential block, a register in a feedback path. A register with no feedback is also sequential. The drawings are sketches: the boxes have no behavior to check.

Everything in Chapters 1 to 3 is combinational. The tables of the last two sections fully describe such a circuit, because for every setting of the inputs there is exactly one output. A sequential circuit cannot be described by a table of its inputs alone, since the same inputs can give different outputs at different times, depending on the memory.

A module is the unit in which SystemVerilog describes hardware.

Definition 1.4.3(module and port).

A module is a named block of hardware description. Its ports are the input and output connections by which the block is joined to the rest of a design. Everything between the keywords module and endmodule says what is inside the block.

This is a first look. Section 2.5 returns to modules and signals in more depth.

Here is a module with all three dashboard indicators from section 1.3.

dashboard_controller.sv
// All three dashboard indicators in one module. Each output has its own assign.
module dashboard_controller(
input logic DriverDoorOpen, PassengerDoorOpen,
input logic LightsOn, HighBeamOn,
input logic DriverBeltIn, PassengerBeltIn, Passenger,
output logic DoorAjar, HighBeamIndicator, SeatBeltLight
);
logic passenger_unbelted;
assign DoorAjar = DriverDoorOpen | PassengerDoorOpen;
assign HighBeamIndicator = LightsOn & HighBeamOn;
assign passenger_unbelted = Passenger & ~PassengerBeltIn;
assign SeatBeltLight = ~DriverBeltIn | passenger_unbelted;
endmodule

Read it from the top. The word module is followed by the module's name and then the port list in parentheses. Each port is declared with its direction, input or output, and its type, logic. A type and direction can be shared by several names, as in input logic DriverDoorOpen, PassengerDoorOpen. Ports are separated by commas, and there is no comma after the last one.

After the port list comes the body. The line logic passenger_unbelted; declares a signal that lives inside the module. It is an internal signal, drawn as a wire between gates, and not a port. Each assign line then connects an output or a wire to an expression: the left side always equals the right side. The operators are ~ for NOT, & for AND and | for OR, and they group as in Boolean notation, with NOT first, AND next and OR last. Parentheses are free, and this book uses them where they help the eye.

Every output has exactly one assign. This matches the three expressions of section 1.3: DriverDoorOpen+PassengerDoorOpen\mathrm{DriverDoorOpen} + \mathrm{PassengerDoorOpen} for the door light, LightsOn⋅HighBeamOn\mathrm{LightsOn} \cdot \mathrm{HighBeamOn} for the high beam, and DriverBeltIn‾+\overline{\mathrm{DriverBeltIn}} + Passenger⋅PassengerBeltIn‾\mathrm{Passenger} \cdot \overline{\mathrm{PassengerBeltIn}} for the seat belt light. The seat belt expression is split in two lines so that the AND of the passenger signals has a name.

The same circuit can be written in several styles. Take the seat belt light of section 1.3, now with a name on each wire that carries a signal other than an input or the output.

Schematic ch01/seatbelt-lightDBIPBIPSBLno_driver_beltno_passenger_beltpassenger_unbelted

A logic schematic with inputs DBI, PBI and P and output SBL.

NOT gate n1: input DBI; output n1.

NOT gate n2: input PBI; output n2.

AND gate a1: inputs n2 and P; output n3.

OR gate o1: inputs n1 and n3; output SBL.

It agrees with the function logic:ch01/seatbelt-light#SBL.

Description: Input DBI goes through a NOT gate whose output wire is named no_driver_belt, which goes to an OR gate. Inputs PBI and P go to an AND gate, with PBI inverted by a NOT gate whose output wire is named no_passenger_belt. The AND gate output is named passenger_unbelted and goes to the OR gate. The OR output is SBL.

Figure 1.4.2. The seat belt light with its internal wires named. The names are the ones used in the modules below.

The first style puts the whole expression in one assign.

seat_belt_expr.sv
// The seat belt light in one continuous assignment, written with operators.
module seat_belt_expr(input logic DBI, PBI, P, output logic SBL);
assign SBL = ~DBI | (P & ~PBI);
endmodule

The second style gives every wire of the drawing its own signal, declared with logic and assigned once.

seat_belt_wires.sv
// The seat belt light with one named signal per wire of the drawing.
module seat_belt_wires(input logic DBI, PBI, P, output logic SBL);
logic no_driver_belt, no_passenger_belt, passenger_unbelted;
assign no_driver_belt = ~DBI;
assign no_passenger_belt = ~PBI;
assign passenger_unbelted = P & no_passenger_belt;
assign SBL = no_driver_belt | passenger_unbelted;
endmodule

The third style uses gate primitives, which are the gates built into the language: not, and, or, nand, nor, xor and xnor, all in lowercase. A primitive is written as the gate, a name for this instance of it (the language lets you leave the name out, but the book always gives one), and then a list of connections in which the output comes first and the inputs follow.

seat_belt_gates.sv
// The seat belt light built from gate primitives: each line is one gate, and the output comes first.
module seat_belt_gates(input logic DBI, PBI, P, output logic SBL);
logic no_driver_belt, no_passenger_belt, passenger_unbelted;
not inv_driver(no_driver_belt, DBI);
not inv_passenger(no_passenger_belt, PBI);
and both(passenger_unbelted, P, no_passenger_belt);
or either(SBL, no_driver_belt, passenger_unbelted);
endmodule

Each line of the third module is one gate of the drawing: two not gates, an and and an or. A gate with more than two inputs just takes a longer list. All three modules were run on all eight input settings and agree with the seat belt table of section 1.3, so they are the same circuit written three ways. The Section 2 exercise that builds the seat belt light asks for either built-in operators only or built-in gates only, not a mixture of the two. (fact ch01/s02-no-mix) That is a limit set by the exercise, to give practice in each style. The language itself lets a module use both.

A module that compiles is not yet a module that works. The book's tools compared each module above with its table, which is how you should test your own, and Chapter 3 shows how to write the test. Every module you write deserves one.

These are real files, and the text under each one is what one of the book's checking tools printed for it. The tool you use may word its messages differently.

A comma after the last port:

bad_trailing_comma.sv
// Does not compile: the port list ends with a comma.
module bad_trailing_comma(
input logic DDO, PDO,
output logic DA,
);
assign DA = DDO | PDO;
endmodule
Does not compile. Slang reports an error (misplaced-trailing-separator) and stops; the output is shown below the listing.
hdl/ch01/s04/ex/bad_trailing_comma.sv:4:18: error: misplaced trailing ',' [-Wmisplaced-trailing-separator]
  output logic DA,
                 ^
Output of slang for bad_trailing_comma

A gate primitive written in capitals:

bad_primitive_case.sv
// Does not compile: gate primitives are written in lowercase.
module bad_primitive_case(input logic DDO, PDO, output logic DA);
OR door_gate(DA, DDO, PDO);
endmodule
Does not compile. Slang reports an error and stops; the output is shown below the listing.
hdl/ch01/s04/ex/bad_primitive_case.sv:2:62: warning: undriven output port 'DA' [-Wundriven-port]
module bad_primitive_case(input logic DDO, PDO, output logic DA);
                                                             ^~
hdl/ch01/s04/ex/bad_primitive_case.sv:3:3: error: unknown module 'OR'
  OR door_gate(DA, DDO, PDO);
  ^~
Output of slang for bad_primitive_case

The same signal assigned twice:

bad_two_drivers.sv
// Does not compile: two assignments drive the same signal.
module bad_two_drivers(input logic DDO, PDO, output logic DA);
assign DA = DDO;
assign DA = PDO;
endmodule
Does not compile. Slang reports an error (multiple-cont-assigns) and stops; the output is shown below the listing.
hdl/ch01/s04/ex/bad_two_drivers.sv:4:10: error: cannot have multiple continuous assignments to variable 'DA' [-Wmultiple-cont-assigns]
  assign DA = PDO;
         ^~
hdl/ch01/s04/ex/bad_two_drivers.sv:3:10: note: also assigned here
  assign DA = DDO;
         ^~
Output of slang for bad_two_drivers

The message calls DA a variable, which is the language's name for a logic signal like this one, not a stored value. Section 2.5 explains the term.

A primitive with its output last. The syntax is legal, so slang reports only warnings, and a flow that looks only at errors would let it through. That makes this the easiest mistake to miss:

bad_output_last.sv
// Wrong: the output comes last. The tool only warns.
module bad_output_last(input logic DDO, PDO, output logic DA);
or door_gate(DDO, PDO, DA);
endmodule
Compiles, but is rejected. This file compiles and slang only prints a warning (undriven-port). The book treats a warning as a failure, so the checks reject it; the warning is shown below the listing.
hdl/ch01/s04/ex/bad_output_last.sv:2:36: warning: unused port signal 'DDO' [-Wunused-port]
module bad_output_last(input logic DDO, PDO, output logic DA);
                                   ^~~
hdl/ch01/s04/ex/bad_output_last.sv:2:59: warning: undriven output port 'DA' [-Wundriven-port]
module bad_output_last(input logic DDO, PDO, output logic DA);
                                                          ^~
hdl/ch01/s04/ex/bad_output_last.sv:3:16: warning: input net port 'DDO' coerced to 'inout' [-Wport-coercion]
  or door_gate(DDO, PDO, DA);
               ^~~
hdl/ch01/s04/ex/bad_output_last.sv:2:36: note: declared here
module bad_output_last(input logic DDO, PDO, output logic DA);
                                   ^~~
Output of slang for bad_output_last

In the last module the gate's first connection is DDO, so the gate drives the input DDO and the output DA has no driver. The warnings say exactly that, and the circuit does not do what its author meant.

How CSE 369 writes this

In CSE 369 we write gate primitives in lowercase with the output first, and declare every signal as logic. A module that is tested has a test file of its own, named after the module with _tb added. The port list of a module is written without a trailing comma. This book also writes module names in lowercase with underscores, such as dashboard_controller. The lecture slides use names like DashboardController too, and both are legal.

Common pitfall

  • Ending a port list with a comma before the closing parenthesis.
  • Writing a gate primitive in capital letters, such as OR. The primitives are lowercase.
  • Listing a primitive's inputs first and its output last. The output comes first.
  • Assigning the same signal in two places. Each signal needs one driver, so combine the conditions in one expression.
  • Assuming that a module that compiles is correct. Compiling checks syntax, not whether the logic matches your table.
Take the quiz (5 questions)

Which circuit is combinational?

Which line correctly writes a NOT gate with input a and output y as a gate primitive?

What does assign SBL = ~DBI | (P & ~PBI); describe?

How many NOT gates does the seat belt circuit in this section contain?

A module has this port list: module m(input logic a, b, output logic y,);. What is wrong?

Exercise 1.4.1

Which of these are combinational and which are sequential? (a) The majority circuit of three votes. (b) A lamp that changes between on and off each time a button is pressed. (c) A one-bit adder. (d) A door lock that stays unlocked after a correct code is entered, until a lock button is pressed.

Hint
Ask whether the output is decided by the inputs now, or whether the circuit has to remember something that happened earlier.
Solution

(a) and (c) are combinational: each output is fixed by the current inputs. (b) is sequential. Pressing the button must flip the lamp from whichever state it was in, so the circuit has to remember whether the lamp is on. (d) is sequential too, since staying unlocked after the code is gone means remembering that the code was entered.

Tags: Warm-upSequential logic

Exercise 1.4.2

Write a module door_ajar for the door warning light. Its inputs are DDO and PDO and its output is DA. Use one assign and no gate primitives.

Hint
The light is on when either door is open. The operator for "either" is |.
Solution

The expression is the OR of the two inputs.

door_ajar.sv
// Exercise solution: the door warning light in one assign.
module door_ajar(input logic DDO, PDO, output logic DA);
assign DA = DDO | PDO;
endmodule

The book's tools ran this module on all four settings of its inputs and compared it with the table of the door warning light.

Tags: Warm-upSystemVerilog

Exercise 1.4.3

Here is the chime circuit of section 1.3 written with gate primitives. Rewrite it as one module with a single assign and no gate primitives.

chime_gates.sv
// Exercise: the chime circuit written with gate primitives.
module chime_gates(input logic KeyIn, DoorOpen, LightsOn, output logic Chime);
logic key_out, door_warning, lights_warning;
not inv_key(key_out, KeyIn);
and door_term(door_warning, KeyIn, DoorOpen);
and lights_term(lights_warning, key_out, LightsOn);
or any_warning(Chime, door_warning, lights_warning);
endmodule
Hint
Name the output of each gate in your head. The or takes the outputs of the two and gates. Then put the pieces together in one expression.
Solution

The not gate gives KeyIn‾\overline{\mathrm{KeyIn}} and the two and gates give KeyIn⋅DoorOpen\mathrm{KeyIn} \cdot \mathrm{DoorOpen} and KeyIn‾⋅LightsOn\overline{\mathrm{KeyIn}} \cdot \mathrm{LightsOn}. The or adds them. Written as one expression:

chime_expr.sv
// Exercise solution: the same chime circuit in one assign.
module chime_expr(input logic KeyIn, DoorOpen, LightsOn, output logic Chime);
assign Chime = (KeyIn & DoorOpen) | (~KeyIn & LightsOn);
endmodule

Both modules were checked against the same table, so they are the same circuit.

Tags: StandardSystemVerilogGates and schematics

Exercise 1.4.4

This module compiles, and it has a bug. Compare it with the seat belt table of section 1.3 and find the rows where it disagrees.

seat_belt_bug.sv
// Exercise: this module compiles. Does it match the seat belt table?
module seat_belt_bug(input logic DBI, PBI, P, output logic SBL);
assign SBL = ~DBI | (P | ~PBI);
endmodule
Compiles, but behaves wrongly. The file compiles and runs, but the design does not do what its testbench expects; the failing check is shown below the listing.
Hint
The first term is correct. Work out the second term for each row where DBI is 1, since the first term is already 1 for every row where DBI is 0.
Solution

When DBI is 0 the light is on in the table, and in the module, because of the first term. So only the four rows with DBI equal to 1 can differ. There the module computes P+PBI‾P + \overline{\mathrm{PBI}}, and the table wants P⋅PBI‾P \cdot \overline{\mathrm{PBI}}. These differ when P and the inverted PBI are different. That is the row with P equal to 0 and PBI equal to 0, where the module turns the light on with nobody in the seat, and the row with P equal to 1 and PBI equal to 1, where it turns the light on for a passenger who is belted. The book's tools found exactly these two rows when they ran the module against the table: DBI, PBI, P equal to 1, 0, 0 and 1, 1, 1. The bug is an OR where an AND was needed.

Tags: StandardSystemVerilogTruth tables

Exercise 1.4.5

Write a module majority_gates for the three-input majority function of section 1.2, with inputs A, B and C and output Out, using only gate primitives. Use the fact that Out is 1 when any pair of inputs is 1.

Hint
There are three pairs. Each pair needs an AND gate, and one OR gate with three inputs combines them.
Solution

Majority is 1 when A and B are both 1, or B and C are, or A and C are. Each pair gets an and gate and a named wire. A single or gate takes all three wires, since a primitive can have any number of inputs.

majority_gates.sv
// Exercise solution: majority of three inputs from gate primitives.
module majority_gates(input logic A, B, C, output logic Out);
logic a_and_b, b_and_c, a_and_c;
and first_pair(a_and_b, A, B);
and second_pair(b_and_c, B, C);
and third_pair(a_and_c, A, C);
or any_pair(Out, a_and_b, b_and_c, a_and_c);
endmodule

All eight input rows were run against the majority table.

Tags: ChallengeSystemVerilogGates and schematics