Digital Design Introduction

Synchronous Sequential Logic

Tarik Graba

2022-2024

Synchronous Logic

Now that we know that computations propagates in combinational operators, and they are not instantaneous.

How to reliably have a sequence of computation where we reuse the same combinational operator?

Imagine that we want to compute the sum of a sequence of numbers by using a single adder. Each number has to be presented at the input of the adder, we have to maintain the inputs stable as long as the result is not valid at the output. Once the output is valid, we have to store the result before new computations starts propagating.


 
 

When we present new values at the inputs of a combinational bloc, we have to wait for the inputs to be steady, then we have to wait for the propagation delay to have a valid output.

Also, this output is only valid as long as the input values do not change. If one of the inputs value change, we have to wait again for the output to be steady.

NOTE: If input value change, the output can will not change instantaneously. The minimum time for the output change after inputs change is called contamination time. Nevertheless, the output is not valid until the propagation is complete.


 
 

One first thing that we want to ensure, is that the inputs values do not change until the propagation is complete.

We will use a memorizing element to capture the inputs and guarantee that they do not change before the end of the propagation.

We will thus, sample the inputs at the same moment and wait for a time longer than the longest propagation time before sampling new values.

Here we can see that we have valid output values, but we do not know exactly when if we just observe the output.


 
 

What we can do here, is to sample the output value when we sample new inputs. Thus, we have the guarantee that the sampled output will not change until the next computation phase.


 
 

Finally, as we need to sample inputs and outputs at the same moment we will use the same global signal to guarantee that sampling occurs in a synchronous manner.

We have obtained a complete synchronous operator where the inputs and outputs are synchronized and where the outputs can also be easily used as the inputs of other combinational blocks.

This global signal is called a clock (often named clk). Generally this signal has to be periodic square wave and the sampling happens at each rising (or falling) edge (when it goes from 0 to 1). We will see later why, when we will study the construction of flip-flops.

D Flip-Flop

The memorizing element ;-)

D flip-flop
clk D Q
\uparrow 0 0
\uparrow 1 1
\downarrow x Q ^{-1}
0 x Q ^{-1}
1 x Q ^{-1}

The D Flip-Flop is the main memorizing element for synchronous logic.

For each rising edge of the clock, the value of it’s input D if captured, otherwise, the value of the output is memorized and do not change.

Discreet time

 

All the outputs of flip-flops are synchronous and can only change at a clock edges.

Thus, the output of a flip-flop is always a delayed version of its input. Also, all changes that could appear on the inputs between two consecutive clock edges will be filtered.

With synchronous logic, changes can only happen at discrete times that correspond to the clock edges.

How to build D-FF with CMOS logic


Memory

To maintain a logical level, we can use a couple of inverters connected in loop.

Looped inverters

This will form a bi-stable element with two stable states. The looped inverters ensure that the electrical level at the output is maintained to a correct value.

Using CMOS technology, this is the smallest memorizing element and is the base of what is called an sram memory point.

It is not a practical to use that design because there are no inputs to control or change the value of its output. But, the important thing to keep in mind here is that we have a combinational loop with the identity boolean open loop function (we could have used a single buffer instead of two inverters).

Next, we will see how we can construct a controllable memorizing element.


The D Latch

In principle, it is similar to having a looped multiplexor.

Looped MUX
ena D Q
0 x Q ^{-1} closed
1 0 0 transparent
1 1 1 transparent
D Latch symbol

The D Latch has two states:

A D Latch is also called a level sensitive latch.

NOTE that this construction is purely theoretical, if we go don to the CMOS transistor structure, a D Latch is generally built differently.


The D Flip-Flop

The edge sensitive memorizing element.

 
 

We can construct a Flip-Flop with two level sensitive latches. The first one is called the Master while the second is the Slave. The two latches are connected to complementary control signals:

This behaviour allows capturing the value that we have at the input at the moment when the clock goes from low to high. We have built an edge-sensitive memorizing element.

NOTE that, here also, this construction is purely theoretical, and optimized, transistor level structures are used in real designs.

