Better Code With RTL Linting And CDC Verification
A simple but effective way to find bugs in ASIC and FPGA designs.
Automated design rule checking, or linting, has been around in RTL verification for at least a couple decades, yet still many HDL designers completely ignore this simple yet very powerful bug hunting method. Why would a busy designer need to run this annoying warning generator? The hostility against using conventional linting tools is often explained by the enormous amount of output noise, limited configurability, ambiguous reporting, and inability to capture a high-level designer’s intent. FPGA users also complain about the lack of vendor libraries support. Whether it’s an ASIC or FPGA, the presence of IP blocks creates an extra challenge for DRC tools, as IPs are often tool-generated or encrypted.
While there are a couple of well-known, mature DRC-based verification tools on the EDA market targeting the large-scale ASIC segment, they also come with an annual license cost that exceeds the price of a new four-wheel SUV. The use of auto-formal methods applied to aspects such as CDC protocol checks and MCP validation, as well as the handling of UPF-based power models and taking physical cell properties into account, indeed make those linting tools very powerful. But is this capacity really necessary for a hypothetical mid-range FPGA project relying on the native vendor’s backend implementation tools?
The problem for such a mid-range FPGA or small ASIC project, such as an IoT device, is that there is simply no budget for a heavyweight, overkilling instrument, and investing in obsolete “warning generators” no longer makes any sense.
Solving typical challenges for key components
- Design Entry and Logic Synthesis Function. Not surprisingly, at the very bottom, an RTL linter requires a robust design entry and logic synthesis function. Without a synthesis engine, a linter would be limited to enforcing coding style and naming conventions, and catching only basic misuse of the HDL constructs. The rules of highest demand address sub-optimal synthesis, synthesis vs. simulation mismatches, or directly refer to “synthesized netlist,” so the netlist must be automatically extracted from the RTL description similarly to a real logic synthesis tool. However, the linter only needs the top of the synthesis iceberg, namely the extraction of sequential elements and glue combinational logic mapped to its own internal library of abstract netlist primitives. Unlike real synthesis tools, the linter does not necessarily need to translate the inferred cells to concrete target technology libraries, may ignore the optimization of timing, area, and does not have to place logic elements on the chip or assign I/O package pins. At a minimum, the linter must only build a high-level projection of the final netlist to extract its key structural and timing dependencies to run the netlist rule checks against this projection. A high-level projection should also be enough to display the detected issues for debugging purposes.
- Netlist Pattern Matching Function. If gates and flip-flops are the “characters” of the netlist, patterns are the “wildcards” or “regular expressions.” Patterns are defined as groups of related netlist primitives, satisfying given connectivity and contextual conditions. Pattern matching, the level above synthesis, is about recognizing the frequently used netlist structures within the clock and reset trees, as well as part of control and data paths. The examples of patterns are clock generation circuits, like a ripple counter, clock gating schemas, pulse/edge detectors, data and reset synchronizers, inserted scan chains, boundaries of finite state machines (FSMs), and FIFOs built around memory elements. Uncovering pattern occurrences helps to capture a higher level semantic view of the netlist and enables much smarter linting rules, like clock/reset connectivity, combinational feedbacks and basic DFT. There are both desired and undesired patterns. Some patterns, like a clock signal driving a reset pin, are inherently evil and they themselves represent an important class of potentially harmful defects.
- Vendor-Specific Primitives Support. In addition to writing custom RTL code, FPGA designers also use direct instantiation of the vendor-specific primitives, such as logic elements, clocking resources, high-speed serial I/O and memory cells. Some components, i.e. Xilinx DDR registers, do not have any known inference pattern in RTL. FPGA vendors supply simulation libraries for all the available primitives, however most of those simulation models cannot be synthesized and interpreted directly. A linter targeting FPGA design verification must handle vendor-specific primitives, and replace them with its internal netlist cell equivalents as close as possible. The lookup tables, multiplexers, latches, flip-flops and memories must be interpreted similarly to RTL-inferred equivalents when looking for netlist patterns. The clocking primitives, like buffers, glitch-free gating/multiplexing schemas, PLLs and DCMs not only affect the netlist patterns, but generally form the clocking hierarchy of the entire design. A linter used in the FPGA project verification cycle must provide excellent coverage of the target vendor’s libraries.
- Detailed Timing View based on Design Constraints. The next level above the netlist patterns and FPGA cell models considers applying design constraints (SDC, XDC) and building a detailed timing view of the design. Having a clear understanding of the timing domains is mandatory to perform advanced verification of clock and reset domain crossings. Misinterpreted timing can lead to generating thousands of false CDC positives, or, even worse, missing an important negative. The default timing view is automatically extracted from the netlist topology and the identified netlist patterns. Often, this default view is satisfying, however applying some external constraints might still be necessary to give hints to the auto-detection, especially when using complex clock gating or multiplexing schemas, also statically selected clock paths. Supplying constraints is a must when the relations between clocks or resets are invisible by looking only on the netlist topology. The linter cannot guess what the clock periods are, but can suggest better rule output when the decision depends on the period comparison (fast-to-slow, slow-to-fast transfers). Similarly, there is no way to predict that selected external clocks belong to a same synchronous group, also, that some of the timing paths are never active and have to be ignored. Additionally, the design could have special I/O pins that depend on the clock signal outside of the design being analyzed (the virtual clocks), which is obviously invisible in the netlist. User-defined constraints polish the auto-detected view and assist the linter to eliminate the output noise.
- Black-Box IPs with Blocks-Level Constraints. Modern designs instantiate at least one or more IP blocks that implement supplementary cores or support communication interfaces, memory units, integration of the custom RTL and the embedded hard CPU. The IPs often come in the form of pre-synthesized netlist or encrypted RTL, and the formats are often specific to the synthesis tools. Even if the HDL code is available, it might be non-synthesizable (a behavioral model for the simulation), and the end-user usually has a limited control over it. For the linter, the IP blocks become black boxes, either by including a stub I/O definition file or with automatic I/O generation based on the port maps. Clock/reset connectivity and CDC verification are incomplete without checking at least the I/O of the IP blocks, especially for multi-clock modules. The most challenging verification problem comes from the IP blocks, which generate new output clock domains on their own, affecting the overall timing view of the design. Failing to correct the structure of the clock domains results in a guaranteed CDC output noise. However, the linting tool must have at least a minimal understanding of the IP block’s timing dependencies as well. So, if there is no synthesizable RTL, the only way is to ask the user to describe the timing properties of the block using the constraints.
- Advanced Debugging and Analysis Mechanisms. The topmost level above the final timing view implements advanced debugging functions and analysis mechanisms. Aldec’s ALINT-PRO offers a few alternatives here that supplement each other, including traditional rule violation reports in various forms (text, CSV, PDF, HTML); textual reports of the extracted timing views, clock domain crossings, identified synchronizers; GUI-based violation viewer with various issue classification modes (by source file, design unit, rule importance, rule topic), filter and summary functions; cross-probing between the detected violations, HDL source code and RTL schematic view; highlighting or filtering the violating netlist patterns on the schematic representation; additional abstract schematic-like representations of the clock and reset trees, as well as the clock domains and related crossings, where the unrelated logic is hidden; TCL-based API to trace the extracted models for custom analysis.
The above presentation functions need to be complemented with a flexible waiving mechanism to explain the corner cases, uncovered by the implemented rules. Waiving is an important function to implement a regular regression-based linting, especially in safety-critical design processes, however it should not be misused to hide the unclear bugs in the design.
Optionally, the linting-based verification process can utilize a phased-based linting. This method keeps the rules and related entry stages organized into multiple phases, each covering a specific goal. The results of the phases should be cleaned step by step. This simple technique minimizes the overlap between the detected interdependent issues, and reduces the total number of rule reports to be verified by the designer.