chipsalliance / chisel

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

Fixed-Point type deprecation #3161

Open mvanbeirendonck opened 1 year ago

mvanbeirendonck commented 1 year ago

Type of issue: Feature Request

Hi! I'm filing this issue about the deprecation of the Fixed-Point type. I believe this is a specific deprecation that falls inside the following category:

For users who depend on deprecated features for which there is no obvious replacement, please reach out to the Chisel developers by filing an issue: https://github.com/chipsalliance/chisel3/issues.

I am a researcher working on hardware acceleration of cryptography. We heavily used Chisel and the Fixed-Point type for a recent FPGA accelerator we built called FPT. We aim to maintain and further develop this accelerator. We want to be able to move to updated Chisel versions in the future, hence why I am reaching out.

I understand that Fixed-Point is a niche and not-so-utilized feature. Are there any plans to add support for Fixed-Point within the new MLIR FIRRTL compiler? If not, is this a "good first issue" we can tackle? FPT was our first project using Chisel and Scala, so we are not that experienced yet.

Another possibility we looked into is to rebuild the Fixed-Point type fully within Chisel, on top of the SInt and UInt types. What is your opinion on that? Why is support preferred or necessary at the FIRRTL level?

I would appreciate any feedback!

Thanks,

Is your feature request related to a problem? Please describe. Deprecation of Fixed-Point type.

Describe the solution you'd like Feedback on best way to migrate a Fixed-Point design.

Describe alternatives you've considered

Additional context

What is the use case for implementing this feature? Being able to migrate a Fixed-Point design to future Chisel versions.

seldridge commented 1 year ago

Thanks for reaching out.

Adding Fixed Point back to Chisel as either libraries that wrap UInt/SInt or as something more clever would work.

We don't want to support these (or Interval types) natively in FIRRTL with as these were never widely used (though did have users) and they are better represented as true user-defined types.

An example of how this could work is something like. This uses Bundle as that is a type that a user is allowed to extend. Doing this inside Chisel would allow this to directly extend Data and could avoid being a Bundle. There may be a way to use an OpaqueType to use a Record to avoid having to extend Data:

class FixedPoint(val binaryPoint: Int, val dataWidth: Int) extends Bundle with Num[FixedPoint] {
  val data = UInt(dataWidth.W)

  override def do_+(that: FixedPoint)(implicit sourceInfo: SourceInfo): FixedPoint = {
    // This should align the binary points and not error!
    require(binaryPoint == that.binaryPoint)

    // This should do some implicit padding/extension and not error!
    require(dataWidth == that.dataWidth)

    val _sum = Wire(new FixedPoint(binaryPoint, dataWidth))
    _sum.data := data + this.data
    _sum
  }

  // Implement all these methods!
  override def do_-(that: FixedPoint)(implicit sourceInfo: SourceInfo): FixedPoint = ???
  override def do_*(that: FixedPoint)(implicit sourceInfo: SourceInfo): FixedPoint = ???
  override def do_/(that: FixedPoint)(implicit sourceInfo: SourceInfo): FixedPoint = ???
  override def do_%(that: FixedPoint)(implicit sourceInfo: SourceInfo): FixedPoint = ???
  override def do_<(that: FixedPoint)(implicit sourceInfo: SourceInfo): Bool = ???
  override def do_<=(that: FixedPoint)(implicit sourceInfo: SourceInfo): Bool = ???
  override def do_>(that: FixedPoint)(implicit sourceInfo: SourceInfo): Bool = ???
  override def do_>=(that: FixedPoint)(implicit sourceInfo: SourceInfo): Bool = ???
  override def do_abs(implicit sourceInfo: SourceInfo) = ???
}

class Foo extends Module {
  val a, b = IO(Input(new FixedPoint(4, 8)))
  val c = IO(Output(new FixedPoint(4, 8)))

  c := a + b
}

object Main extends App {
  println(
    ChiselStage.emitSystemVerilog(
      gen = new Foo,
      firtoolOpts = Array("-disable-all-randomization", "-strip-debug-info")
    )
  )
}