Registers

Register

A register is a bank of D-FF in parallel that share the same clock input. It can be represented with the same symbol as a D-FF, exception the fact that inputs and outputs are multi-bit buses.

In the previous figure we show a 4-bit register.

Note that often, we will use the term register even for D-FF (1-bit register).

Timings of D Flip-Flops

D-FF Timings

For a D flip-flop to behave correctly the input must be stable around the rising edge of the clock. Otherwise, the sampled value may be incorrect.

Thus, there is a temporal window around the rising edge of the clock that is defined:

Additionally, t{co} (clock-to-output) represents the delay between the rising edge of the clock and a valid output. It can be seen as the propagation delay of the D-FF. By design, t_{co} is larger than t_h, which allows connecting the output of a D-FF directly to the input of another D-FF.

Global Timings

Critical Path in synchronous logic

 

Minimal clock period:

T \geq T_{min} = t_{co} + t_{crit} + t_{su}

Or maximum frequency:

F \leq F_{max} = \frac{1}{t_{co} + t_{crit} + t_{su}}

In the context of synchronous logic, the critical path is the longest combinational path between two synchronisation points (registers or flip-flops) in all the system.

This critical path has a direct impact on the maximum frequency of the system’s clock. Thus, the complexity of the combinational logic will have an impact on the maximum working frequency of a synchronous system.

NOTE Here we have assumed that the clock arrives simultaneously to all the registers of a circuit. In a real design, some variation can appear, due to the propagation of the clock signal, between the moments when the clock reaches each register. This delay variation is called clock skew and should be considered for accurate timing validation.

SystemVerilog description

module dff( input clk,
            input d,
            output logic q);

always_ff @(posedge clk)
    q <= d;

endmodule
module reg4( input clk,
            input [3:0] d,
            output logic[3:0] q);

always_ff @(posedge clk)
    q <= d;

endmodule

To describe synchronous logic we use a process declared with the always_ff keyword. The process is executed when ever we have a rising edge of the clock (posedge keyword).

@(xxxx) represents what is called the sensitivity list of the process.

Note also that the assignment operator (<=) is different from what we have seen for combinational logic. This is called non blocking assignment operator. It guarantees that, when simulating SystemVerilog code, the left side of the assignments do not change until all the process sensitive to the same event (the clock rising edge, for example) are executed.

Example of synchronous designs

Shift Registers

module shift_reg(
            input  clk,
            input  i,
            output q);

logic q0,q1,q2,q3;

always_ff @(posedge clk)
begin
   q0 <= i;
   q1 <= q0;
   q2 <= q1;
   q3 <= q2;
end

assign q = q3;

endmodule
 
 

In a shift register, flip-flops are chained such as the output of each flip-flop is connected directly to the input of the following flip-flop.

The output of the shift-register (the output of the final flip-flop) is a delayed version of the input. The total delay is proportional to the number of flip-flops.

Note also that each flip-flop output represent a different delayed version of the input.

Due to the usage of the delayed assignment operator, these assignments:

begin
   q0 <= i;
   q1 <= q0;
   q2 <= q1;
   q3 <= q2;
end

are executed in parallel.

Thus, the order of the assignment is not important ans it is equivalent to write:

begin
   q3 <= q2;
   q2 <= q1;
   q1 <= q0;
   q0 <= i;
end

To describe in a compact manner, we could have used the concatenation operator as shown in the following example:

module shift_reg( input  clk,
                  input  i,
                  output q );

logic [3:0] q_r;

always_ff @(posedge clk)
   q_r <= {q_r[2:0],i};

assign q = q_r[3];

endmodule

D-FF with enable signal

module dff( input  clk,
            input  ena,
            input  d,
            output logic q);

always_ff @(posedge clk)
if(ena)
    q <= d;

endmodule
D-FF with enable

We are here combining combinational logic and a synchronous flip-flop. The intermediary signal between the multiplexor and the flip-flop is implicit in the code. Another descriptions could be:

module dff( input  clk,
            input  ena,
            input  d,
            output logic q);

wire q_next;

