Verilog for Latches, Flip-Flops, and Registers

Code examples for common memory devices

3227

In Verilog, signal assignment statements that begin with the keyword “assign” are known as a continuous assignment statements. Continuous assignment statements model wires in physical circuits that are continuously driven to some value (a ‘0’ or a ‘1’). When the Verilog simulator evaluates the right-hand side of a continuous assignment statement, a new output value can be immediately calculated based on the input signal values, and the calculated value can be assigned to the output signal wire (on the left of the assignment operator).

In some cases, output signal values can only be assigned after the Verilog simulator executes a procedure to check the status of one or more of the input signals. Consider a flip-flop for example. A simple “if-then” procedure must be executed to determine if an active clock edge has just occurred before a new output value can be assigned: “if a rising clock edge has occurred, then assign the output Q the value present on the input D”. Verilog includes procedural assignment statements to check conditions like this prior to making an assignment.

Signals assigned using a procedural assignment must use the “reg” type to identify to the simulator that the output is driven from a procedure (and only procedural assignments can drive reg types). Procedural assignments must be used to infer memory (like flip-flops), but they can also be used to define purely combinational logic. Since procedural assignments are commonly used to define like flip-flops, it is tempting to associate the reg type with a flip-flop output, but this is not the case. The reg type is simply a variable type assigned by a procedure, and a reg-type signal can be physically implemented using memory devices or combinational logic, depending on the Verilog description. Note that when you are writing Verilog to define a circuit that will be synthesized, all signals are either wires or regs – there are no other relevant types.

Procedural assignment statements appear inside an “always” block, between the keywords begin and end. Statements inside an always block are executed in the order they are written, as with any procedural programming language (like C). But unlike C, Verilog is a “concurrent” language. Verilog defines circuits and signals that are inherently parallel. Each defined component is simulated when its inputs change, rather than as a part of a sequential algorithm expressed in C. So, even though Verilog procedural assignment statements can behave like statements in non-concurrent, procedural language like C, you should always keep in mind you defining circuits.

Two different assignment operators are available for use in an always block – the blocking operator (an equal sign “=”) that looks and acts like an assignment operator in a language like C, and the non-blocking operator (“<=”). The blocking operator causes an assignment statement to update the output signal immediately, and so blocks any other processing from taking place until the assignment is complete. The non-blocking operator schedules the output to be updated after the entire always block has completed. As a result, when you use the blocking operator, the sequence order of instructions can matter; when you use the non-blocking operator, the instructions can appear in any order and the results will be the same. In practice, if an always block is used to define purely combinational logic, then the blocking operator can be used. But if an always block defines sequential logic (or a mix of combinational and sequential logic), then the non-blocking operator should be used (more on this topic later).

Continuous assignment statements are only simulated when an input signal (on the right hand side of the assignment operator) changes state, so the simulator doesn’t waste time simulating signals that aren’t changing. Likewise, a procedural assignment statement is only simulated when certain signals change state, but it is up to the designer to provide a list of the signals. Procedural assignment statements include a “sensitivity list”, which is simply a list of all signals that can cause the output to change state. In the case of a flip-flop, these signals include the clock and reset, but not the data signal. Why? Note the sensitivity list can also contain a procedural call, like “posedge(sig)” which will return true if a positive edge just occurred, and thereby cause the always block to execute.

Inside an always block, signal assignments do not use the assign keyword – rather, the output signal appears on its own on the left-hand side of the assignment operator. In addition to simple assignment statements, four other procedural assignment statements can be used, including if, case, while and repeat statements. Each of these statements allow conditions to be tested before an assignment is made (for example, to check for a rising edge on clock). In fact, checking conditions is key to inferring flip-flops in a behavioral Verilog description: if an assignment is to be made only if certain conditions are met, then the signal must remain unchanged if the conditions are not met – and this infers memory. Consider for example the statement “assign to Q the value on D if a clock edge just occurred”. It’s clear what should be done if there is a clock edge – but what if there is no clock edge? What should be done with Q? The idea is Q should remain unchanged, and that requires memory. (Note: If an always block is used to define combinational logic, then all checked conditions must be explicitly accounted for or else latches will be inferred – more on this later).

The example code below illustrates Verilog code for defining various memory devices.

D-Latch. Note the sensitivity list contains the Gate, Reset and Data signals, because changes on any of these signals can cause the output to change state. Also note Rst is checked first - if reset is asserted, the output is driven to ‘0’ and the always block is exited.

always @ (D, G, rst)
begin
   if (rst == 1)
       Q <= 1'b0;	// Q is reset to 0
   else if (G == 1)
       Q <= D;
end

The example code below shows the same D-Latch code, but the sensitivity list signals are replaced by the wild card character “*”. Using the wild card character directs the synthesizer to construct a sensitivity list that includes all signals on the right-hand side of assignment statements that can cause output changes.

always @ (*)
begin
   if (rst == 1)
       Q <= 1'b0;	// Q is reset to 0
   else if (G == 1)
       Q <= D;
end

D-Flip-Flop. Note the use of the “posedge” procedures in the sensitively list. Alternatively, just the signal names could have been used, and then signal levels could be checked with if statements. Also note the check for reset comes before Q is assigned - this defines an “asynchronous reset”.

always @ (posedge(clk), posedge(rst))
begin
   if (rst == 1)
       Q <= 1'b0;	// Q is reset to 0
   else
       Q <= D;
end

D-Flip-Flop with Synchronous Reset. Here, the reset signal is not in the sensitivity list. A reset can only happen after a clock edge, and then only if reset is asserted. This defines a “synchronous reset”.

always @ (posedge(clk))
begin
   if (rst == 1)
       Q <= 1'b0;	// Q is reset to 0
   else
       Q <= D;
end

D-Flip-Flop with input logic. The code below defines a D-flip-flop with an asynchronous reset whose input is driven by combinational logic.

always @ (posedge(clk), posedge(rst))
begin
   if (rst == 1)
       Q <= 1'b0;	// Q is reset to 0
   else
       Q <= (A & B);
end

Register. The code below looks just like the DFF code, but the inputs and outputs are 8-bit busses, and reset assigns all the bits to ‘0’.

reg [7:0] Q

always @ (posedge(clk), posedge(rst))
begin
   if (rst == 1)
       Q <= 8'b00000000;	// Q is reset to 0
   else
       Q <= D;
end

Shift Register. The code below shifts an input bus one position to the left, and fills the vacated 0th bit with a ‘0’. Verilog bus assignments are always “left most to left most”, so the code below assigns DIN(6) to DOUT(7), DIN(5) to DOUT(6), etc. The concatenation operator {} places a 0 into the vacated bit position.

reg [7:0] DOUT

always @ (posedge(clk), posedge(rst))
begin
   if (rst == 1)   DOUT <= 0;
   else
       DOUT <= { DIN[6:0], 1'b0 };
end