Interview guides · Alif Semiconductor
Alif Semiconductor Software Engineer Interview Guide
Alif Semiconductor Software Engineer interview guide: reported rounds, embedded C, bit manipulation, RTOS and SystemVerilog questions, plus a 7-day plan.
The 60-second brief
Alif Semiconductor makes microcontrollers and systems-on-chip, and candidates describe the Software Engineer role as the software side of that silicon: firmware and low-level code for chips aimed at power-efficient edge processing, AI workloads and secure IoT devices. The work reported for the role runs from embedded C and RTOS code through hardware-software co-design, scripting and test automation, with regular contact with hardware design, verification and systems engineering teams. Deliverables candidates describe include peripheral drivers, firmware libraries, memory-footprint reduction and test frameworks that check software across silicon revisions.
This guide is for candidates interviewing for the Software Engineer role at Alif Semiconductor. It covers the three reported rounds, the four question groups candidates report (bit-level embedded C, data structures, hardware design and verification, operating systems and memory), a seven-day plan, and the collaboration stories worth rehearsing.
Candidates report three rounds over roughly 3-5 weeks: a Recruiter Call, Technical Interviews and a Role Discussion. Reported questions range from embedded firmware to digital design and verification, depending on the team.
Focus areas
- UVM (Universal Verification Methodology)
- Bit Manipulation
- SystemVerilog
Overview
The reported questions fall into four groups. The first is bit-level C: toggling, setting and clearing a bit, swapping two numbers without a temporary, printing an integer in binary and reversing a 32-bit value. The second is data structures: a circular doubly linked list, stack operations written from scratch and detecting a loop in a linked list. The third is hardware design and verification: a combinational component such as a decoder or memory block, a SystemVerilog class that returns a shuffled 52-card deck using constraints only, and UVM and SystemVerilog fundamentals. The fourth is operating systems and architecture: MMU paging, multithreading, heap management and linker behaviour in an RTOS, and memory allocation and fragmentation on constrained targets.
Most of these are short pieces of code or short explanations where small details decide the answer: the integer type you choose, whether a shift is well defined, what happens with an empty list or a one-node list, whether a register access needs volatile. Practise them on paper or in a plain editor with no autocomplete, and say your assumptions about word size, memory and signedness before you write the first line. Candidates also describe a tip to avoid guessing: when you are unsure, reason aloud and explain how you would investigate.
The hardware-facing group deserves time even if you see yourself as a firmware engineer. Candidates describe questions varying by team, and the requirements they list name Python for test automation, Verilog or SystemVerilog and UVM as useful extras. Learn enough SystemVerilog to write a constrained-random class, explain what a UVM environment is made of, and sketch a decoder as combinational logic. For the OS group, be able to walk a virtual address through a page table to a physical address, say what a page fault does, and explain how a linker script places code and data into flash and RAM.
The Alif Semiconductor interview process
Candidates report three stages over roughly 3-5 weeks. Which reported questions belong to which stage is not stated, so prepare every question group for the technical stage.
Recruiter Call
Candidates describe an introductory call with a recruiter about your background and the role. Use it to connect your embedded, firmware or hardware-adjacent projects to the skills candidates list for this role: C, microcontroller peripherals, data structures, and optionally RTOS internals, Python scripting, Verilog or SystemVerilog and UVM. Because reported questions vary by team, ask which focus area the position sits in so you know how much weight to give firmware versus verification preparation.
What to demonstrate
- How clearly you describe your own hands-on embedded or hardware-adjacent projects, including academic or personal hardware work
- Whether your experience maps to the listed skills: C, microcontroller peripherals, data structures
- Your reasons for wanting this role
How to prepare
- Write a short walk-through of two projects that touched registers, drivers, an RTOS or a test bench, with your specific contribution in each
- Match your experience line by line to the must-have and nice-to-have skills candidates list, and note any gap you can speak about honestly
- Prepare a question about the team's focus area (firmware, verification, test automation), since the reported questions span all three
Technical Interviews
Candidates describe a series of technical interviews. The reported question types are bit manipulation and register-level C, data structures written from scratch, hardware design and verification (a decoder or memory block, SystemVerilog constraints, UVM) and operating systems topics (MMU paging, RTOS multithreading, heap management, linker behaviour). Candidates also mention a tip to state assumptions about memory and performance before coding and to talk through reasoning when unsure. Work every group aloud, and write code by hand without an IDE.
What to demonstrate
- Correct, readable C written live for bit-level and data structure problems
- Understanding of memory, MMU paging and RTOS concepts that bridge software and hardware
- Digital design, SystemVerilog and UVM basics, for teams whose work includes verification
- Clear reasoning about hardware constraints while solving a problem
How to prepare
- Write set, clear, toggle and check operations for a 32-bit register using unsigned types, then reverse a 32-bit integer both with a loop and with mask-and-shift steps
- Implement a circular doubly linked list with insert and delete, a stack on an array, and Floyd cycle detection, testing empty and one-node cases
- Write a SystemVerilog class that randomizes a 52-card deck with constraints only, and be ready to explain UVM components in your own words
- Explain a virtual-to-physical address translation step by step, then how a linker script places sections in flash and RAM
Role Discussion
Candidates describe a discussion about the role, expectations and salary. Treat it as a two-way conversation about the work the role covers (drivers, memory optimization, test frameworks, code review and documentation as candidates report them) and about what a good first period looks like. Know your own range from your research before you arrive, and have specific questions about the team's silicon, tooling and test practice.
What to demonstrate
- Your understanding of what the role involves day to day
- Alignment between your expectations and the role
- Your communication and collaboration with cross-functional teams, a criterion candidates report
How to prepare
- Reread the responsibilities candidates describe and choose two you have done, with an example for each
- Prepare questions about how software, hardware design and verification teams work together on the team you would join
- Decide your salary range from your own research and practise stating it plainly
Where candidates lose points
Writing bit operations with signed constants, such as 1 << 31 on an int, or clearing a bit with the wrong mask
Use unsigned types (1u << n or a uint32_t constant), toggle with ^=, set with |=, clear with &= ~mask, and check n stays below the register width. Mark hardware register pointers volatile and say why.
Presenting the XOR or add-subtract swap as a finished answer, without noting that it zeroes the value when both operands are the same variable or that signed addition can overflow
Show the XOR version, state the aliasing case (swap(&a, &a)), and say a temporary is usually clearer and no slower on modern compilers. Mention the guard for identical addresses.
Linked list code that only works mid-list: ignoring the single-node case in a circular delete, or storing visited nodes for loop detection
List the cases first: empty, one node, head, tail. Update head when you remove it and set the list to empty when the last node goes. For loop detection use two pointers moving at different speeds, which needs constant extra memory.
Answering the SystemVerilog 52-card deck question with a shuffle loop or with randc, which the question rules out
Declare an array of 52 values, constrain each to the range 0 to 51, and require every pair to differ with nested foreach constraints so the solver produces a permutation. Explain that constraints describe the answer, not a procedure.
Describing MMU paging or RTOS memory in vague terms, such as saying the MMU maps addresses without walking a table or naming what a page fault does
Practise one concrete walk: virtual address split into page-number bits and offset, page table lookup, TLB hit or miss, permission check, then what the fault handler does. For RTOS memory, name stack versus heap, fragmentation and what the linker script decides.
Questions & practice
Tests your proficiency in C programming, bit manipulation, and direct interaction with hardware registers.
Medium · Embedded Systems and Low-Level Programmi
Approach
- Access a memory-mapped register through a volatile pointer to a fixed-width type, e.g. #define CTRL (*(volatile uint32_t *)0x40001000u), so the compiler emits every read and write instead of caching or merging them.
- Build masks from unsigned constants (1u << n, or UINT32_C(1) << n). 1 << 31 on a 32-bit int is undefined behaviour, and so is any shift by the type's width or more.
- Update a multi-bit field with clear-then-insert: reg = (reg & ~(FIELD_MASK << POS)) | ((val & FIELD_MASK) << POS). Prefer explicit masks over C bitfield structs, whose bit order and padding are implementation-defined.
- Name the read-modify-write hazard: if an ISR or another task writes the same register between the read and the write, its change is lost. Disable interrupts around the update, or use the peripheral's dedicated set/clear registers when it has them.
- Treat write-1-to-clear status registers differently: write only the mask of the flag to clear. A read-modify-write on them clears every flag that happened to be set.
Follow-up
- Why does volatile not make the register update safe between an ISR and a task?
- How would you write a 3-bit field at bits 4-6 without disturbing the other bits?
- What goes wrong if you clear a flag in a write-1-to-clear register with reg |= mask?
Write C code for toggling a specific bit.
Medium · Embedded Systems and Low-Level Programmi
Approach
- Toggle with XOR against a one-bit mask: x ^= (1u << n). XOR with 1 flips the bit and XOR with 0 leaves every other bit unchanged.
- Use an unsigned 32-bit type and require n < 32: shifting by the width or more is undefined, and 1 << 31 on a signed int is undefined too. Validate n or document the precondition.
- If you write a macro, parenthesise every argument and the whole expression: #define TOGGLE(x, n) ((x) ^= (1u << (n))), otherwise TOGGLE(r, a + 1) expands to the wrong shift.
- On a volatile hardware register, reg ^= mask compiles to load, XOR, store. That is not atomic, so an interrupt between the load and the store can lose an update; say how you would guard it or use a hardware toggle register if the peripheral provides one.
Follow-up
- How do you toggle every bit between positions lo and hi in one expression?
- How would you test whether bit n is set after the toggle?
- What changes if an ISR toggles the same register bit?
Explain how to implement bit-setting and bit-clearing operations.
Medium · Embedded Systems and Low-Level Programmi
Approach
- Set: x |= (1u << n). Clear: x &= ~(1u << n). Test: (x >> n) & 1u, or (x & (1u << n)) != 0. Each is O(1) and touches only the target bit.
- Match the constant to the operand's width: for a uint64_t use UINT64_C(1) << n, because 1u << 40 is undefined on a 32-bit unsigned int.
- Remember integer promotion: for a uint8_t, ~(1u << n) is a full-width unsigned value. The AND still gives the right byte, but assign the result back with an explicit cast to silence conversion warnings.
- For a field rather than a single bit, clear the field with ~(mask << pos), then OR in (value & mask) << pos, so out-of-range values cannot spill into neighbouring bits.
Follow-up
- How do you clear the lowest set bit, and isolate it, in one expression each?
- How would you count the set bits in a 32-bit word, and what is the complexity?
- Why should these be inline functions rather than macros in production firmware?
How do you swap two numbers without using a temporary variable?
Medium · Embedded Systems and Low-Level Programmi
Approach
- XOR swap: a ^= b; b ^= a; a ^= b. It works because x ^ x = 0 and XOR is associative and commutative, and it is well defined for unsigned integers.
- Call out the aliasing bug: if both arguments point to the same object, the first XOR zeroes it and the value is lost. Guard with if (pa != pb) when the swap takes pointers.
- The arithmetic version (a = a + b; b = a - b; a = a - b) overflows for large signed values, which is undefined behaviour in C; on unsigned types modular arithmetic makes it correct. The multiply/divide variant fails on zero.
- Say what you would ship: a temporary variable. Compilers keep it in a register, while the XOR chain forces three dependent operations and is not faster on modern pipelines. Neither trick applies to floats or structs.
Follow-up
- What does your XOR swap do when called as swap(&x, &x)?
- How would you swap the two nibbles of a byte?
- Is the XOR swap ever faster than using a temporary on a real CPU, and why not?
Write a function to print the binary representation of an integer.
Medium · Embedded Systems and Low-Level Programmi
Approach
- Loop from the most significant bit down: for (int i = width - 1; i >= 0; i--) putchar(((u >> i) & 1u) ? '1' : '0'); with width = sizeof(x) * CHAR_BIT. O(width) time, O(1) extra space.
- Convert to unsigned first (uint32_t u = (uint32_t)x). Right-shifting a negative signed value is implementation-defined, and the cast gives the two's-complement pattern people expect for negative inputs.
- Decide on zeros before the first 1: either print the full fixed width, or skip them and special-case 0 so it still prints "0".
- For firmware output, fill a char buf[33] from the end and send one string over the UART rather than 32 single-character calls; terminate it with '\0' and size it for the bit width plus one.
Follow-up
- How would you group the output into nibbles separated by spaces?
- Write the same routine recursively. What does it cost in stack depth?
- How would you do it without printf or putchar on a target that only has a UART send-buffer call?
Assesses your ability to write clean, efficient code for common data structures under memory constraints.
Medium · Data Structures and Algorithms
Approach
- Start from the memory budget: on a microcontroller, prefer statically sized arrays and fixed-block pools over malloc, so the worst-case footprint is known at link time and there is no heap fragmentation.
- State per-element overhead explicitly: a doubly linked node costs two pointers (8 bytes on a 32-bit target) beyond its payload, which can exceed the payload itself for small items.
- Use intrusive structures where possible: embed the list node inside the owning struct and recover the owner with a container_of-style macro, so inserting never allocates.
- For queues between an ISR and a task, use a ring buffer with a power-of-two capacity and index & (N - 1). Distinguish full from empty by leaving one slot unused or by keeping free-running head and tail counters whose difference is the count.
- Return status codes for full, empty and invalid arguments instead of asserting, and keep every operation O(1) so it is safe in interrupt context.
Follow-up
- How do you make a single-producer, single-consumer ring buffer safe between an ISR and a task without disabling interrupts?
- Write a fixed-block pool allocator with O(1) alloc and free. Where do you keep the free list?
- When is a heap acceptable on an embedded target?
Write code for a circular doubly linked list.
Medium · Data Structures and Algorithms
Approach
- Use a sentinel head node whose next and prev point to itself when the list is empty. Every real node then has non-NULL neighbours, which removes the empty-list and single-node special cases.
- Insert after a position in O(1): n->next = pos->next; n->prev = pos; pos->next->prev = n; pos->next = n. Inserting before the sentinel is an append.
- Delete a known node in O(1): node->prev->next = node->next; node->next->prev = node->prev; then free it and never touch it again. Finding the node by value is O(n).
- Traverse with for (p = head->next; p != head; p = p->next). Without a sentinel, use a do-while from the head and handle the head being deleted, including the one-node list where it points to itself.
- Test the cases that break hand-written lists: empty list, one element, deleting the first and last element, and deleting every node until empty.
Follow-up
- Reverse the list in place. Which pointers do you swap, and do you include the sentinel?
- How does an intrusive list such as the Linux kernel's list_head avoid a separate allocation per element?
- How would you free the whole list without a use-after-free?
Implement stack operations from scratch.
Medium · Data Structures and Algorithms
Approach
- Array-backed stack: an array plus a count (top is the next free slot). push checks count < capacity, pop and peek check count > 0. Every operation is O(1) with no allocation, which suits a fixed embedded budget.
- Linked-list stack: push and pop at the head in O(1), but every push allocates. A growable array that doubles its capacity gives amortised O(1) push when a heap is available.
- Expose a C API that reports errors instead of crashing: bool push(stack *s, int v); bool pop(stack *s, int *out); so overflow and underflow are visible to the caller.
- Be explicit about the top convention (top = count versus top = index of the last element, starting at -1) and test empty, full, and push-pop-push sequences across the boundary.
Follow-up
- Extend it to return the minimum element in O(1).
- How would you fit two stacks in one array so neither overflows until the array is full?
- What has to change if an ISR and a task both push onto it?
Write an algorithm for finding a loop in a linked list.
Medium · Data Structures and Algorithms
Approach
- Floyd's cycle detection: slow advances one node and fast two; if fast or fast->next becomes NULL there is no loop, and if they meet there is one. O(n) time, O(1) space.
- To find where the loop starts, move one pointer back to the head and advance both one step at a time; they meet at the entry node. This works because the distance from the head to the entry equals the distance from the meeting point to the entry, modulo the loop length.
- Loop length: from the meeting point, step one pointer until it returns, counting nodes. To remove the loop, stop at the node whose next is the entry and set that next to NULL.
- Mention the alternative: a hash set of visited node addresses is also O(n) time but O(n) memory, which matters on a small target. Brent's algorithm is another O(1)-space option.
Follow-up
- Prove why resetting one pointer to the head finds the loop entry.
- Remove the loop and leave the list otherwise intact.
- How do you detect the loop if you are allowed to modify the nodes?
Reverse a 32-bit integer efficiently.
Medium · Data Structures and Algorithms
Approach
- Clarify first whether "reverse" means the bit order or the decimal digits; both are common readings. Use uint32_t for the bit version.
- Simple bit reversal: 32 iterations of r = (r << 1) | (x & 1u); x >>= 1;, which is O(w).
- Constant-time version: swap ever larger groups with masks: adjacent bits (0x55555555), pairs (0x33333333), nibbles (0x0F0F0F0F), bytes (0x00FF00FF), then the halves with (x >> 16) | (x << 16). That is five mask-and-shift steps, O(log w). A 256-entry byte table with four lookups trades 256 bytes of flash for speed.
- On Arm cores that implement RBIT (ARMv7-M and later mainline profiles, not ARMv6-M), a single instruction reverses the bits, exposed as __RBIT in CMSIS.
- Decimal digits: pop digits with % 10 and check overflow before multiplying, e.g. if r > INT_MAX / 10 or (r == INT_MAX / 10 and the digit > 7) return 0, with the matching check against INT_MIN for negatives, since C's % keeps the sign of the dividend.
Follow-up
- How would you reverse the byte order instead (an endianness swap)?
- How do you check whether a 32-bit value's bit pattern is a palindrome?
- For the decimal version, how do you detect overflow without using a 64-bit type?
Design a combinational component like a decoder or memory block.
Medium · Hardware Design, Verification, and Digit
Approach
- Decoder: n inputs drive 2^n one-hot outputs, with an enable. Each output is the AND of one input combination, and in SystemVerilog assign out = en ? ({{(2**N-1){1'b0}}, 1'b1} << in) : '0; or an always_comb with a default assignment.
- Avoid inferred latches: in always_comb, assign every output on every path (a default at the top, then the case), and give case statements a default branch.
- Memory block: a ROM is combinational (a case or lookup table). A RAM's write is sequential, done on the clock edge in always_ff; the read can be asynchronous or registered, and that choice changes what the synthesis tool maps it to.
- Parameterise width and depth, derive the address width with $clog2(DEPTH), and state what happens on an out-of-range address.
- Verify small blocks exhaustively: a testbench that loops over all 2^n input values and compares against a reference model.
Follow-up
- Build a 4-to-16 decoder out of 2-to-4 decoders with enables. How many do you need?
- For a RAM, what should a read return when it hits the same address as a write in that cycle?
- What is the difference between a decoder and a priority encoder?
Explores your knowledge of memory management, RTOS internals, and low-level system execution.
Medium · Operating Systems and Architecture
Approach
- Memory regions: code and constants in flash, .data copied from flash to RAM at startup, .bss zeroed, a stack per RTOS task, and optionally a heap. Say what lives where and why.
- Reset to main(): the vector table supplies the initial stack pointer and reset handler; startup code configures clocks, copies .data, zeroes .bss, runs C library init, then calls main(), which starts the scheduler.
- RTOS internals: a preemptive priority scheduler always runs the highest-priority ready task; tasks move between running, ready, blocked and suspended. A context switch saves and restores registers and the stack pointer (on Cortex-M, typically in the PendSV handler).
- Interrupts: keep ISRs short, never block in them, and hand work to a task through an ISR-safe queue or semaphore call (the FromISR variants in FreeRTOS).
- Stack safety: size task stacks from measurement (fill with a pattern and check the high-water mark), and use an MPU guard region or overflow hook to catch overruns.
Follow-up
- What happens if .bss is not zeroed before main() runs?
- How do you choose a stack size for a new task?
- What is priority inversion, and how does priority inheritance fix it?
Discuss multithreading concepts, heap management, and linker behavior in an RTOS environment.
Medium · Operating Systems and Architecture
Approach
- Multithreading: a mutex has an owner and in FreeRTOS supports priority inheritance; a binary semaphore has no owner and suits signalling, including from an ISR. Prevent deadlock with a fixed lock order and keep interrupt-disabled regions short.
- Heap: general malloc is not deterministic and fragments over time. FreeRTOS ships heap_1 (no free), heap_2 (best fit, no coalescing), heap_4 (first fit with coalescing), heap_5 (heap_4 across several regions) and heap_3 (wraps the C library malloc with the scheduler suspended).
- Fixed-size block pools give O(1) allocation with no external fragmentation; many systems allocate only at init and never free afterwards.
- Linker: the script's MEMORY block defines flash and RAM regions and SECTIONS places .text, .rodata, .data (load address in flash, run address in RAM) and .bss; startup code uses linker symbols to copy and zero them.
- Use the map file to find what consumes flash and RAM, compile with -ffunction-sections -fdata-sections and link with --gc-sections to drop unused code, and place time-sensitive code or DMA buffers in a named section mapped to a specific RAM region.
Follow-up
- Why can an ISR give a semaphore but not take a mutex?
- How would you diagnose a slow heap exhaustion that appears after days of uptime?
- How do you place one buffer in a specific RAM bank for DMA?
Write the update path that detects a concurrent edit
Medium · optimistic concurrency · transactional outbox · audit history
resource carries version INT NOT NULL DEFAULT 1. resource_revision holds revision_id, resource_id, version, actor_user_id, change_kind, patch JSONB, request_id, created_at with UNIQUE (resource_id, version). outbox_event holds aggregate_type, aggregate_id, aggregate_version, event_type, payload, status. A PUT carries the version the client read. Write the exact statements for the single transaction that applies the edit, records the revision and enqueues 'resource.updated', and give the handler's branch on zero affected rows. Then say what PostgreSQL 16 does under READ COMMITTED when two of these updates hit one row at once.
Approach
- One transaction, three writes, no network call inside it: UPDATE resource SET title = $3, version = version + 1, updated_at = now() WHERE resource_id = $1 AND tenant_id = $4 AND version = $2; then INSERT the resource_revision row at version $2 + 1; then INSERT the outbox_event row at the same aggregate_version. The event goes to a table rather than a broker because no transaction spans both.
- Branch on the affected-row count before doing anything else. Zero has three causes — stale version, wrong tenant, row gone — so re-read once and map to 409 carrying the current version, or 404 for an id outside the caller's tenant, which also stops the endpoint confirming that another tenant's id exists.
- State the engine behaviour instead of assuming it. Under READ COMMITTED the second UPDATE blocks on the row lock, and when the first commits PostgreSQL re-evaluates the WHERE clause against the newly committed row, so the version predicate now fails and the statement reports zero rows. Under REPEATABLE READ the identical collision raises SQLSTATE 40001 instead, so the handler must fold both shapes into one conflict response.
- Keep UNIQUE (resource_id, version) even though the predicate already serialises writers. It is what makes a lost update unwritable if any other path ever reaches the revision table, and it converts a logic bug into 23505 rather than into a silently missing history row.
Follow-up
- A client sends the version it read ten minutes ago and the resource has moved three versions. What is in your 409 so it can resolve the conflict without a full re-fetch?
- Two editors, two disjoint fields, no overlap. Does your answer still refuse the second write, and should 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?
Evaluates your understanding of digital systems, hardware verification, and SoC architecture.
Medium · Hardware Design, Verification, and Digit
Approach
- Separate combinational logic (outputs depend only on current inputs) from sequential logic (state held in flip-flops on a clock edge), and explain setup and hold time and how the slowest path between registers limits the clock frequency.
- Clock domain crossing: a two-flop synchronizer for a single control bit; for multi-bit data, an asynchronous FIFO whose pointers are Gray-coded so only one bit changes per increment.
- Sketch an SoC from the software view: CPU cores, an interconnect such as AXI, AHB or APB, a memory map of on-chip SRAM, flash and peripheral registers, an interrupt controller and DMA.
- Verification layers: a testbench that drives stimulus, a reference model and scoreboard that check results, assertions for protocol rules, and functional coverage to show what was exercised; constrained-random stimulus reaches cases directed tests miss.
- Tie it back to firmware: when DMA and a data cache share a buffer, clean the cache before a DMA transmit and invalidate it after a DMA receive.
Follow-up
- Why are async FIFO pointers Gray-coded rather than binary?
- What is metastability, and why does a second flip-flop reduce the risk rather than remove it?
- What happens if firmware skips the cache invalidate after a DMA receive?
Write a class to return a deck of 52 playing cards with randomization using constraints only, without using un
Medium · Hardware Design, Verification, and Digit
Write a class to return a deck of 52 playing cards with randomization using constraints only, without using unique or randc.
Approach
- Model the deck as rand bit [5:0] deck[52], each value a card index 0-51, with foreach (deck[i]) deck[i] inside {[0:51]}; then decode suit = idx / 13 and rank = idx % 13 in post_randomize or a print function.
- Express uniqueness without unique or randc as pairwise constraints: foreach (deck[i]) foreach (deck[j]) if (i < j) deck[i] != deck[j]; That is 1,326 inequalities. Range plus distinctness forces a permutation, since 52 distinct values from 52 possible are all of them.
- Alternative: an occurrence count per slot, foreach (deck[i]) deck.sum() with (int'(item == deck[i])) == 1; Cast to int, because summing 1-bit comparisons in their own width truncates the total.
- Do not rely on a sum constraint alone: values summing to 1,326 do not prove a permutation.
- Check the return value of randomize() and report a failure. If the class must return the deck, expose a function that randomizes and returns the array, or a queue of card names.
Follow-up
- Add a constraint so that no two adjacent cards share a suit.
- Deal a random 5-card hand of distinct cards using constraints only.
- Why would randc on each array element not give you a shuffled deck?
Answer technical questions regarding hardware design, scripting, and test methodologies.
Medium · Hardware Design, Verification, and Digit
Approach
- Hardware design basics: use non-blocking assignments (<=) in clocked always_ff blocks and blocking (=) in always_comb, and explain the simulation race that mixing them in a shift register causes.
- Scripting: describe a concrete Python automation you could write, such as driving a board over a serial port with pyserial, parsing logs with regular expressions, and writing a pass/fail summary a regression job can read.
- Test methodology for firmware: host-side unit tests against a hardware abstraction layer with mocked registers, then hardware-in-the-loop tests on the real board, run on every change.
- Test methodology for hardware: directed tests for known cases, constrained-random for breadth, and measuring both code coverage and functional coverage.
- For each topic, have one example from your own work ready: the bug the test caught or the manual step the script removed.
Follow-up
- How would you test a peripheral driver before the silicon is available?
- What does functional coverage tell you that code coverage does not?
- How would you make a flaky hardware-in-the-loop test reproducible?
Discuss UVM fundamentals and SystemVerilog concepts.
Medium · Hardware Design, Verification, and Digit
Approach
- UVM structure: test, then env, then agents, each agent holding a sequencer, a driver and a monitor; plus a scoreboard and coverage collectors. Sequences generate sequence items, the driver pulls them from the sequencer and drives a virtual interface, and the monitor publishes observed transactions through an analysis port.
- Phases: build_phase runs top-down to create components, then connect_phase and the later function phases run bottom-up; run_phase is the time-consuming task phase. The test raises and drops objections to keep simulation running until stimulus completes.
- Factory and configuration: create components with type_id::create so type or instance overrides can swap them without editing the env, and pass the virtual interface and settings through uvm_config_db.
- Distinguish uvm_component (static hierarchy, has phases) from uvm_object (transient, like sequence items), and active agents (drive) from passive agents (only monitor).
- SystemVerilog concepts to cover: classes and virtual interfaces, rand and randc with constraints, covergroups, immediate versus concurrent assertions, clocking blocks, mailboxes and semaphores, and fork...join, join_any and join_none.
Follow-up
- What happens if no objection is raised in the run phase?
- How does a transaction from the monitor reach the scoreboard?
- When would you use an instance override instead of a type override?
Explain how MMU paging works in modern architectures.
Medium · Operating Systems and Architecture
Approach
- The MMU splits a virtual address into a page number and an offset; with 4 KB pages the low 12 bits are the offset and pass through unchanged, while the page number is translated to a physical frame.
- Translation uses multi-level page tables (for example four levels of 9 bits each for a 48-bit address with 4 KB pages on x86-64 and on AArch64), so unmapped regions need no table memory. A flat table for a 32-bit space with 4 KB pages and 4-byte entries would be 4 MB per process.
- The TLB caches recent translations; on a miss a hardware walker reads the tables. Page table entries carry valid, read/write/execute, user/kernel, accessed and dirty bits, and memory attributes such as cacheability.
- A page fault happens when the entry is invalid or permissions are violated; the OS then loads or allocates the page (demand paging, copy-on-write) or terminates the access with a fault.
- On a context switch the table base register changes (CR3 on x86, TTBR0 on Arm); address-space IDs let TLB entries survive the switch. Contrast with the MPU on microcontrollers, which enforces region permissions but does no translation.
Follow-up
- Why do huge pages improve performance for some workloads?
- What is a TLB shootdown, and when is it needed on a multicore system?
- What would you use instead of an MMU to isolate tasks on a microcontroller?
Every query on one table stalls for forty seconds mid-deploy
Medium · lock contention · ddl · migrations
During a release on PostgreSQL, every query touching resource times out for about 40 seconds and then recovers with no intervention. The release ran one migration, ALTER TABLE resource ADD COLUMN archived_reason TEXT, and the migration log shows it completing in 6 ms. Unrelated tables showed no change in error rate. Explain how a 6 ms statement caused a 40-second stall, give the ordered checks you would run on a live system to confirm it, and give the migration procedure that prevents a repeat.
Approach
- Separate the statement's duration from the lock's duration. ADD COLUMN with no default is a catalogue-only change and genuinely runs in milliseconds, but it requires ACCESS EXCLUSIVE, and it cannot acquire that until every transaction already touching the table has finished.
- Account for the queueing, which is the part that surprises people. A lock request that is waiting blocks later requests for conflicting modes behind it rather than letting them overtake, so one long-open transaction holds the DDL and the DDL holds all the traffic. The stall length is set by the longest open transaction, not by the size of the change.
- Confirm on a live system in this order: pg_stat_activity for that table ordered by xact_start, looking for the oldest transaction and specifically for state = idle in transaction; then pg_locks where granted = false to find the waiter; then join them on pid to name blocker and blocked. pg_blocking_pids() does that join for you and is the fastest single call.
- Prevent rather than merely time it better. Set lock_timeout to a second or two on the migration session so the DDL abandons the queue after a bounded wait and is retried, instead of holding it for as long as the oldest transaction lives. Be exact about what that buys: queries arriving during the wait still queue behind the pending ACCESS EXCLUSIVE request, so each attempt costs them up to one lock_timeout of added latency. The outage goes from 40 seconds to about one second per attempt, not to zero. Also run migrations away from deploy-time peaks, and put a statement timeout and an idle-in-transaction timeout on the analytics role that opens the long transactions.
Follow-up
- The same release also wants NOT NULL on that column. What is the sequence that gets there without a long lock?
- Your lock_timeout retry fails ten times in a row because the analytics transaction is always open. What do you change?
Suggested practice checklist
Each day targets one reported question group, finishing with the operating systems and memory material and a collaboration rehearsal. The tasks use the reported questions directly, so the plan can be repeated on the weakest day.
Day 1 — Register-level bit operations
- Write set, clear, toggle and check functions for a 32-bit register using unsigned types and volatile, and test bit 0, bit 31 and an out-of-range index
- Implement the swap-without-temporary question with XOR, then add the guard for identical addresses and explain why it exists
- Write a function that prints the binary representation of an integer, most significant bit first, and test 0, 1 and negative input
Deliverable: A short C file holding the bit operations, the XOR swap with its guard and the binary printer, each with edge-case test calls.
Day 2 — Bit reversal and efficiency
- Reverse a 32-bit integer with a loop, then with the mask-and-shift method that swaps adjacent bits, then pairs, nibbles, then bytes
- Compare the loop, mask-and-shift and lookup-table versions for code size, speed and flash use
- Test with 0, 1, 0x80000000 and 0xFFFFFFFF and confirm that reversing twice returns the input
Deliverable: Three working reversal functions with a table comparing their size and operation counts.
Day 3 — Linked lists and stacks from scratch
- Write a circular doubly linked list with insert at head, insert at tail and delete of a given node, covering empty and single-node lists
- Implement a stack on a fixed array with push, pop, peek and explicit overflow and underflow handling, then a version on dynamic memory
- Write loop detection with slow and fast pointers and explain why they must meet if a cycle exists
Deliverable: A list implementation, an array stack and a cycle detector, each with a test list of edge cases.
Day 4 — SystemVerilog constraints and UVM
- Write a SystemVerilog class that randomizes 52 distinct card values using constraints only, with no unique and no randc
- Write a one-page explanation of UVM in your own words: what the environment, agent, driver, monitor, sequence and scoreboard each do
- Prepare two answers on how you would script or automate a test for a driver you wrote
Deliverable: The card-deck class, a one-page UVM summary and two spoken answers on test methodology.
Day 5 — Combinational design and digital systems
- Design a 3-to-8 decoder with an enable input: write the truth table, then the logic equations, then the SystemVerilog
- Sketch a small memory block with address, data and write enable, and describe its read path and write path
- Explain in one paragraph how this component connects to a bus on an SoC
Deliverable: A decoder with truth table and code, a memory block sketch and a short SoC-context explanation.
Day 6 — MMU, RTOS and memory management
- Draw a two-level page table walk for a virtual address, including TLB hit and miss, permission checks and a page fault
- Write a one-page explanation of mutexes, semaphores, priority inversion and safe data sharing between an interrupt routine and a task
- Outline how the linker script places code and data in flash and RAM, then prepare the memory allocation and fragmentation answer with a fixed-block pool example
Deliverable: A page walk diagram, a concurrency summary and a rehearsed answer on allocation and fragmentation.
Day 7 — Collaboration stories and role conversation
- Write two stories about working with hardware or verification teammates, covering the problem, what you did, what changed and what you would do differently
- Rehearse your introduction for the Recruiter Call and your questions for the Role Discussion
Deliverable: Two written stories, a short introduction, a list of questions for the team and a recorded run of one story.
Hardware-software collaboration stories
Candidates report a collaboration criterion: how you explain technical ideas, work with cross-functional teams, take feedback and stay composed during hard problems. For this role, those teams include hardware design, verification and systems engineering. Choose stories where software and hardware met: a register specification you had to interpret, a bug that was unclear whether it was silicon or firmware, or a memory limit that forced a design change.
- Tell me about a bug where it was unclear whether hardware or software was at fault, and how you worked it out with the other side.
- Describe a time you changed your approach after feedback in a code review or design discussion.
- Tell me about a project where you had to reduce flash or RAM use, what you measured and what you changed.
- Describe how you explained a low-level technical issue to someone who did not work on firmware.
How do you handle memory allocation and fragmentation in resource-constrained embedded targets?
Medium · Operating Systems and Architecture
Approach
- Open with the target's constraints, because they decide the strategy: RAM and flash size, whether there is an MMU or only a memory protection unit, hard real-time deadlines, and how long the device runs without a reset. Say which of these applied on your project.
- Describe the default I reach for: avoid dynamic allocation after initialization. Use static buffers, and fixed-size block pools where objects are created and destroyed at runtime. A pool hands out equal-size blocks from a free list, so allocation and release are constant time and there is no external fragmentation, since any freed block fits any later request of that size. Use one pool per size class.
- If a general heap is unavoidable, separate internal fragmentation (padding and alignment waste inside a block) from external fragmentation (free space split into pieces too small to use). Allocate long-lived objects once at startup, keep short-lived ones in an arena that is reset together, avoid mixing lifetimes, and never call the allocator from an interrupt handler since its worst-case time is unbounded and it may take a lock.
- Show measurement. On a real target I tracked the largest free block rather than total free bytes, the allocation high-water mark, the stack watermark per task, and the static footprint from the linker map. Always check the allocation result and define what the system does on failure.
- Tell it as a story: symptom (allocation fails after long uptime although total free memory looks fine), what you measured, the change, and the number that improved. The mistake that sinks it is a list of techniques with no case where you applied one.
Follow-up
- How would you detect fragmentation on a device that has been running for days?
- Why is calling malloc from an interrupt service routine a problem, and what do you do instead?
- When is a fixed-block pool the wrong choice?
Unblock an engineer without taking the keyboard
Easy · mentoring · leases · at-least-once
A teammate has spent two days on a job handler that occasionally writes duplicate rows. They are certain the queue is delivering twice by mistake. You suspect a lease expiring under a slow handler, so the job is running concurrently with itself. Describe how you have unblocked someone in this position: what you asked before offering a hypothesis, what you showed them rather than told them, and what you left them owning. Then say what you would do if their theory turned out to be the right one.
Approach
- Ask before diagnosing, and ask for things answerable from data they already have: the attempt count on the job rows that produced duplicates, the handler's observed duration against its lease expiry, and whether the duplicate rows share a natural key that a unique constraint could have caught.
- Teach the shape rather than the answer. A lease cannot distinguish a dead worker from a slow one, so a handler that outruns its lease is running twice by design, and deploys deliver the other half by killing handlers mid-run on every rollout. Both of their candidate theories produce identical duplicate rows, which is why the evidence has to come from timings rather than from argument.
- Hand over a checklist they execute: a natural key on every write the handler performs so the second copy collides rather than appends, the record of intent written before any external effect, a lease heartbeat while running, and the metric that shows it working.
- Keep ownership with them deliberately. Pair on the first write, then step back; if you finish it yourself you have closed one ticket and left the same person stuck on the next redelivery.
Follow-up
- How would you distinguish a genuine double-delivery from a lease expiry using only the data already stored?
- Their handler calls an external endpoint before recording that it did. What do you tell them to change first?
Narrate an outage you owned from page to postmortem
Hard · incident response · blast radius · postmortems
Pick an incident you personally drove, ideally one where writes were affected rather than reads. In six to eight minutes: state the symptom as it first appeared on a dashboard, the blast radius you established before you knew the cause, the mitigation you applied and when, the mechanism you eventually proved, and the follow-up that would prevent a repeat. Bring numbers: error rate, tenants affected, minutes to mitigate, minutes to resolve. If you cannot name what you measured, choose a different incident.
Approach
- Open on the signal rather than the cause: which metric at which percentile moved, on which service, at what time, so the listener follows the same evidence you had rather than a conclusion you already reached.
- Separate mitigation from diagnosis out loud. State what you did to stop the bleeding (flag off, shed traffic, drain a lease, roll back a deploy) and say plainly that you did it before the mechanism was known, because those are two jobs with different deadlines.
- Establish blast radius in countable terms: how many tenants, how many writes, and crucially whether the effect was loss or only delay. An append-only revision table or a pending outbox row means the change survived and the projection was merely behind, which is a repair rather than a data-loss incident.
- Prove the mechanism instead of asserting it. Name the trace span that grew, the plan that flipped to a sequential scan, the lease that expired, plus one alternative you ruled out and the signal that stayed flat while you ruled it out.
Follow-up
- What would you do differently in the first five minutes, given the same dashboard and no more information?
- Which follow-up action did you deliberately not take, and why was dropping it the right call?