• Top
    • Documentation
    • Books
    • Recursion-and-induction
    • Boolean-reasoning
    • Projects
    • Debugging
    • Std
    • Proof-automation
    • Macro-libraries
    • ACL2
    • Interfacing-tools
    • Hardware-verification
      • Gl
      • Esim
      • Vl2014
      • Sv
      • Vwsim
      • Fgl
      • Vl
        • Syntax
        • Loader
        • Warnings
          • Lint-warning-suppression
          • Warning-basics
            • Vl-warning
            • Vl-warninglist-add-ctx
            • Vl-warninglist->types
            • Propagating-errors
            • Vl-reportcard
            • Vl-some-warning-fatalp
            • Clean-warnings
            • Lint-whole-file-suppression
            • Vl-keep-warnings
            • Warn
            • Vl-warninglist
            • Vl-remove-warnings
            • Flat-warnings
            • Vl-some-warning-of-type-p
            • Vl-msg
            • Vl-warning-add-ctx
            • Vl-print-warning
            • Vmsg-binary-concat
            • Ok
            • Vl-trace-warnings
            • Fatal
            • Vmsg
          • Getting-started
          • Utilities
          • Printer
          • Kit
          • Mlib
          • Transforms
        • X86isa
        • Svl
        • Rtl
      • Software-verification
      • Testing-utilities
      • Math
    • Warnings

    Warning-basics

    General introduction to vl-warning objects and error handling in VL.

    Introduction

    Many parts of VL can run into situations where we want to issue a warning or cause an error. For instance:

    • Our loader could encounter syntax that is simply malformed, or it could run into a construct that is legal but that we don't support yet. It could also notice valid but strange cases, e.g., 4'd16 is well-defined but weird because 16 doesn't fit in 4 bits.
    • Our transforms might run into semantically ill-formed constructs, e.g., perhaps a module declares wire [3:0] foo and then later declares integer foo, or perhaps we are trying to instantiate a module that is not defined.
    • Our vl-lint checks might notice "code smells" where even though the input Verilog is semantically well-formed, it is somehow strange and looks like it could be an error. For instance, perhaps there are multiple assignments to the same wire, or expressions like a & b where a and b have different sizes.

    Handling these many kinds of cases is tricky. In the earliest days of VL, our approach to warnings and errors was quite ad-hoc. We sometimes printed warning messages to standard output using the cw function. For more serious conditions, we sometimes caused errors using er. This ad-hoc approach had a number of problems. In particular,

    • It led us to see many of the same warnings repeatedly because our various well-formedness checks were run many times on the same modules in different stages of the translation.
    • For some warnings, we did not particularly care about the individual instances of the warning. For instance, unless we're interested in fixing the "if" statements to be ?: instead, we don't want to be told about each occurrence of an if statement. We just want a higher-level note that hey, there are 30 if-statements to clean up.
    • The warnings were not "attached" in any way to the modules that they were actually about. Practically speaking, this might mean that users might not even see the warnings that had been generated for the modules they are working on.
    • There is no way to recover form an error created with er, so if we ran into some bad problem with a particular module, it could actually prevent us from translating any of the modules. This was particularly troublesome because Verilog is such a large language that, especially in the beginning, we often ran into constructs that we did not yet support.

    These sorts of problems quickly led us to want a more coherent, global approach to dealing with warnings and errors.

    Warning Objects

    Our new approach to warning and error handling centers around explicit vl-warning objects. These objects are in many ways similar to the Exception objects found in other programming languages. Each warning has a type and a message that describes the error. These messages can conveniently make use of VL's printer, so you can directly pretty-print arbitrary Verilog constructs when writing warning messages.

    We use vl-warning objects universally, for all kinds of warnings and errors. That is, everything from the most minor of code smells (e.g., wire foo is never used for anything), to the most severe problems (e.g., the module you're instantiating isn't defined) results in a warning. To distinguish minor oddities from severe problems, our warning objects include a fatalp field.

    As a general philosophy or strategy for using these warning objects:

    • Warning messages should never be printed to standard output. Instead, we should create a vl-warning-p object that provides context and explains the problem as clearly and concisely as possible.
    • Errors should not cause sudden, unrecoverable exits. That is, er should never be used for warnings that could plausibly be triggered by malformed Verilog. (However, it is reasonable to use er in an assert-like fashion, to rule out programming problems that we believe are impossible.)
    • Non-fatal warnings should be used for any issues that are purely stylistic, "code smells," etc., such as linter-like checks.
    • Fatal warnings should be used for any issues that are truly errors. For instance: malformed syntax, conflicting declarations of some name, references to undefined modules, etc.
    • Fatal warnings may also be used when a transform encounters constructs that are valid but not supported, e.g., because we have simply not yet spent the time to implement them.

    Accumulating Warnings

    Warning objects are simple enough to understand, but what do we actually do with them? We adopt another general principle:

    • Every warning object should be associated with the top-level design elements (e.g., module, package, interface, etc.) where it was caused.

    This approach allows us to easily do many practically useful things with the warnings. For instance, it lets us easily filter out any modules that have fatal warnings; see propagating-errors. As another example, we can create reports such as a vl-reportcard that summarize the warnings in our design. These kinds of capabilities are especially useful in tools like vl-lint.

    Practically implementing this philosophy is slightly tricky.

    Deep within some particular transform, we might encounter something that is wrong and decide to issue a warning. In a typical object-oriented programming language, this would be trivial: our module class might have an add-warning that (destructively) adds a new warning to the module.

    But our programming language is truly functional, so we cannot modify existing modules. Instead, whenever some subsidiary function wants to produce a warning, its caller must take measures to ensure that the warning is eventually added to the appropriate module.

    Our usual approach is to add a warnings accumulator as an argument to our functions. Typically this argument should be named warnings. Functions that take a warnings accumulator return the (possibly extended) accumulator as one of their return values. Macros like ok, warn and fatal can assist with implementing this convention.

    At a high level, then, a function that transforms a top-level design element, e.g., a module, begins by obtaining the current warnings for the module. Using these warnings as the initial accumulator, it calls its subsidiary helpers to carry out its work. This work transforms various parts of the module, and meanwhile the warnings are perhaps extended. Finally, the function returns a new vl-module-p which is updated with the extended list of warnings.