Interview guides · Celestial AI
Celestial AI Software Engineer Interview Guide
Celestial AI Software Engineer interview guide: reported rounds, UVM, Python/Tcl EDA scripting and domain questions, plus a 7-day prep plan.
The 60-second brief
Celestial AI builds the Photonic Fabric, an optical interconnect technology for moving data between chips in AI systems. Candidates describe the problem it targets as the Memory Wall: memory bandwidth and latency that fail to keep pace with compute. Reported products and parts include optical chiplets and Optical Multi-chip Interconnect Bridges (OMIB). The Software Engineer title is reported to sit in verification with UVM and SystemVerilog, CAD and EDA automation with Python, Tcl and SKILL, or SoC integration. In those tracks the software checks chip designs before fabrication, automates the analog/mixed-signal and digital design flows, and runs the firmware and tooling around the hardware.
This guide is for engineers interviewing for a Software Engineer role at Celestial AI whose work is closer to chip design than to web services. It covers the three reported rounds, the reported UVM, scripting and domain questions, and how to rehearse each. Candidates describe requirements of Python or Tcl for automation, SystemVerilog and UVM for verification, Cadence or Synopsys tool experience for CAD, and 5-10+ years of relevant experience, with C++, CI/CD and SLURM as extras. Read the job posting first: the same title is reported to map to different tracks.
Candidates report three rounds over roughly 3-5 weeks: a Recruiter Screen, a Technical Phone Screen and an Onsite Interview. The reported onsite is a panel covering technical depth, problem-solving and behavioral fit.
Focus areas
- Semiconductor Packaging Design
- Heterogeneous Integration
- Photonic Fabric
Overview
Candidates describe this as a hardware-adjacent software loop. The title Software Engineer is reported to map to Verification (UVM), CAD or EDA automation, or SoC integration, so your first decision is which track the posting actually is. A verification candidate needs SystemVerilog and UVM fluency. A CAD candidate needs Python, Tcl or SKILL and some command of the Cadence and Synopsys flows. Preparing the wrong track wastes the days that matter most.
The reported questions fall into three groups. Verification and UVM covers phasing and reset synchronisation, a scoreboard for out-of-order transactions, the difference between new and create, functional coverage definition, and a SystemVerilog constraint for an aligned address inside a range. Scripting and automation covers triaging thousands of log files, extracting netlist information from a Virtuoso database with Python, checking that release files match a version control tag, and exception handling in a Tcl synthesis flow. Domain knowledge covers Network-on-Chip versus a bus, verification of 2.5D packaging, clock domain crossing and SerDes simulation parameters. Candidates also report that for UVM and CAD roles the technical stage leans on discussing tools and methodology rather than generic algorithm puzzles, and that whiteboard scripting can come up.
Beyond the question list, candidates report that the onsite includes deep dives into past projects, especially how you handled technical roadblocks in chip design or verification, and that how you built something matters more than the theory behind it. Expect to talk through the ASIC flow from RTL to GDSII, setup and hold timing, static timing analysis and the difference between functional and timing ECOs. Reported tips also name PCIe, CXL, UCIe and IEEE 1687 (IJTAG) as standards worth knowing, depending on the role.
Plan your time accordingly. Spend the early days on the concepts you must explain without hesitation (UVM phases, factory, coverage, CDC), then write real code for the scripting questions instead of only reading about them, and finish with a full rehearsal that mixes a project story, a whiteboard script and a domain question.
The Celestial AI interview process
Candidates report three stages, in this order. The descriptions below are what candidates say about each stage, not a published process.
Recruiter Screen
Candidates describe the Recruiter Screen as a first conversation about your background and your interest in semiconductors and AI hardware. Use it to confirm which track the posting is (verification, CAD or EDA automation, SoC integration), since the same Software Engineer title is reported to cover several. Reported tips also say most engineering roles are onsite in Santa Clara or Irvine, so be ready to talk about location.
What to demonstrate
- How your experience lines up with the verification, CAD or SoC track of the posting
- Your interest in semiconductor and AI hardware, and how you explain the problem the company works on
How to prepare
- Write a short summary of your chip-adjacent work: the tools (UVM, SystemVerilog, Python, Tcl, SKILL, Cadence or Synopsys), the scale, and your own contribution.
- Prepare a brief plain-language explanation of the Memory Wall and why data centers are moving from single SoCs toward systems of chips linked by optical interconnect.
- Ask which track the role belongs to and what the next technical stage covers, and have your location constraints ready.
Technical Phone Screen
The Technical Phone Screen is reported as a conversation with a hiring manager or a senior engineer that focuses on the methodologies for the role: UVM concepts for verification roles, scripting for CAD roles. Candidates report that for UVM and CAD tracks this stage is more a discussion of tools and methods than a generic algorithm test. Prepare to explain concepts out loud and to write short snippets of SystemVerilog, Python or Tcl.
What to demonstrate
- Working command of UVM and SystemVerilog fundamentals for verification roles
- Scripting ability in Python or Tcl for CAD and automation roles
- How clearly you explain a methodology you have actually used
How to prepare
- For verification, rehearse UVM phasing and reset synchronisation, the factory (create versus new), a scoreboard for out-of-order transactions, functional coverage definition, and the aligned-address constraint.
- For CAD, write the log-triage script, the release-versus-tag check and a Tcl wrapper with real error handling, then say out loud why each design choice was made.
- Practise saying what you did in your own past flows, in first person, since the reported emphasis is on how you implemented something.
Onsite Interview
The Onsite Interview is reported as a panel covering technical domain knowledge, problem-solving and behavioral fit, held virtually or in person at the Santa Clara or Irvine offices. Candidates report deep dives into past projects, including how you handled technical roadblocks in chip design or verification, and say whiteboard-style scripting can come up. Treat it as a mix of domain explanation, a project walkthrough and a cross-team conversation.
What to demonstrate
- Technical domain knowledge across UVM, EDA flows and the ASIC design flow
- Problem-solving under physical and flow constraints such as timing, power, area and thermal limits
- Behavioral fit, including working with analog designers, packaging engineers and physicists
How to prepare
- Prepare two project walkthroughs with a concrete bug or roadblock, the debugging sequence you followed, and the result.
- Rehearse domain explanations: Network-on-Chip versus a bus, 2.5D versus monolithic verification, clock domain crossing, SerDes simulation parameters, setup and hold, functional versus timing ECOs.
- Practise writing a script on paper or a shared screen with attention to structure and error handling, not only syntax.
- Prepare one story about explaining a software idea to a hardware or physics colleague.
Where candidates lose points
Treating the role as pure software and describing the hardware context vaguely
Be able to explain the Memory Wall, the move from single SoCs to systems of chips, and why optical interconnect is relevant, in plain language, plus where your own work sits in the RTL-to-GDSII flow.
Defining UVM terms without tying them to a testbench you built
For phasing, the factory (create versus new), coverage and scoreboards, give the definition and then one concrete example: how you held a sequence until reset deasserted, or why factory overrides needed create.
Writing a log-triage script that only handles the happy path
For the thousands-of-logs question, handle unreadable or truncated files, a pass/fail rule, an unclassified error bucket, and a summary count by error type. Say how you would scale it across a directory of that size.
Catching Tcl or Python errors and carrying on
Name the failure classes, retry only transient ones, always log the stage and command, clean up in finally, and exit non-zero on fatal errors. Never leave an empty catch or a bare except.
Preparing for the wrong track because the title is just Software Engineer
Read the posting before day one and decide whether it is verification, CAD or EDA automation, or SoC integration. Weight the plan toward that track and keep the other two at overview level.
Questions & practice
"Write a SystemVerilog constraint to generate a random address that is 64-bit aligned and within a specific me
Medium · Verification & UVM
"Write a SystemVerilog constraint to generate a random address that is 64-bit aligned and within a specific memory range."
Approach
- Confirm the meaning first: 64-bit aligned means 8-byte aligned, so the low three address bits are zero (a 64-byte cache-line alignment would be addr[5:0] == 0).
- Write it: class addr_item; rand bit [63:0] addr; bit [63:0] lo, hi; constraint c_align { addr[2:0] == 3'b000; } constraint c_range { addr inside {[lo:hi]}; } endclass
- Prefer the bit slice to addr % 8 == 0: both are correct, but the slice is easier for the solver.
- Handle edge cases: if lo > hi, or the range contains no aligned address, randomize() fails and returns 0, so I always check it with an if or assert. For a burst of N beats, constrain addr + N*8 - 1 <= hi, using a wider intermediate so the sum cannot wrap at 2^64.
- Bias toward corners with dist (lo, hi minus 8, region boundaries) and keep the range constraint hard while the bias is soft, so tests can override it.
Follow-up
- How would you constrain the address to avoid a 4 KB boundary crossing within a burst?
- How do you make the range configurable per test without editing the class?
- What happens to this constraint when an inline randomize() with { } adds a conflicting rule?
"Explain the concept of 'Clock Domain Crossing' (CDC) and how you verify it."
Medium · Domain & System Knowledge
Approach
- Define the risk: a flop sampling a signal from an asynchronous clock can violate setup or hold and go metastable; a synchronizer reduces the failure probability, and MTBF grows exponentially with the resolution time given to it.
- Match the structure to the signal: a two-flop (or deeper) synchronizer for single-bit level signals; a toggle synchronizer or pulse stretch for pulses going from fast to slow domains; Gray-coded pointers in an async FIFO, or a req/ack handshake, for multi-bit data, because bits synchronized independently can arrive in different cycles.
- Resets: assert asynchronously, deassert synchronously through a reset synchronizer in each domain.
- Verify in layers: a structural CDC tool for missing synchronizers, multi-bit and reconvergence issues; SVA in simulation such as $onehot0(gray ^ $past(gray)) to prove a Gray pointer changes at most one bit per cycle, and stability of data while a handshake is pending.
- Note that plain RTL simulation never models metastability, so I add metastability injection (random extra-cycle delay at synchronizer outputs) to expose logic that depends on exact crossing latency.
Follow-up
- Why is a Gray-coded pointer safe to synchronize but a binary counter is not?
- What is reconvergence, and why can two correctly synchronized signals still cause a bug?
- How do you waive a CDC tool violation safely?
Collapse a redelivered event batch into per-aggregate high-water marks
Easy · hashing · at-least-once · aggregation
You drain a batch of up to 5,000,000 events, each (aggregate_id BIGINT, aggregate_version INT, event_type, payload). The log guarantees order within one aggregate only; the batch merges 64 partitions, and a relay failover has redelivered a range, so an older version for an aggregate can appear after a newer one. Given a map of last_applied_version per aggregate, produce the events worth applying, at most one per (aggregate_id, version), plus the count discarded. Target O(n) time. State the memory for 2,000,000 distinct aggregates and what you do when it does not fit.
Approach
- One pass, one hash map from aggregate_id to the highest version kept, and a discard counter. An event whose version is at or below last_applied_version for its aggregate is dropped without further work, which is the whole reason the event carries its version rather than a delta. O(n) expected time, O(d) space in distinct aggregates.
- Keep the maximum, never the last occurrence. The redelivered range means the final appearance of an aggregate in the batch can be an older version than one seen earlier in the same batch, so last-wins applies stale state over newer state and the projection regresses with no error anywhere.
- Cost the memory instead of calling it large: an 8-byte key plus a 4-byte version is 12 bytes of payload, and an open-addressed table held at a 0.7 load factor costs roughly 17 bytes per entry before per-slot metadata, so 2,000,000 aggregates is tens of megabytes in a native layout and several times that in a runtime that boxes both key and value.
- If the distinct set exceeds memory, partition on hash(aggregate_id) mod P and reduce each partition independently. Every event for one aggregate hashes to the same partition, so the per-partition result is exact and the merge is concatenation rather than a second reduction.
Follow-up
- The payload is a patch rather than a snapshot, so applying only the highest version loses the intermediate changes. What changes in your reduction?
- How do you detect that version 7 arrived while version 6 was never delivered, and what should the consumer do about the gap?
Hold a per-tenant active cap against concurrent creates
Hard · write skew · isolation levels · constraints
A tenant on the standard plan may hold at most 50 resources with status='active'. The create handler runs SELECT count(*) FROM resource WHERE tenant_id = $1 AND status = 'active', compares to 50, then inserts. Two creates arrive 3 ms apart on different instances and the tenant lands at 51. Name the anomaly, say whether PostgreSQL 16 READ COMMITTED or REPEATABLE READ prevents it and why, then give an implementation that holds the cap at READ COMMITTED with the exact statements. Finally, say what changes when the cap is 'at most one running export per tenant' on job_run.
Approach
- Name it: write skew. The two transactions read an overlapping set and write disjoint rows, so there is no row-level conflict for the engine to detect and each commit is individually legal.
- Rule out the levels precisely. READ COMMITTED takes a fresh snapshot per statement and takes no lock on the counted rows, so both see 49. PostgreSQL's REPEATABLE READ is snapshot isolation: it removes non-repeatable reads and phantoms within the snapshot but still admits write skew, because the anomaly is not a re-read of a changed row, it is a read of a set that a concurrent transaction invalidates. Only SERIALIZABLE closes it, by tracking the read dependency and aborting one transaction with SQLSTATE 40001 — a guarantee that exists only if the application re-runs the whole transaction from the read.
- Convert the set predicate into a single-row conflict: keep tenant.active_resource_count and run UPDATE tenant SET active_resource_count = active_resource_count + 1 WHERE tenant_id = $1 AND active_resource_count < 50 in the same transaction as the INSERT. Zero affected rows is the cap, returned as 409. The row lock serialises the decision at any isolation level, and contention is bounded to one tenant's row — which is also the fair-scheduling unit, unlike a global counter that would convoy every tenant behind one row.
- State the cost you just took on: a counter is a second source of truth that can drift, so every path that changes status must adjust it inside the same transaction, and a periodic reconciliation has to exist, with resource_revision as the authority for what the count should have been.
Follow-up
- A resource moves from archived back to active. Which statements change, and what breaks if the counter update and the status change land in different transactions?
- The cap becomes plan-dependent and a plan can change mid-month. Where does the number 50 live, and who reads it?
Keep soft-deleted accounts from blocking re-registration
Easy · soft delete · unique constraints · engine differences
app_user holds user_id, tenant_id, email CITEXT, password_hash (NULL for SSO principals), email_verified_at, auth_version, status ('invited','active','suspended','deactivated'), created_at, updated_at, deleted_at. Two live accounts for one address inside a tenant must be impossible, but an address freed by a soft delete must be reusable, and the same tenant may delete and re-register it repeatedly. Write the uniqueness DDL for PostgreSQL 16, then the equivalent for MySQL 8 where partial indexes do not exist, and say what each permits once three deleted rows already hold that address.
Approach
- Start from what is actually unique: not (tenant_id, email), but (tenant_id, email) among live rows. PostgreSQL says that directly — CREATE UNIQUE INDEX app_user_live_email ON app_user (tenant_id, email) WHERE deleted_at IS NULL. A full constraint over the same two columns burns the address permanently the first time someone deletes an account.
- Keep case-insensitivity in the type or the index, never in the application: CITEXT as given, or UNIQUE (tenant_id, lower(email)) as an expression index where the extension is unavailable. A case-sensitive unique column is exactly how two accounts for one human appear.
- For MySQL 8 the predicate has to move inside the key: add a discriminator column that is a constant 0 while the row is live and is set to user_id on delete, with UNIQUE (tenant_id, email, deleted_marker). Live rows share the constant and still collide; deleted rows differ from each other and stop colliding.
- State the NULL variant and its dependency: leaving the marker NULL for deleted rows also works, because a unique index treats NULLs as distinct — true in MySQL, and true in PostgreSQL only under the default NULLS DISTINCT, which PostgreSQL 15 lets you reverse. Check the polarity against the three existing deleted rows: constant-on-live is what preserves the collision you want, and reversing it silently admits duplicate live accounts.
Follow-up
- A deleted account re-registers with the same address the next day. Do the old resource rows follow the new user_id, and how does the API keep the two principals apart?
- How do you honour an erasure request while resource_revision.actor_user_id still references this table?
"Explain the UVM phasing mechanism. How do you synchronize sequences with the DUT reset?"
Medium · Verification & UVM
Approach
- Lay out the phase order: build, connect, end_of_elaboration, start_of_simulation (function phases, zero simulation time), then run_phase, then extract, check, report, final. build_phase runs top-down so a parent configures children before they exist; connect_phase runs bottom-up.
- Explain that run_phase executes in parallel with the twelve runtime sub-phases (pre_reset, reset, post_reset, pre_configure through post_shutdown), and that a task phase ends only when every raised objection has been dropped.
- For reset sync, give two patterns: start the stimulus sequence in main_phase so it cannot begin before reset_phase finishes, or in run_phase have the sequence or driver wait on the virtual interface until reset deasserts before calling get_next_item.
- Cover mid-test reset: the driver forks its item loop against a reset watcher, kills the loop when reset asserts, completes any in-flight item_done, drives idle values, and the sequencer's stop_sequences() clears pending requests before traffic restarts.
- Name the trade-off: phase jumping back to reset_phase works but leaves components that are unaware of it in inconsistent state, so I keep reset handling local to driver, monitor and scoreboard.
Follow-up
- What happens if no component raises an objection in run_phase?
- How does the scoreboard avoid flagging transactions that were killed by a mid-test reset?
- Why are the runtime sub-phases rarely used in practice compared with run_phase alone?
"How would you model a scoreboard for a protocol that supports out-of-order transactions?"
Medium · Verification & UVM
Approach
- Wire the scoreboard with two analysis imps (using the uvm_analysis_imp_decl macros) or two analysis FIFOs: one fed by a predictor from the input monitor, one by the output monitor.
- Store expected transactions in an associative array keyed by the protocol's transaction tag (for AXI, the ID), each entry a queue, because ordering is only guaranteed within one ID; an actual response pops the head of its ID's queue and compares field by field.
- If the protocol has no tag, key on address or search the pending queue with compare(); state that lookup is O(n) per response and becomes expensive with many outstanding transactions.
- Handle an actual that arrives before its expected entry by parking it in a pending-actual map, and flag unknown IDs immediately rather than at the end.
- In check_phase, report any expected entries left over as dropped transactions; on reset, flush both maps; optionally age entries to catch responses that never return.
Follow-up
- How would you add a latency or timeout check per outstanding transaction?
- What changes if the same ID can be reused before the previous transaction with that ID completes?
- Where should the reference model live: inside the scoreboard or in a separate predictor component?
"What is the difference between new and create in UVM, and why does it matter?"
Medium · Verification & UVM
Approach
- new() is the plain SystemVerilog constructor: it always builds exactly the class named in the code.
- type_id::create("name", parent) asks the UVM factory for the object, so a test can substitute a derived class with set_type_override_by_type or an instance override by hierarchical path, without editing the environment.
- That only works if the class is registered with uvm_component_utils or uvm_object_utils, and the override is registered before create is called, typically in the test's build_phase before the env is built.
- Give a concrete use: a test overrides the base transaction with an error-injecting subclass, or swaps the driver for one with a different timing model, and every agent picks it up.
- Mention that factory.print() (or uvm_factory::get().print()) shows active overrides, which is the first check when an override silently does nothing.
Follow-up
- Why does an instance override fail if the component was built with new()?
- What constraints apply to the override class relative to the original type?
- How does create interact with the component hierarchy name used by uvm_config_db?
"How do you approach functional coverage definition to ensure you aren't over-covering or under-covering?"
Medium · Verification & UVM
Approach
- Start from the verification plan: each spec feature maps to a covergroup or coverpoint, so every bin traces back to a requirement and nothing is covered just because it is easy to count.
- Use explicit bins for meaningful values (boundaries, min, max, wrap points) instead of automatic bins on wide fields, which default to auto_bin_max of 64 and create noise; mark impossible values with ignore_bins and forbidden ones with illegal_bins.
- Cross only where the interaction matters; crosses multiply (8 x 8 x 16 bins gives 1024 cross bins), so restrict them with binsof/intersect or ignore_bins.
- Sample on completed, valid transactions from the monitor rather than on every clock, otherwise idle cycles inflate hit counts.
- Treat coverage without checking as a hole: a hit bin proves the stimulus happened, not that the result was right, so pair each coverage item with a scoreboard or assertion check, and review holes with the designers.
Follow-up
- Coverage is stuck at a few percent short of the goal: how do you decide between new constraints, directed tests or waiving bins?
- How do functional coverage and code coverage complement each other at sign-off?
- How would you merge coverage across thousands of regression seeds?
"Given a directory of thousands of log files, write a script to identify which tests failed and categorize the
Medium · Scripting & Automation (Python/Tcl)
"Given a directory of thousands of log files, write a script to identify which tests failed and categorize the error types."
Approach
- Walk the tree with pathlib.Path.rglob('*.log') and read each file line by line (open with errors='replace'), so a multi-GB log never sits in memory; handle .gz with gzip.open.
- Decide pass or fail from the terminal status: UVM_ERROR and UVM_FATAL counts in the report summary, plus a missing summary line, which marks a crash, kill or timeout rather than a pass.
- Categorize with an ordered list of compiled regexes (UVM_FATAL, UVM_ERROR with its [ID], assertion failure, timeout, license checkout failure, segfault), then normalize the message by stripping numbers, hex values, paths and timestamps so the same failure groups into one signature.
- Aggregate with collections.Counter and a defaultdict(list) of example tests per signature; write a CSV or JSON sorted by count and exit non-zero when anything failed so CI can gate on it.
- Scale: work is O(total bytes); parallelize per file with concurrent.futures.ProcessPoolExecutor, and record unreadable, empty or truncated files as their own category rather than silently skipping them.
Follow-up
- A test has several different errors: which one is its primary signature and why?
- How would you track whether a failure signature is new compared with last night's regression?
- How do you keep the regex list maintainable as new error types appear?
"How would you use Python to interact with a Cadence Virtuoso database to extract netlist information?"
Medium · Scripting & Automation (Python/Tcl)
Approach
- State that Virtuoso's native API is SKILL: dbOpenCellViewByType(lib cell view nil "r") opens a cellview read-only, and cv~>instances, cv~>nets and net~>instTerms give connectivity.
- Lay out three Python routes: drive a running Virtuoso session through a SKILL bridge (for example the open-source skillbridge package), read the OpenAccess database directly through OA scripting bindings, or run the netlister in batch mode and parse the CDL or Spectre output.
- Name the trade-off: a live bridge sees exactly what the designer sees but needs a session and license; the netlist route is simple and scriptable in CI but loses schematic-only information such as properties that are not netlisted.
- Handle hierarchy explicitly: descend through instance masters with a switch list and stop list of view names, expand iterated instances such as I0<3:0>, and account for global nets and inherited connections.
- Open read-only, close every cellview (dbClose) to avoid leaking memory and locks, and emit structured JSON so downstream checks do not reparse text.
Follow-up
- How would you compare the extracted connectivity between the schematic and the layout view?
- What breaks when two designers have the same cellview open while your script runs?
- How would you test the extraction script itself?
"Describe how you would implement a check to ensure that all design files in a release match the version contr
Medium · Scripting & Automation (Python/Tcl)
"Describe how you would implement a check to ensure that all design files in a release match the version control tag."
Approach
- Build the expected manifest from the tag: git ls-tree -r <tag> lists every path with its mode and blob SHA; use <tag>^{commit} so annotated and lightweight tags behave the same, and git verify-tag if tags are signed.
- Hash each released file with git hash-object, which computes the same blob ID (SHA-1 of a header plus content), and compare path by path, reporting three lists: missing, extra and modified.
- Watch the traps: hash-object applies clean filters for a given path, so line-ending conversion or Git LFS pointers produce different hashes (compare LFS files against their pointer's sha256 oid instead), and mode bits and symlinks need their own comparison.
- Exclude generated outputs through an explicit allowlist, run the check as a release gate that exits non-zero, and store the manifest with the release. Cost is O(total bytes) and parallelizes per file.
- For a design-data system other than Git, apply the same pattern against the checksums that system records for the labelled revision.
Follow-up
- How do you prove the release was not modified after the check passed?
- Large binary layout files make hashing slow: what would you cache, and how do you keep the cache trustworthy?
- How would you handle a file that is legitimately patched after tagging?
"How does a Network-on-Chip (NoC) interconnect differ from a standard bus architecture?"
Medium · Domain & System Knowledge
Approach
- A bus is a shared medium: masters arbitrate, typically one transfer is in flight on the shared path, and each added master or slave adds wire load and arbitration delay, so bandwidth does not scale. A crossbar allows parallel transfers but its area grows with masters times slaves.
- A NoC is packet-switched: routers and point-to-point links in a topology such as a mesh or ring, packets split into flits, with many transfers in flight at once, so aggregate bandwidth grows with the network.
- Name the mechanisms: routing (dimension-order XY routing is deadlock-free on a mesh), wormhole switching with credit-based flow control, and virtual channels for deadlock avoidance and traffic classes.
- Give the costs: per-hop latency, router area and power, ordering across paths, and protocol-level deadlock where request and response traffic share resources.
- Close with the verification angle: end-to-end scoreboards across many routes, ordering checks, deadlock and livelock stress with heavy random traffic, and credit-counter assertions.
Follow-up
- Why does XY routing avoid deadlock on a mesh, and what breaks it on a torus?
- How do you guarantee ordering for one master's transactions when packets can take different paths?
- How would you measure NoC latency and throughput in simulation?
"What are the specific verification challenges associated with 2.5D packaging compared to monolithic SoCs?"
Medium · Domain & System Knowledge
Approach
- Frame the difference: several dies, possibly on different process nodes or from different vendors, connected through an interposer, so the system is never one netlist in one simulator.
- Die-to-die links (UCIe is a common standard) need their own verification: PHY bring-up and link training, lane repair or redundancy, and protocol layers on top, across dies whose RTL may only be available as models.
- Cross-die integration: clock and reset crossings between dies, independent power-up sequencing, and connectivity checks that die bumps, interposer routing and package pins match, which a single-die LVS does not cover.
- Test access: dies must be known good before assembly and tested again through the stack, using standard test-access architectures such as IEEE 1687 (IJTAG).
- Capacity and physical effects: full-system simulation is too large, so I mix models, emulation and targeted tests; interposer signal integrity, thermal and power-delivery effects need sign-off checks that cover more than one die.
Follow-up
- How would you build a system-level testbench when one die exists only as a behavioral model?
- What would you check to confirm that die bump assignments match the interposer netlist?
- How does known-good-die testing change what you verify before tape-out?
"In a high-speed SerDes design, what are the critical parameters you need to track during simulation?"
Medium · Domain & System Knowledge
Approach
- Start with the eye: eye height and eye width at the target bit error rate, read from bathtub curves, since a bit-by-bit simulation cannot reach rates like 1e-12; statistical or IBIS-AMI channel simulation extrapolates there.
- Break jitter down: random jitter (Gaussian, unbounded) and deterministic jitter (ISI, duty-cycle distortion, periodic, crosstalk). With the dual-Dirac model, total jitter at 1e-12 is about DJ + 14.07 x RJ rms.
- Track the channel: insertion loss at Nyquist (half the baud rate), return loss and crosstalk (NEXT and FEXT), and the equalization that fights them: TX FFE taps, RX CTLE peaking and DFE taps.
- Track the receiver loop: CDR lock behavior, jitter tolerance and jitter transfer, plus supply noise and PVT corners; for PAM4, three eyes, level linearity and SNR.
- Track link-level behavior in digital simulation: link training, lane deskew and PRBS generator and checker error counts (PRBS7, PRBS31).
Follow-up
- Why does DFE help with ISI but not with crosstalk or random jitter?
- How would you choose between a statistical and a time-domain simulation for a sign-off decision?
- What changes in your checklist when the link moves from NRZ to PAM4?
One log partition stops advancing while the others drain
Easy · poison message · head-of-line blocking · consumer lag
Search results for a subset of tenants are hours stale; the rest are current. The projection consumer reports lag of zero on 15 of 16 partitions and 400,000 on one. Its error rate is flat and its CPU is idle. outbox_event has no pending rows older than a second, so the relay has published everything it holds. Identify the mechanism, give the ordered checks, and state what you do in the first ten minutes versus what you change permanently.
Approach
- Read the lag distribution first. A slow consumer lags everywhere; zero on fifteen partitions and 400,000 on one is not throughput. Idle CPU on the stuck partition means the consumer is not advancing its offset at all, which points at one message it cannot get past rather than at a rate problem.
- Exonerate the producer before touching the consumer. No pending outbox rows older than a second means the relay published, so the event exists in the log. This separates never sent from sent and never applied, which are different code paths and usually different owners.
- Read the message at the stuck offset and the handler's log lines for its event_id. A flat error rate with no progress has two explanations and you must distinguish them: the handler is throwing and the retry loop is swallowing it, or the handler is blocking on something and never returning. Idle CPU with no error lines favours the second.
- Mitigate before diagnosing further. Move the offending event to a dead-letter store and commit the offset past it. Adding consumers does nothing here, because a partition is consumed by exactly one member of the group, and the blast radius is every aggregate hashed to that partition, not only the aggregate that produced the bad event.
Follow-up
- The dead-lettered event carried aggregate_version 7 and the projection had applied 6. What must the replay do differently if 8 and 9 landed in the meantime?
- How do you show staleness to the user while the partition is behind, given the API already returns the projection's watermark?
Suggested practice checklist
The plan starts by fixing your track, then works through the reported UVM, scripting and domain questions, and ends with a mixed rehearsal of the onsite.
Day 1 — Fix your track and the hardware context
- Read the posting and decide whether it is verification, CAD or EDA automation, or SoC integration, then list the tools it names.
- Sort the reported topics into UVM and SystemVerilog, scripting and automation, and domain knowledge, and mark your weakest group.
- Write a short plain-language explanation of the Memory Wall and of why optical interconnect between chips matters.
Deliverable: A one-page track decision, a topic map with the weakest group marked, and a written Memory Wall explanation.
Day 2 — UVM phasing and the factory
- Write the UVM phase order from build through to report and say what each phase is for, then describe how a sequence waits for reset to finish before driving stimulus.
- Write a short explanation of new versus create, with the factory override case that only works with create.
- Say both answers out loud without notes.
Deliverable: A written phase list with a reset-synchronisation paragraph, and a spoken new-versus-create answer.
Day 3 — Scoreboards, coverage and constraints
- Sketch a scoreboard for out-of-order transactions: an associative array keyed by transaction ID, matching on response, and how you flag leftover entries at the end of the test.
- Write the SystemVerilog constraint for an address aligned to 64 bits inside a given range, then test the edge cases of the range ends.
- Outline a coverage plan for a small block: what the coverpoints and crosses are, and how you would spot over- and under-coverage.
Deliverable: A scoreboard sketch, a working constraint with the edge cases listed, and a one-page coverage plan.
Day 4 — Log triage and release integrity scripts
- Write a script that walks a directory of log files, decides which tests failed and groups failures by error signature, including an unclassified bucket.
- Write the release check that compares every design file in a release against the version control tag and reports mismatches, missing files and untracked extras.
- Test both on messy inputs: empty files, odd encodings, a huge log.
Deliverable: Two working scripts with a short list of the edge cases each one handles.
Day 5 — Tcl error handling and tool interaction
- Build a small Tcl wrapper that runs three stages with catch or try, retries a transient failure, and stops with a clear summary and a non-zero exit on a fatal one.
- Write down the ways to get netlist data out of a Virtuoso database with Python (exported netlist parsing versus the tool's own scripting interface) with the trade-off of each.
- Practise a short spoken story about a flow that failed silently and what you changed.
Deliverable: A running Tcl wrapper, a two-option comparison for the netlist extraction, and a spoken failure story.
Day 6 — Domain and system knowledge
- Write a comparison of a Network-on-Chip and a shared bus covering topology, concurrency, scaling and arbitration.
- List the extra verification concerns for 2.5D packaging compared with a monolithic SoC, and explain clock domain crossing with the checks you would use to verify it.
- List the parameters you would track when simulating a high-speed SerDes link, and review PCIe, CXL, UCIe and IJTAG at the level of what each is for.
Deliverable: Four short written answers (NoC versus bus, 2.5D verification, CDC, SerDes parameters) and a one-line purpose for each standard.
Day 7 — Mixed onsite rehearsal
- Give a project walkthrough covering one real bug or roadblock, your debugging sequence and the result, then answer follow-ups from a friend.
- Write a small script on paper or a shared screen with a spoken explanation of structure and error handling.
- Rehearse one cross-team story, then one pass through the weakest domain answers from the earlier days.
Deliverable: A recorded or witnessed rehearsal with a list of the answers that still stumble.
Project deep dives and cross-team stories
Candidates report that the onsite includes behavioral fit and deep dives into past projects, especially technical roadblocks in chip design or verification. They also describe the work as cross-functional, with software engineers talking to analog designers, packaging engineers and physicists. Prepare stories that show what you decided, how you debugged, and how you explained software to hardware colleagues.
- Describe a difficult bug you found using constrained random stimulus and how you debugged it.
- How do you handle reuse of verification components from block level to SoC level?
- Tell me about a time you explained a software constraint or tool limit to an analog designer, packaging engineer or physicist.
- Describe a bottleneck you found in a design or verification flow and the change you made, including how you measured the result.
- Describe a project where the schedule or the ambiguity was high and you had to move without detailed guidance.
"How do you handle exception handling in Tcl when automating a critical synthesis flow?"
Medium · Scripting & Automation (Python/Tcl)
Approach
- Start with the primitives. In Tcl, catch runs a script and returns 0 for success, 1 for error, and other codes for return, break or continue. Capture both the result and the options dictionary: catch {run_stage $name} msg opts. The options hold -errorinfo, -errorcode and -code, so you can log the full stack and decide what to do. On Tcl 8.6 you can also use try with on error and trap handlers and a finally clause.
- Use try / trap / finally for structured handling. Trap on specific error codes so a missing optional file, a license checkout failure and a tool error each get their own response, and put cleanup in finally so locks, temp directories and open channels are released whether the stage passed or failed. On older Tcl without try, use catch and rethrow with return -options $opts $msg so the original error is not lost.
- Separate recoverable from fatal failures. A transient problem such as a license checkout can be retried a bounded number of times with a pause. A real synthesis error should stop the flow, write the stage name, the failing command, -errorinfo and the log path, and exit with a non-zero status so the scheduler or CI marks the run as failed. Raise your own errors with error or throw and a structured error code so callers can trap on them.
- Never leave an empty catch. It hides typos and real tool failures, and the flow keeps going on bad data. Also check whether the tool commands you rely on raise Tcl errors or only print messages to a log, and add an explicit check of the log or return status after the stage if they only print.
- If asked for a real example, tell a short story with a named failure: what the flow did silently before, what you changed (per-stage catch, error codes, a failure summary at the end, a resumable stage list), and how you confirmed it, for instance by injecting a failure in a test run and checking the exit status and log. Stay in first person and say what you decided yourself.
Follow-up
- What is the difference between a catch result of 1 and 2, and why does it matter inside a proc that calls return?
- How would you make the flow resumable so a failure at one stage does not force rerunning the completed stages?
- How do you stop a catch around a whole stage from hiding a misspelled command name?
Argue against a design, lose, and commit anyway
Medium · disagreement · service boundaries · decision records
Describe a design you argued against and lost. State the failure you predicted as a named mechanism, not a feeling about complexity: two services that would need one transaction, a projection with no rebuild path, a write path with no idempotency key. Say what evidence you brought, what the decision maker weighed instead, and what you did after the decision was made: what you instrumented, what you wrote down, and whether the prediction came true. Five minutes.
Approach
- State the prediction in falsifiable form up front: the mechanism, the condition that triggers it, and the observable outcome. A prediction that cannot be checked also cannot be credited to you later.
- Show the evidence you had at the time and label each piece honestly as measured, analogous, or intuition. Keeping the intuition is fine; disguising it as data is the thing that erodes your standing in the next argument.
- Represent the opposing case at full strength, including the constraint you did not control: a fixed date, a team boundary, or the fact that the decision was cheap to reverse and yours was not.
- Make disagree-and-commit concrete. Name the artefact you left behind so the prediction could be settled without you: the alert and its threshold, the counter on the dashboard, the decision note that recorded the trade-off and the condition that would revisit it.
Follow-up
- What threshold on that alert would have proved you right, and did anyone ever look at it?
- If the same proposal arrived tomorrow with the same deadline, would you argue it the same way?
Tell callers you do not own that their integration breaks
Medium · deprecation · compatibility · stakeholders
A field in a write endpoint's response must change shape. You own the endpoint; you do not own the four internal callers or the outbound webhook consumers who read it. Describe a deprecation you were responsible for: what you shipped first, how you established who was actually reading the field, the window you gave and what set its length, what you did about the consumer who never moved, and how you decided removal was safe. Name the signal you used, not the announcement you sent.
Approach
- Establish the reader set empirically rather than from a wiki of owners: per-field usage counters keyed by principal, or access logs attributed to a consumer. State the blind spot of whichever you pick, since a consumer that reads the field only on a monthly job will not appear in a week of logs.
- Ship additive first. Populate the new field alongside the old one so no reader is forced to move, which is also what keeps a rolling deploy safe, because old and new instances answer the same requests at the same time and a rollback must still find the old shape present.
- Set the window from the slowest legitimate consumer's release cadence, not from your calendar, and decide separately what to do for a consumer with no release process at all, such as an external webhook endpoint you can only email.
- Convert silence into evidence before you rely on it: a short, low-traffic removal window that makes a still-dependent consumer fail visibly and loudly while you are watching, rather than at three in the morning after you have moved on.
Follow-up
- How would you detect a consumer that reads the field only during a monthly export?
- One caller refuses to move and has a commercial relationship behind it. What changes in your plan and what does not?