Two Verilog Interview Questions Worked Through: Open-Drain Outputs and a Nonblocking Delay Puzzle
Interview questions for verification and design roles tend to probe two things: whether you understand the hardware you are modelling, and whether you understand the simulator that is modelling it. This post takes one question of each kind, both originally published here as short notes in 2015 and 2016, and works them through properly. The second one is worth reading even if you have seen it before, because the original answer on this blog was wrong, and the way it was wrong is the whole lesson.
Question 1: What is an open-drain output, and why would you use one?
An open-drain output (open-collector in bipolar logic) is a digital output stage that contains a single pull-down transistor and nothing else. It can do exactly two things: pull the line to ground, or let go of it. It can never drive the line high. A resistor to the supply, the pull-up, provides the high level whenever nobody is pulling low.
| Transistor | Output | Who sets the level |
|---|---|---|
| Off | High impedance | The pull-up resistor pulls the line to VDD |
| On | Low | The transistor sinks current to ground |
That asymmetry is the feature, not a limitation. Because no device ever drives high, any number of open-drain outputs can share one wire without contention. If several push-pull outputs shared a wire, one driving high and another driving low would short the supply to ground through the two transistors. With open-drain outputs the worst case is several devices pulling low at once, which is harmless.
Wired-AND and wired-OR
A shared open-drain line is low if any device pulls it low, and high only when all devices release it. Read with active-high logic, that is an AND of the released states, so the arrangement is called wired-AND. Read with active-low logic, where "asserted" means low, the line is asserted if any device asserts, which is a wired-OR. Same wire, two names, depending on which polarity you call true.
Verilog models this directly with the wand and wor net types, or by resolving multiple assign statements that each drive either 0 or z. Here is a three-device line, run with Icarus Verilog:
// Three open-drain drivers sharing one line
module wired_and_demo;
reg d1, d2, d3; // 0 = pull low, 1 = release
wand line; // wired-AND net: low if any driver is low
pullup (line); // the external pull-up resistor
assign line = d1 ? 1'bz : 1'b0; // open-drain: drive 0 or release
assign line = d2 ? 1'bz : 1'b0;
assign line = d3 ? 1'bz : 1'b0;
initial begin
$monitor("t=%0t d1=%b d2=%b d3=%b line=%b", $time, d1, d2, d3, line);
{d1,d2,d3} = 3'b111; #10; // everyone released: pull-up wins
{d1,d2,d3} = 3'b110; #10; // one device pulls low
{d1,d2,d3} = 3'b000; #10; // all pull low: no contention
{d1,d2,d3} = 3'b111; #10;
$finish;
end
endmodule
t=0 d1=1 d2=1 d3=1 line=1
t=10 d1=1 d2=1 d3=0 line=0
t=20 d1=0 d2=0 d3=0 line=0
t=30 d1=1 d2=1 d3=1 line=1
The pullup primitive is the part most answers forget. Without it, a wand net that every driver has released resolves to z, not 1, which is exactly what happens on a real board when someone forgets to fit the resistor.
Where it shows up
| Application | Why open-drain |
|---|---|
I2C SDA and SCL | Multi-controller bus. Clock stretching works because any target can hold SCL low. |
| Shared interrupt request lines | Several peripherals assert one IRQ; the CPU polls to find which. |
Fault and alert pins (SMBus ALERT#, SPI fault lines) | Any device can flag an error without an arbiter. |
| Reset distribution | Several reset sources, including a supervisor chip and a debug probe, can each hold the system in reset. |
| Level shifting | An open-drain line can be pulled up to a different voltage than the driver's supply. |
The follow-up questions
Interviewers rarely stop at the definition. Expect these:
- Why is I2C slow? The pull-up resistor charges the bus capacitance on every rising edge, an RC ramp, while falling edges are driven by a transistor and are fast. Speed is limited by how small a resistor you can tolerate for power and drive strength. Fast-mode I2C reaches 400 kHz, and the higher speed modes need active pull-up assistance.
- How do you pick the pull-up value? Small enough that the RC rise time meets the bus timing spec, large enough that a device pulling low does not exceed its sink current rating. Vendors publish the arithmetic. For a 3.3 V I2C bus with modest capacitance, values between 1 kΩ and 10 kΩ are typical.
- How would you check this in a testbench? Model the pull-up with
pullupor a weak drive, model each device as driving0orz, and assert that no device ever drives1. A device that drives high on an open-drain bus is a real bug that simulation with plainwirenets will hide as anx.
Question 2: What do clk and a look like?
This one, from an Analog Devices interview, is about the simulator rather than the hardware. Read the code and predict the waveform before scrolling down.
module stratified_event;
reg a, clk;
always
#10 clk = ~clk;
always @(posedge clk) begin
$display($time);
a <= #15 clk;
end
initial begin
clk = 1'b1;
a = 1'b0;
#200 $finish;
end
endmodule
The wrong answer, and why it was attractive
The original post on this blog said: the clock has a period of 20, the first positive edge is at t=10, and a becomes 1 at t=25. It is a tidy story and every number in it is wrong.
Start with the clock. It is initialized to 1, so the first toggle at t=10 takes it to 0. That is a negative edge. The first guaranteed positive edge is at t=20, then 40, 60 and so on. Anyone who says "posedge at 10" has not looked at the initial value.
What actually happens at time zero
The subtle part is the very first time step. Both always blocks and the initial block start at t=0, and the LRM does not define the order in which they run within the active region. Two histories are possible:
- The event control is reached first. The
always @(posedge clk)block starts, reaches its event control, and waits. Then theinitialblock runs and setsclkfromxto1. In Verilog a positive edge is any transition from0,xorzto1, so thexto1change is a posedge. The block wakes at t=0, prints0, samplesclk, which is now1, and schedulesa <= 1for t=15. - The initial block runs first.
clkis already1by the time thealwaysblock reaches its event control, so thexto1edge is missed. The block first wakes at t=20, samples1, andabecomes 1 at t=35.
Icarus Verilog takes the first path. The trace, with a $monitor added, is:
0
t=0 clk=1 a=0
t=10 clk=0 a=0
t=15 clk=0 a=1
20
t=20 clk=1 a=1
t=30 clk=0 a=1
40
t=40 clk=1 a=1
Either way, a rises once and stays high forever. The block only ever samples clk at a positive edge, when clk is by definition 1, so the value assigned is always 1. The #15 is an intra-assignment delay: the right-hand side is evaluated at the edge, and the update is scheduled 15 time units later. Nothing about clk changing in between matters, because the sampled value was already captured.
What the interviewer is actually testing
Three separate concepts hide in eleven lines:
- Edge semantics on 4-state values.
xto1counts as a positive edge. This is why a reset that starts atxcan triggeralways @(posedge rst)logic at time zero, and why good testbenches initialize everything in the same time step, or use$urandomseeds and reset sequences that do not depend on initial-value races. - Time-zero races. The LRM leaves the order of concurrent processes at the same time step undefined. Two simulators can legally give different answers, which is what the two histories above show. An interview answer that says "it depends on the simulator, here is why, and here is how I would remove the race" is stronger than any single waveform.
- Intra-assignment delay with nonblocking assignment.
a <= #15 clksamples now and updates later. Contrast with#15 a <= clk, which delays the whole statement and samplesclkfifteen time units after the edge, at which pointclkis 0. The two forms produce opposite waveforms, and being able to say so instantly is the point of the question.
Removing the race
If this were real testbench code, initialize the clock with a declaration assignment or drive it from a single initial block that owns it, and never sample a clock as data. The cleaned-up version has no ambiguity:
module stratified_event_fixed;
reg clk = 1'b0; // declaration assignment: no x at time zero
reg a = 1'b0;
always #10 clk = ~clk; // first posedge at t=10, unambiguous
always @(posedge clk)
a <= #15 1'b1; // sample a real signal, not the clock
initial #200 $finish;
endmodule
Key takeaways
- Open-drain outputs can only pull low or release. That is what lets many devices share one wire, and why the pull-up resistor is a required part of the design, in silicon and in the testbench model.
- Wired-AND and wired-OR describe the same shared line under opposite polarity conventions. Verilog models them with
wandandwor, or with multiple drivers of0andz. - A positive edge in Verilog includes transitions from
xandzto1. Initial values decide what the first time step looks like. - Processes that start at the same time step run in an undefined order. Any waveform that depends on that order is a race, and a race is a bug.
a <= #15 bsamples now and updates later.#15 a <= bwaits, then samples. Know both.
Verified with Icarus Verilog 13.0. The open-drain example was also linted with Verilator 5.052.
Comments (0)
Leave a Comment