Allow zero length VecInit()
Type of issue: Feature Request
Allow zero length VecInit()
Is your feature request related to a problem? Please describe.
If I try to create a zero length VecInit(), I get "Vec hardware values are not allowed to be empty"
Describe the solution you'd like
Allow zero length VecInit()
Describe alternatives you've considered
Iffy code in modelling.
Additional context
What is the use case for implementing this feature?
Use Case: Zero-Length VecInit() in Chisel Modeling and Testbenches
Chisel’s power as a hardware DSL extends beyond synthesizable RTL—it enables expressive, parameterized modeling and testbenches.
An example is the use of zero-length VecInit():
- Zero-length Vecs are not hardware (no wires or registers are emitted), but are useful for parameterized designs.
- They allow you to write generic code that naturally handles edge cases (like zero ports, lanes, or slices) without special-case logic or errors.
- This is especially valuable in non-synthesizable modeling and testbenches, where parameter sweeps and corner cases (including zero) are common.
- Even in RTL, zero-length Vecs can simplify parameterized code, letting cases reduce to zero cleanly for some configurations.
Chisel lets you:
- Write one unified, hardware-like model for both RTL and testbenches.
- Use constructs like
VecInit()with zero length for flexible, robust parameterization. - Avoid hacks, special cases, or non-hardware workarounds found in other HDLs.
Bottom line:
Chisel’s support for zero-length VecInit() is a practical example of how its modeling capabilities go beyond RTL, making it easier to write, test, and verify highly parameterized hardware
Chisel solves the underlying problem that made Verilog so hard to use for modeling and testbenches—a problem that led to a patchwork of workarounds like SystemC, cocotb, DPI/FFI hacks, and custom event loops.
Instead of relying on these ad-hoc paradigms, Chisel lets you express parallelism and hardware behavior directly and naturally, all within a unified, hardware-centric DSL.
By writing everything in Chisel, you get clearer, more maintainable, and more accurate models and testbenches—without the complexity and fragmentation of mixing
Loosely-Timed Models with DecoupledIO
Chisel makes it easy to build loosely-timed models directly in the hardware DSL using DecoupledIO.
By leveraging DecoupledIO and its built-in ready/valid handshake, you can naturally express transaction-level interfaces, backpressure, and flow control.
This allows you to model systems where timing between transactions is flexible, without needing to manually manage threads or coroutines—enabling high-level, protocol-faithful modeling that remains hardware-consistent and easy to manually manage threads or coroutines—enabling high-level, protocol-faithful modeling that remains hardware-consistent and easy to reason about.
This seems reasonable to me.
This seems reasonable to me.
A couple of years back, similarly, UInt could not be 0 bits wide, I think they can now?
It's not that anyone doesn't think empty VecInit() should work, it's actually just that it's unclear how to implement it. Chisel requires a type object to know the type of something and when you have an empty VecInit, there is no "prototype" object. The compromise we came up with several years ago is that you can use VecInit.fill(0)(<object here>). It's not great, obviously it would be best if VecInit() worked, but it's something.
Interesting... I dont have any suggestions how to avoid iffy code here on the user side. As I understand, Java(?) cant express the type of an empty list(?).
I'm not sure what Java does here, it's fine in Scala--it will infer the the type parameter based on any relevant annotated types or use the bottom type (the subclass of all types) Nothing if there is no context:
$ scala-cli -S 2.13
Welcome to Scala 2.13.16 (Java HotSpot(TM) 64-Bit Server VM, Java 21.0.7).
Type in expressions for evaluation. Or try :help.
scala> val x: List[Int] = List()
val x: List[Int] = List()
scala> val y = List()
val y: List[Nothing] = List()
The problem for Chisel is that we need a runtime object, not just a compile-time type.
With reworking of Chisel's internals it might be possible to do--make it such that we can still create a Vec without a type object, but it violates a lot of design assumptions so I'm not sure it would work. For example, it is currently legal to index a size zero Vec:
class Foo extends Module {
val in = IO(Input(Vec(0, UInt(8.W))))
val out = IO(Output(UInt(8.W)))
out := in(0.U(0.W))
}
This is a warning and could become an error, but as long as that's supported, that dynamic indexing call needs to return something of the same type as the type argument. That would be impossible to implement for an empty VecInit even with internals reworking (but again, this should be an error so that would help).
Another approach is something I'd like to do which is to change Data to become more typeclass-y rather than inheritance-y. That would make this possible but is a fairly large change to the public API so will have to be done carefully.