Electronic – Best way to structure Verilog module to allow for simulation clocks

fpgaverificationverilog

Quick question that I am likely missing an obvious solution for. I have a relatively simple Verilog design which I'll call taco, where the top-level design entity is taco_top (because I'm writing this before lunch).

It's the only module in the design with all relevant logic contained in taco_top. The design targets the ever-popular Lattice iCE40 UltraPlus FPGA. In taco_top, I'm using the iCE40's low-frequency and high-frequency oscillators to generate 10kHz and 6MHz clocks. There are no external clocks in the design; taco_top's ports all correspond to physical I/O pins on the FPGA and the clocks I need are sourced from these internal oscillators.

In simulation, I want to replace these with simple dummy clocks with the appropriate period since I can't simulate the LFOSC and HFOSC primitives. So, I've created the appropriate simulation only clocks and kludged them into my design with `define which is not particularly elegant.

In writing a testbench, I would normally instantiate my taco_top module and feed it the appropriate stimuli, but those sim clocks are buried inside / not exposed at the top-level.

What's an elegant way to structure my design such that I can easily feed those clocks in simulation and maintain their behavior in the synthesized design?

Best Answer

In our designs, we normally divide the design into two layers, like an onion. We call the outer layer the "pad ring" (e.g., taco_io), and it contains everything that's specific to the physical interfaces of the chip — I/O buffers, on-chip clocks, etc.

The inner layer is the "core" layer, and contains all of the actual application functionality. In other words, the functional hierarchy is implemented with taco_core as its top level. The functional simulation testbench instantiates taco_core only, where it has access to drive those clocks you're talking about.

Your taco_top is used for synthesis only. It just instantiates two modules: taco_io and taco_core and connects them together. It also connects taco_io to the actual external pins of the device.

Note that you could put all of those I/O bits right into taco_top, but we find that it's generally cleaner to put them in their own submodule (taco_io).