[HW] Encoding enum types and values
With https://github.com/llvm/circt/pull/3452, rudimentary enum support is now available in CIRCT. However, this commit leaves encoding of the enum up to a lowering pass (which currently, in #3478, will leave this up to the synthesis tool).
Some things to consider in this domain:
Base types
Specifying a base type for an enum should restrict the encoding space and be reflected in the emitted SV and by default, an enum should be emitted as an unsigned integer: e.g.:
hw.typedecl @myEnum : !hw.enum[i5]<A, B, C>
hw.typedecl @myEnum2 : !hw.enum<D, E, F>
emits as
typedef enum bit [4:0] {A, B, C} myEnum_t;
typedef enum unsigned integer {D, E, F} myEnum2_t;
Encoding values:
Allow specifying user-defined encodings for enum fields in either binary or decimal.
hw.typedecl @myEnum : !hw.enum[i3]<A : 1, B : b10, C>
emits as
typedef enum bit [2:0] {A = 3'b001, B = 3'b010, C} myEnum_t;
In case an explicit base type has been defined, we should check whether the requested encoding fits within the specified bitvector.
Q: how do we handle invalid/partial encodings?
hw.typedecl @myEnum : !hw.enum[i2]<A : 3, B, C>
Depending on how downstream lowering and tooling interprets the above, it may (incrementally assigned: B==4, C==5) or may not (assigned to available encodings B==0, C==1) be illegal. My hunch is that codifying something here can lead into a rabithole that may not be relevant for anyones immediate use-case. My suggestion would be to only verify that requested encoding values fit within the base type, and then let downstream tooling decide whether or not something is an invalid enum.
Bitvector casting
Integer casting allows interpreting enums as bitvectors and vice versa:
%0 = hw.constant 3 : i3 // A, B
%1 = hw.enum_cast %0 : i3 to !hw.enum[i3]<A = 0b1, B = 0b10, C>
%2 = hw.enum_cast %1 : !hw.enum[i3]<A, B, C> to i5
Q: Should we only allow bitcasting on enums with explicit base types? Or should enums by default be considered as having a i32 base type unless explicitly specified?
FSM encoding
Currently, FSM conversion is the only user of the hw enum type. In this conversion , we also have explicit awareness of where our state register is, and how we might want the synthesis tool to infer our state machine. FSM encoding attributes are generally supported across different tools (vivado, quartus) and it would be due diligence to consider whether such FSM encoding attributes are an inherent part of an enum type or if it is specific to the site which uses the enum type.
If part of the enum type, there could be 'smarts' which determine that some using ops (like sv.reg) needs to be emitted with an fsm encoding attribute:
hw.typedecl @myEnum : !hw.enum['onehot']<A, B, C>
%state_reg = sv.reg : !hw.enum<A, B, C>
// emits as
typedef enum {A, B, C} myEnum_t;
(* fsm_encoding = "one_hot" *) myEnum_t state_reg ;
If not, the attribute could be attached directly to the using op, possibly as an attribute of the fsm dialect;
hw.typedecl @myEnum : !hw.enum<A, B, C>
%state_reg = sv.reg : !hw.enum<A, B, C> attributes {fsm.encoding = 'onehot'}
Which is lowered to an appropriate SV attribute based on the target tooling:
hw.typedecl @myEnum : !hw.enum<A, B, C>
// circt-opt --lower-fsm-encodings="target=quartus"
%state_reg = sv.reg svattrs [#sv.attribute<"syn_encoding" = "one-hot">] : !hw.enum<A, B, C>
// circt-opt --lower-fsm-encodings="target=vivado"
%state_reg = sv.reg svattrs [#sv.attribute<"fsm_encoding" = "one_hot">] : !hw.enum<A, B, C>