chipsalliance / chisel

Chisel: A Modern Hardware Design Language
https://www.chisel-lang.org/
Apache License 2.0
3.92k stars 588 forks source link

type-level width #1942

Open wentbroke opened 3 years ago

wentbroke commented 3 years ago

Type of issue: feature request Impact: API modification Phase: proposal

Currently, Chisel manages the width of Data at value-level. So we write modules like this:

class SomeModule extends Module {
  val io = IO(new Bundle {
    val rx = Input(UInt(8.W))
    val tx = Output(UInt(1.W))
  })

This is fine while we write modules as class, but when I try to write the same thing as a function, a problem arises, because I can't specify the width of UInt.

def someModule(rx: UInt(8.W)): UInt(1.W) = {
  // syntax error!
}

So we are forced to write modules as class, but there's too much boilerplate IMO...

With Scala 3 supporting match types, we can implement type-level natural numbers and its operations(+, -, *, max, min, etc...) quite easily, so I think we can provide API like this:

def someModule(rx: UInt[W8]): UInt[W1] = {
  // valid syntax
}

class SomeModule extends Module {
  val io = IO(new Bundle {
    val rx = Input(UInt[W8])
    val tx = Output(UInt[W1])
  })
}

With this API we can get a better errors from IDE because a mismatch of width emerges at compile-time.

What do you guys think? Will this change cause other problems? One thing I could imagine is that width-inference of literals won't work? I mean there's no way we could determine the width of a value "b11111" at type-level...

I know this is a big change so I don't think we can implement it soon, but when we move to Scala 3, backward-compatibility breaks anyway, so that time is the good timing to do a big change I think...

seldridge commented 3 years ago

Not having widths in the type system has been one of my biggest bugbears. Moving to dependent or refinement types for widths has always seemed logical to me.

One thing to note is that even when you do the class, technically the same problem is there. The Scala types of rx and tx are "just" UInt. There's no width information exposed to the Scala compiler.

class SomeModule extends Module {
  val io = IO(new Bundle {
    val rx: UInt = Input(UInt(8.W))
    val tx: UInt = Output(UInt(1.W))
  })

There would also need to be some sentinel value for unknown width (as having modules with purely unknown widths, e.g., an adder) are useful for library writers.

The space of this problem is related to the "Chisel Type" vs. "Hardware Type" split where this information is tracked as global, mutable state in the Builder. It may be similarly beneficial to know if something is hardware that you can connect to as opposed to relying on run-time Builder information to figure it out:

val r: HW[UInt[1]] = Reg(UInt[1])

Conversely, a problem here is that this is pushing a lot of stuff into the Scala type system. (Completely contradicting what I said before!) If you start pushing more stuff into the Scala type system, then there's less control over it by the actual compiler infrastructure. Ideally, you'd like to be able to express the API you want here (widths in the type system) where the resolution is handled by a well-structured hardware IR where the act of constructing this results in errors back to the user immediately.

This is a weird design space and I haven't fully grokked the trade-offs.

As a cool comparison, DFiant does it just like you suggest with widths, but I think it pulls in the Shapeless library to do it as opposed to leaning on Scala 3.

ekiwi commented 3 years ago

Something that might help with your immediate problem of wanting to write a function:

def someModule(rx: UInt): UInt = {
  require(rx.getWidth == 8)
  // your code here
}

Note that the width of rx may not actually be defined in the Chisel frontend, since most of the width inference (even for trivia things) happens at the firrtl level. So encoding a strict requirement like an 8bit width could be too restrictive. Chisel embraces more of a pythonic approach when it comes to widths, i.e., just work with a UInt and if the width is insufficient, one of your operations is going to cause an error from firrtl.

ekiwi commented 3 years ago

More long term your idea is pretty awesome and I think we would appreciate it if you wanted to prototype that API with Scala 3. However, just as a heads up, it will probably take years and a new major version (Chisel 4) to make such a big API change that will break all currently existing Chisel code.

sequencer commented 3 years ago

make such a big API change that will break all currently existing Chisel code.

That’s what I’m worry about :( We already have a compatibility layer to Chisel2, and users are reluctant to port to newer version of tool, I think that might be the greatest barrier :( I think the step might be droping support to 2.11; supporting 3.00; droping support to 2.12; adding another type system layer; porting original type system to new one as a compatibiliy layer; deprecate original one.

But the very first thing, “deprecating Chisel2 grammar” seems impossible to happen, since the dependence of RocketChip :(