Digital Design / Module 12: Projects and interview preparation
67. Industry project: streaming packet engine
Work to a realistic specification.
See it move
The goal
Build a streaming packet engine and deliver it as a professional would: specification first, then RTL, then evidence. This is the portfolio piece to discuss in interviews, so what matters is how well you can explain it.
The design
Data arrives on several valid/ready input ports, in one . Packets cross to a core clock through async . A round-robin picks one packet at a time, and keeps its grant until the end of that packet. A pipelined block computes a checksum. The output uses a , and appends the checksum.
What to deliver
- Specification: interfaces, packet format, target, error behaviour. Write it before any RTL.
- Architecture document: block diagram, clock domains, FIFO depth calculation, and reasons for each choice.
- RTL: parameterised and -clean.
- Test plan: a list of what must be proved, written before the tests.
- Self-checking testbench: random traffic, random , a scoreboard, and coverage of the corners.
- Reports: synthesis area and timing, using an open-source flow if you need one.
- README: results that a reader can reproduce with a single command.
Corners to test
- Full FIFOs with continuous input
- A packet of minimum length, and one of maximum length
- Different clock ratios, in both directions
- Reset during a transfer
- One port idle, or one port always busy
Presenting it
Structure your explanation like this: the problem, the architecture, the key trade-offs, how you verified it, the measured numbers, and what you would change. Be ready to justify every FIFO depth, and every crossing. "I chose depth 32 because the burst is 64 words at half the drain rate, plus 4 for delay" is what an experienced listener wants to hear.
Common mistake
Publishing a repository with no results. Reviewers rarely run your code. A short report containing a block diagram, timing, area, and coverage shows the work far better. Use only original work. Never include anything from an employer, or from another party's confidential documents.
Key ideas
- Valid/ready input, async FIFO, arbitration, and checksum.
- Write a specification, plan your tests, and report timing and area.
- Publish on GitHub with a clean README.
Your exercise
Interview questions
coreHow do you present a project?
Problem, architecture, trade-offs, verification, measured results, and what you would change.
basicWalk through the packet engine's blocks in data order and say what each one is for.
Each input port uses a valid and ready interface, so a sender can be paused. Each port crosses into the core clock through its own asynchronous FIFO, so only Gray pointers cross. A round-robin arbiter picks one port that has data waiting and keeps it until that packet's last byte. The core passes the bytes through while adding them up for the checksum, and the output goes through a skid buffer, which keeps full throughput while its ready path is registered.
basicA packet carries the bytes 0x10, 0x20 and 0xF5. The engine appends the sum of the bytes, keeping only the low 8 bits. What byte is appended?
0x10 + 0x20 = 0x30, and 0x30 + 0xF5 = 0x125. Keeping the low 8 bits gives 0x25. In decimal: 16 + 32 + 245 = 293, and 293 - 256 = 37, which is 0x25. A checker in the testbench must use the same 8-bit wrap, or every packet with a carry will look wrong.
coreWhy must the arbiter keep a port's grant until that packet's last byte, and which check catches a design that does not?
If the arbiter picks again after every byte, bytes from different ports are interleaved on the output, so packets get mixed together even though every port is served fairly. The output protocol has no way to separate them, so the stream is corrupt. A scoreboard that keeps a queue per port and checks that every output packet comes entirely from one port catches this immediately. That is why holding the grant is written into the specification.
coreA port's FIFO is 4 entries deep and the core reads faster than the port writes, yet the port only reaches about two thirds of its full rate. Why?
In an async FIFO, the writer only sees a freed entry after the read pointer has crossed back through a synchronizer, which takes a few write cycles. Until then, it still believes the FIFO is full. With only 4 entries, it runs out of space it knows about before that news arrives, so it stalls even though the reader has room. A deeper FIFO, 8 entries in this design, covers the round trip and lets the port run at nearly full rate.
coreA test run reports zero wrong bytes. What else must the results show before that zero means anything?
That the important corners were actually reached. Coverage counts should show minimum and maximum length packets, FIFOs going full under continuous input, different clock ratios in both directions, reset during a transfer, and one port idle or always busy. If the FIFOs never filled, a zero says nothing about overflow behaviour. A zero is only evidence when you can show the test exercised the case.
coreWhy put a skid buffer on the output rather than passing ready straight back through the core?
Passing ready combinationally from the output back through the core and the arbiter makes a long timing path that grows with the design. A skid buffer registers the ready path while still allowing one transfer every cycle, because it has a spare entry to catch the data already in flight when the downstream side stops accepting. So it breaks the timing path without losing throughput. Its cost is a second data register and a little control logic.
advancedA slow port that wins the arbiter makes the engine wait for each of its bytes, while a busy port's data sits ready. How could you fix this, and what does it cost?
This is head-of-line blocking caused by holding the grant for a whole packet. One fix is to only arbitrate a port once its whole packet is in the FIFO, store and forward, so the engine never waits mid-packet. That needs each FIFO to hold at least one maximum-length packet, 16 bytes or more, plus a packet count or end marker that crosses safely into the core domain. You would report the waiting cycles measured before and after, so the trade-off is backed by numbers.
advancedInput ports run at 100 MHz with one byte per cycle. The core handles one byte per cycle and appends a 1-byte checksum to every packet. For one port to send at its full rate, what core clock is needed with 16-byte packets, and with 1-byte packets?
With 16-byte packets, every 16 input bytes become 17 output bytes, so the core needs at least 100 x 17 / 16 = 106.25 MHz. With 1-byte packets, every input byte becomes 2 output bytes, so it needs 200 MHz. These are lower bounds before any per-packet arbitration overhead. The worst case comes from the shortest packets, which is why the minimum length belongs in the test plan.