Something clicked last week while I was chasing a bug that kept slipping through. I realized I've spent my whole career eyeballing waveforms and calling it verification. Never once wrote a testbench that actually checked anything on its own. So I spent a couple evenings building one for my SPI module. Reference model, scoreboard, automatic pass or fail at the end. It caught a corner case on the very first run, one I'd been hunting on and off for a month. Bit annoying how fast it was, honestly. The funny part is I always assumed this stuff was for big teams with formal processes and code reviews. Turns out it's just code like anything else. Would have saved me a lot of late nights staring at a scope if I'd picked it up years ago.
Eight years in and I finally wrote my first self-checking testbench
Oct 13, 2026
Last reply: 1 day ago
4 Replies
Same realization hit me a few years back with an I2C peripheral I'd been shipping in projects for ages. I'd always just wired it up, watched the scope, shrugged, moved on. Then a customer report came in about a failure that only showed up when the clock got stretched mid-transfer, and I couldn't reproduce it to save my life. That's what finally pushed me to write a proper self-checking testbench for it.
Took me about three evenings, mostly beacuse I had to learn how to structure it. The checker caught the bug in about ten seconds of simulation. The slave was releasing the data line one cycle early after a repeated start, something I never would have spotted on a scope because it only mattered under that specific stretch condition.
What surprised me was how much less scary the code was than I'd built it up in my head. A reference model is really just a second copy of the thing that behaves the way you think it should, then you compare. I started small with just the scoreboard checking one signal at a time and grew it as I went. Now I write the testbench first, before the module, and it's changed how I design entirely. When the checker is alredy there, you find yourself thinking about corner cases while you're still writing the RTL instead of six months later at two in the morning.
I went through the same shift but landed somewhere slightly diferent. I still write mostly directed tests because my brain works that way, but the thing that acutally saved me was dropping a few assert statements into the RTL itself. Had a FIFO in an ADC capture path that woud silently overflow when the host read slower than the sample rate, and I'd been staring at output data trying to spot the dropouts by eye. One assert on the full flag and the simulator pointed at the exact clock cycle within ten minutes.
What I like about it is you don't have to build a whole reference model first. The checks live in the design, so they run every time anyone simulates, even on some quick throwaway test a coworker throws togather. It caught a second bug six months later that my own testbench never would have hit, because a different person was driving the module in a way I never tested. That said, the scoreboard approach the OP describes is the real deal for anything protocol based. My UART checker only got trustworthy once I stopped eyeballing the RX data and started comparing against an expected stream. Funny how the tools are free and we still put off learning them for years.
What got me over the hump was realizing I could start stupidly small. Added one check to an existing bench that just made sure a counter reset properly, took twenty minutes. That tiny win pushed me to keep adding. Now I'm curious where you two landed on random stimulus stuff, that still feels like wizardry to me. I can write a test that pokes known inputs, but letting the tool throw a thousand weird combos at a module sounds like a whole different skill. Did anyone here pick that up naturally or does it stay awkward for a while?
The thing that finally made it stick for me was cocotb. I'd bounced off writing benches in the HDL itself a couple times, the syntax felt like homework on top of an already long day. But I already knew Python from scripting my builds, so writing the checks in a language I didn't have to think about made the whole idea click. Rewrote the bench for my UART in one afternoon and the reference model was barely forty lines. Random stimulus stopped feeling like wizardry once I quit trying to be clever, just randomized the bytes and let the checker yell when it disagreed. For me the barrier was always the language, never the concept.
Join the Discussion
Have something to add? Share your thoughts — no account required.
Didn't find your answer?
Ask the community — no account required