To turn behavioural Verilog with a run of @(posedge clock) delay statements into synthesizable RTL, replace the delays with an explicit state machine: each wait becomes a state, and each clock edge moves the machine to the next state. When the waits are all on the rising edge this is a simple one-process FSM. When the behavioural code waits on both edges, the clean synthesizable answer is not a flip-flop that triggers on both edges, which standard cell libraries do not provide, but two single-edge processes that share one sequence counter.
This is part 5 of the series on writing synthesizable Verilog from behavioural constructs. It covers delay statements, a case often asked about in RTL design interviews.
Key takeaways
- Every
@(posedge clock)in a behavioural loop marks a state boundary. Count the waits and you know how many states you need. - Rising-edge-only sequences map directly to a
casestate machine in onealways @(posedge clock or posedge reset)block. - Sequences that mix rising and falling edges need care. The dual-edge sensitivity list in the original example simulates correctly, but most ASIC and FPGA synthesis flows will not map it to hardware.
- A portable fix: let a rising-edge counter own the sequence and let a falling-edge process do the falling-edge work, each driving its own registers.
- Always check start, done, idle behaviour, reset state and the width of the state register.
Why delay statements are not synthesizable as written
A behavioural loop such as forever begin ... @(posedge clock); ... end describes a sequence in time. A synthesis tool has to build hardware that holds its place in that sequence between clock edges, and that place has to be stored in flip-flops. An explicit state register gives it exactly that. Delay statements require careful attention if there are several in a row, because each one is an implied state.
Some synthesis tools have historically accepted several @(posedge clock) statements inside one always block and inferred an "implicit" state machine, but support is limited and not portable, so the explicit form below is the one to write and the one interviewers expect.
Case 1: delays on the rising edge only
If there are only delays on the positive edge of the clock you can implement them with a state machine.
Behavioural
forever
begin
command1;
@(posedge clock);
command2;
@(posedge clock);
command3;
@(posedge clock);
end
Synthesizable
always @(posedge clock or posedge reset)
if (reset) // reset the state machine when reset is high
begin
state <= 0;
end
else
begin
case (state)
0 : begin
command1;
state <= 1;
end
1 : begin
command2;
state <= 2;
end
2 : begin
command3;
state <= 0;
end
endcase
end
The mapping is mechanical. The code before the first wait goes in state 0, the code between the first and second waits goes in state 1, and so on. The last state returns to state 0, which closes the forever loop.
| Behavioural step | Becomes | Runs at |
|---|---|---|
| command1, then wait for rising edge | State 0 | Rising edge 1, 4, 7, ... |
| command2, then wait for rising edge | State 1 | Rising edge 2, 5, 8, ... |
| command3, then wait for rising edge, loop | State 2, next state 0 | Rising edge 3, 6, 9, ... |
A complete, simulated version
The fragment above leaves out declarations. Here is the same machine as a full module, with the commands replaced by a step output so it can be simulated. Note the 2-bit state register and the default branch, which returns the unused code 3 to state 0.
// Example 1: three commands, one per rising edge
module seq3 (
input wire clock,
input wire reset,
output reg [1:0] step // stands in for command1..command3
);
reg [1:0] state; // 2 bits: enough for states 0..2
always @(posedge clock or posedge reset)
if (reset) begin
state <= 2'd0;
step <= 2'd0;
end else begin
case (state)
2'd0: begin step <= 2'd1; state <= 2'd1; end // command1
2'd1: begin step <= 2'd2; state <= 2'd2; end // command2
2'd2: begin step <= 2'd3; state <= 2'd0; end // command3
default: state <= 2'd0; // recover from 3
endcase
end
endmodule
In simulation, step cycles 1, 2, 3, 1, 2, 3 on successive rising edges after reset, which is the behavioural sequence.
Case 2: delays on both clock edges
If both clock edges are present, the original article implemented it with a state machine changing values on both clock edges.
Behavioural
forever
begin
command1;
@(posedge clock);
command2;
@(negedge clock);
command3;
@(posedge clock);
@(posedge clock);
command4;
@(negedge clock);
end
Original dual-edge state machine
always @(posedge clock or negedge clock or posedge reset)
if (reset) // reset the state machine when reset is high
begin
state <= 0;
end
else
begin
case (state)
0 : begin
command1;
state <= 1;
end
1 : if (clock == 1) // wait for the positive edge
begin
command2;
state <= 2;
end
2 : begin // this will definitely begin at the negative edge as state 1 precedes it
command3;
state <= 3;
end
3 : state <= 4; // we arrive at the positive edge of the clock, but need to wait a clock cycle
4 : if (clock == 1) // wait for the positive edge
begin
command4;
state <= 0;
end // we'll get back to state 0 at the negative clock edge, the right time for command1
endcase
end
The logic of this machine is right. It wakes on every edge, and the if (clock == 1) tests make states 1 and 4 wait for a rising edge, so the commands run in the same order as the behavioural code: command1 on a falling edge, command2 on the next rising edge, command3 on the falling edge after that, one full cycle of waiting, command4 on a rising edge, and back to command1 on the falling edge. We checked this order in simulation with Icarus Verilog, against a behavioural reference model.
Correction: why this form usually does not synthesize
The problem is the hardware, not the sequence. always @(posedge clock or negedge clock ...) asks for a flip-flop that captures on both edges of the same clock. Standard cell libraries and FPGA fabrics provide flip-flops that capture on one edge, and mainstream synthesis tools generally reject this sensitivity list or fail to map it. Testing the clock as data (clock == 1) also puts the clock into the logic cone, which timing analysis and clock tree tools handle badly. So the original block is best treated as a simulation model of the behaviour, not as synthesizable RTL.
A synthesizable alternative with single-edge flip-flops
Look at the behavioural sequence edge by edge. It repeats every three clock cycles. The rising edges do command2, nothing, command4. The falling edges do command1, command3, nothing. That suggests a counter that advances on the rising edge and tells both processes where they are in the sequence.
// Example 2, single-edge flops only: a posedge counter owns the
// sequence; posedge work and negedge work each drive their own registers.
module seq_both (
input wire clock,
input wire reset,
output reg [2:0] p_cmd, // command2 and command4 (rising edge)
output reg [2:0] n_cmd // command1 and command3 (falling edge)
);
reg [1:0] cnt; // 3 = waiting to start, then 0, 1, 2, 0, ...
always @(posedge clock or posedge reset)
if (reset) begin
cnt <= 2'd3;
p_cmd <= 3'd0;
end else begin
case (cnt)
2'd0: p_cmd <= 3'd2; // command2
2'd2: p_cmd <= 3'd4; // command4
default: ; // 1: the extra cycle of waiting; 3: start-up
endcase
cnt <= (cnt == 2'd2) ? 2'd0 : cnt + 2'd1; // 3 + 1 wraps to 0
end
always @(negedge clock or posedge reset)
if (reset)
n_cmd <= 3'd0;
else
case (cnt)
2'd0: n_cmd <= 3'd1; // command1 (after command4, or first after reset)
2'd1: n_cmd <= 3'd3; // command3 (half a cycle after command2)
default: ; // 2: idle falling edge
endcase
endmodule
| Edge | cnt seen | Command |
|---|---|---|
| Rising (first after reset) | 3 | none, cnt goes to 0 |
| Falling | 0 | command1 |
| Rising | 0 | command2, cnt goes to 1 |
| Falling | 1 | command3 |
| Rising | 1 | none (the extra cycle of waiting), cnt goes to 2 |
| Falling | 2 | none |
| Rising | 2 | command4, cnt goes to 0 |
| Falling | 0 | command1 again |
Every register here is written by one process and clocked on one edge, so it maps to ordinary flip-flops. The falling-edge process reads cnt, which changed half a cycle earlier, so the half-cycle path from the rising-edge flops to the falling-edge logic must meet timing. That is the real cost of mixing edges, and it is why many teams go one step further and retime the whole sequence onto the rising edge, or run a single-edge machine from a clock at twice the frequency, if the design allows it.
The start-up code 3 means the first command1 lands on the first falling edge after the first rising edge out of reset. If reset is released while the clock is high, the sequence starts half a cycle later. Decide which behaviour you need and set the reset value to match.
As you can see, multiple clock edges require care to implement in synthesizable Verilog.
Checklist for any sequence state machine
In general, commandi refers to a block of commands. It is assumed there is an appropriate clock for the case statement state machines. Care is required in setting appropriate reset states, initialisation, and completion of use of a state machine:
- Is there a signal to tell the state machine to begin?
- Does a done signal go high, signalling the state machine has finished?
- When it is not in operation, does the state machine idle correctly? Does it change signal values shared with other code? Does it set outputs from it to appropriate idling values?
- Is the state machine reset to the idle state by a reset signal?
- Ensure that you initialise all registers.
- Ensure that your state register has the correct bit width. If it is too small, assigning a larger state value will just return it to an earlier state. For example, with a 2-bit register,
state <= 4stores 0.
Common mistakes
- No declared width for state. Declare
reg [2:0] state;or wider for a five-state machine. A 1-bit register can only hold two states, and every larger value is truncated. - No default branch. Without one, an unused state code (after an upset or an X at power-up) can lock the machine. Send unused codes back to idle.
- Driving one register from two processes. In the dual-edge alternative, rising-edge work and falling-edge work must write different registers.
- Using the clock as data. Tests such as
if (clock == 1)inside clocked logic simulate, but they mix clock and data paths in hardware. - Blocking assignments to state. Use non-blocking (
<=) in clocked blocks so the simulation matches the flip-flops.
How interviewers probe this topic
- "Convert this loop with three
@(posedge clk)waits into RTL." They want the state count, the state width and a reset. - "Is
always @(posedge clk or negedge clk)synthesizable?" The expected answer is no for normal libraries, with the reason: there is no general dual-edge flip-flop. - "How would you do it instead?" Two single-edge processes, a retimed single-edge design, or a 2x clock, with the half-cycle timing cost explained.
- "What happens if the state register is too narrow?" The value wraps and the machine jumps to an earlier state.
FAQ
Can I use #10 style delays in synthesizable code?
No. Synthesis tools ignore # delays. Use a counter clocked by a real clock to measure time.
Is @(posedge clock) inside an always block ever synthesizable?
As the single event control at the top of the block, yes, that is the normal flip-flop template. Several of them inside one block form an implicit state machine, which only some tools accept. Write the explicit FSM instead.
Do any devices have dual-edge flip-flops?
Many FPGA and ASIC I/O cells offer double data rate (DDR) registers, and a few libraries contain special cells, but general logic does not. DDR registers are typically built from two single-edge registers and a multiplexer, which is the same idea as the alternative above.
How many bits does my state register need?
For binary encoding, the ceiling of log2 of the number of states. Five states need three bits. One-hot encoding uses one bit per state.


This is a nice post, infact i have been looking for something on similar lines. I will look at the other 4 posts on the same topic. This is awesome man!!
ReplyDelete