Target Triples for Hardware Compilers

It is expected that CIRCT (or any hardware compiler generating Verilog) needs to be able to generate hardware description language (HDL) output that is compatible with how a user wants to use it. Specifically, a user probably cares about some or all of the following things:

  1. the output language, e.g., Verilog or VHDL
  2. the language standard to which the output conforms, e.g., Verilog 1995 or Verilog 2001
  3. addition or removal of specific features within a language standard
  4. the “target”, e.g., simulation, FPGA, ASIC, or none (generic)
  5. the vendor providing the tools
  6. specific tools within the vendor ecosystem

I am assuming that we don’t want to target the “lowest common denominator” of all of the above for a specific language. I also expect that a lowest common denominator may not exist for some combination of tools or it may be comically restrictive (e.g., Verilog 95).

This is motivated by the fact that we are starting to see weird tool/language incompatibilities with what Verilog CIRCT is generating. Some of the known problems are:

  • Formality places restrictions on the number of statements you can have in always blocks that are asynchronously reset [1].
  • Yosys doesn’t support multidimensional packed arrays [2], a SystemVerilog feature, yet this is an accepted style for muxes in ASIC tools.
  • Connections involving Verilog inout need to be handled differently for different tools. VCS wants alias (alias a = b;), DC wants multiple assigns (assign a = b; assign b = a;), and Verilator accepts nothing [3].

Secondarily, language specification conformance is also a company/project-level concern. Companies have policies on what language features are allowable. E.g., structs and interfaces are often lightning rod issues for teams with people arguing vehemently for one, the other, or neither. While these may show up for SystemVerilog output, it is also necessary to selectively enable/disable specific features to align with what a company/project wants.


I propose that we borrow from existing software compilers and implement a “Target Triple” (or quadruple, or quintuple…) and the ability to turn specific language features on and off. A Target Triple [4] is directly borrowed from software compilers where the messiness of generating binaries for different (1) architectures (e.g., x86 or RISC-V), (2) vendors (e.g., Apple, SiFive), and (3) operating systems (e.g., Linux or Plan9) is just accepted. There are sane defaults, but the compiler is not attempting to generate “lowest common denominator” code, e.g., Intel 8080-compatible code.

Additionally software compilers can be further qualified by choosing a specific CPU architecture, FPU, and FPU application binary interface. (See [4] for more details.)

Consider the following of how you could call firtool with a hypothetical -target option:

  • firtool -target systemverilog-fpga-xilinx would generate the latest version of SystemVerilog (2017), generate memories that can be inferred as FPGA block RAMs (amongst other things), and have optimizations enabled that are known to work well with Xilinx tools. This may result in certain SystemVerilog features being removed if Xilinx tools are known to not support them well.
  • firtool -target verilog1995-none-none would generate generic Verilog 1995, e.g., this could be necessary for some legacy tool that a user wants to use or if they want to just have extremely simple output.

What this means concretely is that the code generation portion of the CIRCT knows how to generate code for specific languages, targets, and vendors. It also implies that higher-level language constructs can be transformed into lower-level language constructs, e.g., SystemVerilog structs can be flattened to individual Verilog ports. Examples of these are discussed briefly below.


The following languages are probably worth considering:

  • verilog1995, verilog2001
  • systemverilog2005, systemverilog2009, systemverilog2017
  • vhdl1987, vhdl1993, vhdl2000, vhdl2002, vhdl2007, vhdl2008, vhdl2019

Each of these would have some “generic” target that aliases to the latest version of the standard, e.g., verilog would mean verilog2001 and systemverilog would mean systemverilog2017.


This gets messier, but it generally means what backend flow is going to be used.

  • none: a generic target which is probably just “simulation”
  • simulation: a target that is optimized for simulation, e.g., interpretation with Icarus Verilog or compilation using Verilator/VCS
  • fpga: a target that is optimized for FPGAs. This would turn on conversion of memories to Block RAM templates.
  • asic: a target that is optimized for an ASIC flow. Memories are blackboxed, non-synthesizable constructs are extracted or ifdef-gated.


  • synopsys
  • cadence
  • mentor
  • xilinx
  • intel
  • none

While I expect we need to bootstrap some of these on a need-by-need basis (i.e., if some developer needs a specific vendor optimization), I expect that this load will eventually be borne by the actual vendor. Analogously, Intel or AMD have a vested interest in gcc or clang generating good code for their CPUs and it’s worthwhile for them to invest in this. If CIRCT succeeds, vendor-borne generation improvement should follow. It’s most important for the CIRCT community to get the infrastructure right so vendor-specific improvements can happen.

Feature Selection

I expect that the target triples above will be insufficient for users that need fine-grained feature selection/deselection. For this, we should also have some option that chooses to enable or disable specific language generation features.

E.g., to disable struct and interface emission:

  • firtool -target systemverilog-none-none -mno-systemverilog-struct -mno-systemverilog-interface