assign q_next = ena? d : q;

always_ff @(posedge clk)
    q <= q_next;

endmodule

Or using a process for the combinational logic:

module dff( input  clk,
            input  ena,
            input  d,
            output logic q);

logic q_next;

always_comb
if(ena)
    q_next = d;
else
    q_next = q;

always_ff @(posedge clk)
    q <= q_next;

endmodule

Note that for combinational logic, the if...else statement must be complete we must explicitly express all cases, whereas, when just expressing synchronous logic, if a case is missing, it means that the signal keeps its value (memorize) which is legal.

always_ff @(posedge clk)
if(ena)
    q <= d;
// else
//  q <= q; // implicit

Counters

Example of a 4-bit counter:

module dff( input  clk,
            output logic[3:0] q);

always_ff @(posedge clk)
   q <= q + 1;

endmodule
4-bit counter

This is modulo 16 counter. It will count through all the values between 0 and 15.

Here also we are combining combinational logic with registers.

Note that:

In other words, the next value of the register (q+1) will replace its actual value (q) at the rising edge of the clock.

Another way to express the same behaviour would be:

module dff( input  clk,
            output logic[3:0] q);

wire [3:0] q_next;

assign q_next = q + 1;

always_ff @(posedge clk)
   q <= q_next;

endmodule

Note also that with this counter we do not control the initial value of the register at power up. Indeed, when powering up a flip-flop we can not predict its value. We need an additional mechanism to force its initial state.

Initial value/Reset

How to force the initial value of a register?


Synchronous Reset

module dff( input clk,
            input rst,
            input [3:0] d,
            output logic[3:0] q);

always_ff @(posedge clk)
if(rst)
    q <= '0;
else
    q <= d;

endmodule
synchronous reset

A synchronous reset allows forcing the state of a register. It is called synchronous because its effect will only appear at rising edge after it is active.

It can be seen as an additional combinational function that forces the next value of the register input to its initial value. Nevertheless, it is possible that this reset is implemented at the transistor level for optimized implementations.

Another way to represent this is:

module dff( input clk,
            input rst,
            input [3:0] d,
            output logic[3:0] q);

wire [3:0] q_next;

assign q_next = rst? '0 : d;

always_ff @(posedge clk)
    q <= q_next;

endmodule

Notes:

For example:

module dff( input clk,
            input nrst,
            input [3:0] d,
            output logic[3:0] q);

always_ff @(posedge clk)
if(!nrst)
    q <= '0;
else
    q <= d;

endmodule

Asynchronous Reset

module dff( input clk,
            input nrst,
            input [3:0] d,
            output logic[3:0] q);

always_ff @(posedge clk or negedge nrst)
if(!nrst)
    q <= '0;
else
    q <= d;

endmodule
Symbol of registers with reset

Contrary to the synchronous reset, an asynchronous reset will have an immediate effect. The way to express this in SystemVerilog, is to add a second event to the sensitivity list of the process. In the previous example, we have an active low reset signal, thus, if it goes from 1 to 0, the process is triggered and the register is initialized.

Warning registers have a unique clock signal, thus, even if there is a second event in the sensitivity list, the second signal can not be interpreted as clock.

It is even clearer with the following example of an active high asynchronous reset:

module dff( input clk,
            input rst,
            input [3:0] d,
            output logic[3:0] q);

always_ff @(posedge clk or posedge rst)
if(rst) begin
    // asynchronous reset
    q <= '0;
end
else begin
   // synchronous behaviour
    q <= d;
end

endmodule

Notes

Exercises

D-FF with reset and enable

  1. Draw the schematic
  2. Write the SystemVerilog description and test it using digital-JS

Counter with synchronous (then asynchronous) reset

Up-Down counter

Edge detector

Combine the edge detector with the counter (Homework)

Moving average filter

  1. We receive continuously, and synchronously (one each clock cycle), a series of 8-bit samples for which we want to compute the average of the four last values.
  1. Suppose that the output of this filter will be stored in a register,
  1. Propose an optimized version of the filter with less than three adders.

Back to the index