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:
- the output language, e.g., Verilog or VHDL
- the language standard to which the output conforms, e.g., Verilog 1995 or Verilog 2001
- addition or removal of specific features within a language standard
- the “target”, e.g., simulation, FPGA, ASIC, or none (generic)
- the vendor providing the tools
- 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 wantsalias
(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.
Proposal
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.
Languages
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
.
Targets
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 orifdef
-gated.
Vendors
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.
Implementation
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.