Alternatively, you can go the other way and start from Verilog 2001 and enable structs:

  • firtool -target verilog-none-none -msystemverilog-struct

This is starting to push towards “code formatting” tools like clang-format, but may require heavier compiler optimizations to work, e.g., the aforementioned struct to flattened port lowering.


At a very high level, this is going to be a bunch of information captured in some table format. I.e., this tool supports features X, Y, and Z but doesn’t support A, B, and C. Each of these features should be captured in an individual option that can be enabled and disabled.

Concretely, a language is a collection of different features like how SystemVerilog provides structs, interfaces, always_ff, always_comb, logic, reg, wire, etc. These features were added in different versions of the language. Therefore, when using a specific language version, this is the same as turning on a bunch of different “feature” options. This helps with composition where language options can be turned on and off as needed.

It is expected that higher-level language features need to be convertible to lower level language features. Ideally this can be handled with rewrite patterns to make this less error prone.

Unresolved Items

Often a user will want to have a target triple that is compatible with the intersection of different tools. E.g., a user may want to target cadence tools for ASIC design, but wants to use Synopsys Formality for formal equivalence checking. In this example, it is of extreme risk for the user to generate different Verilog for the ASIC flow and for the formal equivalence checking (what’s the point then?). Due to this use case, we may need something like the ability to pass multiple targets and then use the intersection of their supported features:

  • firtool -targets verilog-asic-cadence, verilog-asic-synopsys_formality

Additionally, this highlights the issue that specific tools from the same vendor may have different requirements for the HDL they accept. This can be handled with a straightforward addition to the vendor string that identifies what tool within that vendor ecosystem the user is trying to target.

It is expected that the output of -targets may be an error if the intersection of features is the null set.


In the FPGA world, “target” is more likely a device name or a board name.

In the compiler world, the triple is often heavily interpreted by the compiler driver, in an effort to determine what features to enable or disable. I wonder if it makes sense to have a more distinct separation here between the features o the HDL generation and the way those features are enabled.
In particular this might affect your ‘target intersection’ idea. A target might have some options that it requires, some it prefers, and others that are fundamentally incompatible.

Fleshing out the compiler driver aspect might also help to avoid premature optimization here, since there are likely to be many different flows we are dealing with, many of which probably don’t look like “generate RTL for a particular synthesis tool”.

That’s a great point!

This proposal doesn’t currently address more synthesis-related “targets”. I’ve usually been thinking about this for ASIC technology, but not FPGA. To try and enumerate what these are:

  • FPGA part (or FPGA board?)
  • ASIC technology (e.g., TSMC 16)
  • ASIC standard cell library
  • ASIC technology corner (or corners)

I’m not sure how much these will affect the Verilog generated, but it’s probably at least good to figure out all the dimensions.

Does this align with what you were thinking? Is there anything else you’d add here?

I think this is worth exploring. (I also need to look into existing compiler drivers. :sweat_smile: )

What I think is free to move forward on is some of the language-specific output and feature enabling/disabling. I.e., we’re already running into issues with incomplete Verilog/SystemVerilog support and we need a good way to turn certain features, like multidimensional packed arrays, on/off.

Put differently, bullets 1–3, just relating to the language standard seem like low-level options to a CIRCT tool, while bullets 4–6 seem closer to a compiler driver that may generate options 1–3.

Hi @seldridge sorry for being behind on this thread.

I completely agree with your problem statement - we need to support “flavors” of different output, and have a convenient way to express them. We have a good way to capture the individual options and parameterize the lowering logic (LoweringOptions) but we don’t have a convenient way to reference them.

That said, I don’t think that a triple/quadruple is going to work very well: there isn’t a lot of consistency across simulation platforms, and they aren’t orthogonal to other features. Instead of an n-tuple of orthogonal axes, I feel like we have a bunch of isolated design points to reason about (including “house styles”).

The one bit of orthogonality I see is between options that are “stylistic” (e.g. line length) vs “structural” options. That could be worth supporting independent specification of these.

Instead of triples, I’d recommend we consider two different simpler approaches:

  1. Single named groups with deltas. For example, we could allow specifying yosis,disallowPackedArrays=false which would prevent "automatic variable"s, but would allow packed arrays. The degenerate version of this would just be yosis which would be user friendly, and we can add variants (e.g. yosis1.5) if there were a reason to do so. We could add things like xilinx-fpga-tool1 for some tool or sifive for the sifive house style, etc. This would be implemented with a string switch in LoweringOptions.cpp.

  2. A fancier approach would be something akin to Clang warning groups (e.g. -Wall) where certain flags twiddle a bunch of setting and leave a bunch alone. This would allow everything in #1, but would allow support for orthogonal features, e.g. -style=sifive,lowRISCFormatting) or whatever. This is similarly a switch statement over each of the things, applying different formats.