I spoke to @jackkoenig about this and he suggested using a test-driven approach where you use something like this snippet (https://scastie.scala-lang.org/3Kqw0Q9mShGkUW8ZjA0zdQ) to test the behavior of the old SFC implementation against whatever Chisel-native Fixed Point type is added.

mvanbeirendonck commented 1 year ago

Thanks for providing some guidance! I already played around a bit with your suggestion in the past week.

On our side, it actually looks most interesting to have FixedPoint as a user-defined Bundle or Record, or at least as something that we can still extend later on (so not a sealed class like UInt or SInt). The reason is that we require some more exotic functionality, e.g. explicitly allowing overflows without padding in a signal assignment. This probably shouldn't be in a base FixedPoint class, hence why we want it to be extensible. If we would implement a user-defined type extending Bundle or Record, what would be the best way to contribute it?

Regarding the test-driven approach you suggest, what would be the exact "goal"? To have the emitted FIRRTL be equal to the MLIR FIRRTL (up to the Fixed type annotations)?

seldridge commented 1 year ago

Regarding the test-driven approach you suggest, what would be the exact "goal"? To have the emitted FIRRTL be equal to the MLIR FIRRTL (up to the Fixed type annotations)?

The Scastie example doesn't show it. However, it would be ideal if the emitted Verilog was the same with the SFC and with the new Chisel-only Fixed Point types. Here is an updated Scastie which is just printing the Verilog out: https://scastie.scala-lang.org/6dogAWcSRhed7QmmlUuD8w

If we would implement a user-defined type extending Bundle or Record, what would be the best way to contribute it?

If the exotic functionality seems to make sense, I wouldn't be against adding it. If it's totally funky (or something that you wouldn't want to contribute), then you could probably do it by landing the FixedPoint support upstream in Chisel and using extension methods (implicit classes) to add only the weirder ops to even sealed/final classes.

Roughly:

final class FixedPoint

object FunkyStuff {
  implicit class FixedPointHelpers(a: FixedPoint) {
    def foo(that: a): FixedPoint = ???
  }
}
konda-x1 commented 1 year ago

Hi. My colleagues and I also have an interest in FixedPoint as we use it as a dependency in our designs. I'm also interested in doing development work to ensure it lives on in the future, so we can eventually migrate our code base to newer Chisel versions without worries. Thanks @seldridge for providing guidance in this direction!

Adding Fixed Point back to Chisel as either libraries that wrap UInt/SInt or as something more clever would work.

However, it would be ideal if the emitted Verilog was the same with the SFC and with the new Chisel-only Fixed Point types.

Out of curiosity, what would a "more clever" solution look like? Is there a possibility of a non "Chisel-only" solution? I'm thinking of something that wouldn't depend on UInt/SInt, but would rather be on equal footing with those types, just like FixedPoint was before being deprecated. Sorry, I'm not too familiar with how this all works, but I want to get a sense of just what all the possibilities are for implementation, ie. if there are alternatives to the approach of extending Bundle and wrapping UInt/SInt. I've seen the "Chisel breakdown 3" talk and now I understand that you use Chisel IR as a first step in elaboration, but I don't really know how exactly you go from that to FIRRTL (or MLIR?) and if following an approach like that is something you can actually do when writing a library that works with Chisel to add a new Chisel type. Thanks.

seldridge commented 1 year ago

More clever would push in the direction of supporting user-defined types in FIRRTL and exposing that to Chisel. Users would then be free to define any type they need and the operations it supports. The new type alias feature (https://github.com/chipsalliance/firrtl-spec/pull/106) is the first piece of this. (Admittedly, for a different use---associating names with bundles to improve Verilog emission.) However, we are a ways away from user-defined primitive operations (functions?) in FIRRTL, though. 😅

The primary motivation of the Bundle approach is that it works today without any modifications to Chisel internals while providing almost the same behavior as before (except binary point inference). Allowing Chisel users to extend Data directly would be another option.

When a Chisel program executes, it's building up "Chisel IR" operations that match almost exactly the order of execution of the Chisel program. That Chisel IR is converted to FIRRTL IR with essentially no modifications. That is then written to FIRRTL text and given to a FIRRTL compiler to convert to Verilog. That FIRRTL compiler(CIRCT) is based on MLIR and will parse the FIRRTL text into FIRRTL Dialect (which is an MLIR representation of FIRRTL). That eventually becomes Verilog after going through mandatory passes at the FIRRTL Dialect level and after being converted to other, lower-level dialects.

It is good to keep Chisel IR as essentially the same as FIRRTL IR without introducing any processing between them. E.g., it would be non-ideal to add Chisel IR operations to represent fixed point and then convert them to FIRRTL IR UInt/SInt. This would start to look like a Scala compiler between Chisel and CIRCT which we'd like to avoid.

konda-x1 commented 1 year ago

Hi. I gave it a shot at implementing a FixedPoint library. The goal was to replicate the existing FixedPoint API as faithfully as possible so existing code that relied on FixedPoint can continue to work properly with little to no changes. I stole Chisel's FixedPoint tests and it seems to be working fine with those. I also integrated it by hand into dsptools in order to test it with its test suite as well. Those tests seem to pass as well, barring 3 tests which fail due to reasons out of my control. For pretty much every FixedPoint feature there is, I did a side-by-side comparison of generated SystemVerilog for Chisel's FixedPoint and my FixedPoint, and they seem to be functionally equivalent in all cases. My FixedPoint generates a few more wires and the naming can get a little weird but it's the same code nonetheless. You can find my implementation here: https://github.com/ucb-bar/fixedpoint

Unfortunately, there were some limitations that were hard to overcome, so here's a few remarks from my experience of attempting to get this to work:

So, that's the result of my efforts. I don't know how things should proceed from here regarding FixedPoint...

jackkoenig commented 1 year ago

That's some nice work @konda-x1. I think on the Chisel side we should help you lift some of those limitations, thank you for laying them out and explaining them here. We had been resistant to allowing overriding connection behavior--I didn't realize it was even possible for the methods where it was possible--so perhaps we can come up with a better API to expose for you to use here. Same thing with cloneSuperType.

jurevreca12 commented 1 year ago

Hey, I am also a researcher. I am working on generating highly quantized neural network implementations on FPGA (https://github.com/cs-jsi/chisel4ml). I would also be very interested to see the FixedPoint type be available in some form. I am also willing to help with the implementation and testing of any proposed solution.

jurevreca12 commented 1 year ago

One thing I forgot to add. I have one issue with the original FixedPoint implementation in Chisel, and that is that that it assumes a signed number. Having unsigned fixed-point numbers would also be nice.

konda-x1 commented 1 year ago

Thanks, @jackkoenig. There is one more tiny limitation which I forgot about, and that is the inability to access the stringAccessor method when implementing the toString method. I needed this particularly in the case where my bundle is connected to DontCare, so I can add that info like the other Chisel types do. I ended up using the toString method of the underlying SInt field and then extracting the needed information via regex. Simply overriding className instead of toString for this purpose is inappropriate because toString will then also return the underlying SInt content of the Bundle.

If you guys need a hand in providing APIs that will enable the fixed-point library to overcome its current limitations, I'm willing to help. I've done my fair share of debugging during the development of this library, so I can roughly tell what goes on under the hood when the limitations are hit. My naive guess about what needs to be provided in the API would be the following:

konda-x1 commented 1 year ago

I agree with @jurevreca12 that having unsigned fixed-points would be convenient. It also shouldn't be hard to implement them, now that the signed version is more or less fleshed out. I'm interested in what the Chisel developers think of this, and, if they also agree with having an unsigned fixed-point type, I would like to consult with them about the best way to provide an API for both signed and unsigned fixed-points.

I think it goes without saying that the fixed-point type would at least have a base class that takes a generic parameter, which could either be SInt or UInt. However, providing a class like that as part of the public API would, in my opinion, make things unnecessarily complicated for the users, so I would make the generic base class—let's call it FixedPointBase—private, and extend it with concrete implementations that are based on SInt/UInt:

class SFixedPoint extends FixedPointBase[SInt]
class UFixedPoint extends FixedPointBase[UInt]

This way users wouldn't have to deal with generic parameters, especially since there are only two sensible options to choose from. If the demand arises to make FixedPointBase non-private so users can extend it with their own base types—a sign-magnitude signed-int implementation comes to mind—then we could eventually make it public, but it would definitely start out as a package-private class of the fixed-point library.

The choice in naming for SFixedPoint/UFixedPoint is intended to mimic the naming of SInt/UInt. In that case the type FixedPoint would cease to exist, and existing usages of "FixedPoint" would simply need to be replaced with "SFixedPoint".