Can I compute constants in software before Chisel begins designing hardware? - chisel

I'm new to Chisel, and I was wondering if it's possible to calculate constants in software before Chisel begins designing any circuitry. For instance, I have a module which takes one parameter, myParameter, but from this parameter I'd like to derive more variables (constant1 and constant2) that would be later used to initialize registers.
class MyModule(myParameter: Int) extends Module {
val io = IO(new Bundle{
val in = Input(SInt(8.W))
val out = Output(SInt(8.W))
})
val constant1 = 2 * myParameter
val constant2 = 17 * myParameter
val register1 = RegInit((constant1).U(8.W))
val register2 = RegInit((constant2).U(8.W))
//...
//...
}
Is there a way to configure Chisel's functionality so that an instance of MyModule(2) will first evaluate all Scala vals in software: constant1 = 2 * 2 = 4 and constant2 = 17 * 2 = 34. Then proceed to instantiate and initialize registers register1 = RegInit(4.U(8.W)) and register2 = RegInit(34.U(8.W))?

I was wondering if it's possible to calculate constants in software before Chisel begins designing any circuitry
Unless I'm misunderstanding your question, this is, in fact, how Chisel works.
Fundamentally, Chisel is a Scala library where the execution of your compiled Scala code creates hardware. This means that any pure-Scala code in your Chisel only exists at elaboration time, that is, during execution of this Scala program (which we call a generator).
Now, values in your program are created in sequential order as defined by Scala (and more-or-less the same as any general purpose programming language). For example, io is defined before constant1 and constant2 so the Chisel object for io will be created before either constants are calculated, but this shouldn't really matter for the purposes of your question.
A common practice in Chisel is to create custom classes to hold parameters when you have a lot of them. In this case, you could do something similar like this:
// Note this doesn't extend anything, it's just a Scala class
// Also note myParameter is a val now, this makes it accessible outside the class
class MyParameters(val myParameter: Int) {
val constant1 = 2 * myParameter
val constant2 = 17 * myParameter
}
class MyModule(params: MyParameters) extends Module {
val io = IO(new Bundle{
val in = Input(SInt(8.W))
val out = Output(SInt(8.W))
})
val register1 = RegInit((params.constant1).U(8.W))
val register2 = RegInit((params.constant2).U(8.W))
//...
//...
}

Related

Create a generic Bundle in Chisel3

In Chisel3, I want to create a generic Bundle ParamsBus with parameterized type.
Then I follow the example on the Chisel3 website:
class ParamBus[T <: Data](gen: T) extends Bundle {
val dat1 = gen
val dat2 = gen
override def cloneType = (new ParamBus(gen)).asInstanceOf[this.type]
}
class TestMod[T <: Data](gen: T) extends Module {
val io = IO(new Bundle {
val o_out = Output(gen)
})
val reg_d = Reg(new ParamBus(gen))
io.o_out := 0.U
//io.o_out := reg_d.dat1 + reg_d.dat2
dontTouch(reg_d)
}
However, during code generation, I have the following error:
chisel3.AliasedAggregateFieldException: Aggregate ParamBus(Reg in TestMod) contains aliased fields List(UInt<8>)...
at fpga.examples.TestMod.<init>(test.scala:20)
Moreover, if I exchange the two lines to connect io.o_out, another error appears:
/home/escou64/Projects/fpga-io/src/main/scala/examples/test.scala:23:34: type mismatch;
found : T
required: String
io.o_out := reg_d.dat1 + reg_d.dat2
^
Any idea of the issue ?
Thanks for the help!
The issue you're running into is that the argument gen to ParamBus is a single object that is used for both dat1 and dat2. Scala (and thus Chisel) has reference semantics (like Java and Python), and thus dat1 and dat2 are both referring to the exact same object. Chisel needs the fields of Bundles to be different objects, thus the aliasing error you are seeing.
The easiest way to deal with this is to call .cloneType on gen when using it multiple times within a Bundle:
class ParamBus[T <: Data](gen: T) extends Bundle {
val dat1 = gen.cloneType
val dat2 = gen.cloneType
// Also note that you shouldn't need to implement cloneType yourself anymore
}
(Scastie link: https://scastie.scala-lang.org/mJmSdq8xSqayOceSjxHkRQ)
This is definitely a bit of a wart in the Chisel3 API because we try to hide the need to call .cloneType yourself, but least as of v3.4.3, this remains the case.
Alternatively, you could wrap the uses of gen in Output. It may seem weird to use a direction here but if all directions are Output, it's essentially the same as having no directions:
class ParamBus[T <: Data](gen: T) extends Bundle {
val dat1 = Output(gen)
val dat2 = Output(gen)
}
(Scastie link: https://scastie.scala-lang.org/TWajPNItRX6qOKDGDPnMmw)
A third (and slightly more advanced) technique is to make gen a 0-arity function (ie. a function that takes no arguments). Instead of gen being an object to use as a type template, it's instead a function that will create fresh types for you when called. Scala is a functional programming language so functions can be passed around as values just like objects can:
class ParamBus[T <: Data](gen: () => T) extends Bundle {
val dat1 = gen()
val dat2 = gen()
}
// You can call it like so:
// new ParamBus(() => UInt(8.W))
(Scastie link: https://scastie.scala-lang.org/JQ7D8VZsSCWP2i6DWJ4cLA)
I tend to prefer this final version, but I understand it can be more daunting for new users. Eventually I'd like to fix the issue you're seeing with a more direct use of gen, but these are ways to deal with the issue for the time being.

Chisel Bundle connection and type Safety

Just noticed you can do something like:
class MyBundle1 extends Bundle {
val a = Bool()
val x = UInt(2.W)
val y = Bool()
}
class MyBundle2 extends Bundle {
val x = Bool()
val y = Bool()
}
class Foo extends Module {
val io = IO(new Bundle {
val in = Input(new MyBundle1)
val out = Output(new MyBundle2)
})
io.out := io.in
}
not get an error and actually get the correct or, at least, expected verilog. One would think that Chisel was type-safe wrt to Bundles in this sort of bulk connection. Is this intentional? If so, is there any particular reasons for it?
The semantics of := on Bundle allow the RHS to have fields that are not present on the LHS, but not vice-versa. The high-level description of x := y is "drive all fields of x with corresponding fields of y."
The bi-directional <> operator is stricter.
I can't speak precisely to the reasoning, but connection operators are often a matter of taste with respect to "do what I mean." The idea of including alternate forms of connection operators for future releases is an ongoing discussion, with the threat of "operator glut" being weighed against the ability of users to specify exactly what they'd like to do.

how to override/extend chisel signal naming

It seems not an easy thing to do or even impossible, but we are using a naming convention that prefix or postfix signals with "i_" or "o_" for inputs/outputs in verilog.
Is there some method to mess with or override inside the chisel library to to that?
I saw that except for clock and reset, all signals have "io" prefix.
Is is possible to use just "i" for input and "o" for output?
The easiest way to do this is to probably use a MultiIOModule. However, you can also do it with suggestName. Both approaches are shown below.
MultiIOModule
This a more flexible Module that lets you call the IO method to add ports to a module more than once. (Module requires that you define an io member and only allows you to call IO once.)
Because MultiIOModule frees you from the constraints of val io = ... you can use the prefix/postfix naming that you want with the names of your vals. Reflective naming will then get these right in the generated Verilog.
Consider the following Chisel code:
import chisel3._
import chisel3.stage.{ChiselStage, ChiselGeneratorAnnotation}
class Foo extends MultiIOModule {
val i_bar = IO(Input(Bool()))
val o_baz = IO(Output(Bool()))
o_baz := ~i_bar
}
(new ChiselStage).execute(Array.empty, Seq(ChiselGeneratorAnnotation(() => new Foo)))
This produces the following Verilog:
module Foo(
input clock,
input reset,
input i_bar,
output o_baz
);
assign o_baz = ~ i_bar;
endmodule
SuggestName
As an alternative, you can use the suggestName method to change the name to be different from what reflective naming (getting the name from the name of the val) would use.
Using suggestName you can coerce the names to be whatever you want. The following Chisel produces the same Verilog as above:
class Foo extends MultiIOModule {
val a = IO(Input(Bool())).suggestName("i_bar")
val b = IO(Output(Bool())).suggestName("o_baz")
b := ~a
}

chisel-firrtl combinational loop handling

I have met some troubles while simulating design that contains comb-loop. Firrtl throws exception like
"No valid linearization for cyclic graph"
while verilator backend goes normal with warnings.
Is it possible to simulate such design with firrtl backend?
And can we apply --no-check-comb-loops not for all design but for some part of it while elaborating?
Example code here:
import chisel3._
import chisel3.iotesters.PeekPokeTester
import org.scalatest.{FlatSpec, Matchers}
class Xor extends core.ImplicitModule {
val io = IO(new Bundle {
val a = Input(UInt(4.W))
val b = Input(UInt(4.W))
val out = Output(UInt(4.W))
})
io.out <> (io.a ^ io.b)
}
class Reverse extends core.ImplicitModule {
val io = IO(new Bundle {
val in = Input(UInt(4.W))
val out = Output(UInt(4.W))
})
io.out <> util.Reverse(io.in)
}
class Loop extends core.ImplicitModule {
val io = IO(new Bundle {
val a = Input(UInt(4.W))
val b = Input(UInt(4.W))
val mux = Input(Bool())
val out = Output(UInt(4.W))
})
val x = Module(new Xor)
val r = Module(new Reverse)
r.io.in <> Mux(io.mux, io.a, x.io.out)
x.io.a <> Mux(io.mux, r.io.out, io.a)
x.io.b <> io.b
io.out <> Mux(io.mux, x.io.out, r.io.out)
}
class LoopBackExampleTester(cc: Loop) extends PeekPokeTester(cc) {
poke(cc.io.mux, false)
poke(cc.io.a, 0)
poke(cc.io.b, 1)
step(1)
expect(cc.io.out, 8)
}
class LoopBackExample extends FlatSpec with Matchers {
behavior of "Loop"
it should "work" in {
chisel3.iotesters.Driver.execute(Array("--no-check-comb-loops", "--fr-allow-cycles"), () => new Loop) { cc =>
new LoopBackExampleTester(cc)
} should be(true)
}
}
I will start by noting that Chisel is intended to make synchronous, flop-based, digital design easier and more flexible. It is not intended to represent all possible digital circuits. Fundamentally, Chisel exists to make the majority of stuff easier while leaving things that tend to be more closely coupled to implementation technology (like Analog) to Verilog or other languages.
Chisel (well FIRRTL) does not support such apparent combinational loops even if it possible to show that the loop can't occur due to the actual values on the mux selects. Such loops break timing analysis in synthesis and can make it difficult to create a sensible circuit. Furthermore, it isn't really true that the loop "can't occur". Unless there is careful physical design done here, there will likely be brief moments (a tiny fraction of a clock cycle) where there will be shorts which can cause substantial problems in your ASIC. Unless you are building something like a ring oscillator, most physical design teams will ask you not to do this anyway. For the cases where it is necessary, these designs typically are closely tied to the implementation technology (hand designed with standard cells) and as such are not really within the domain of Chisel.
If you need such a loop, you can express it in Verilog and instantiate the design as a BlackBox in your Chisel.

Chisel/FIRRTL constant propagation & optimization across hierarchy

Consider a module that does some simple arithmetic and is controlled by a few parameters. One parameter controls the top level behavior: the module either reads its inputs from its module ports, or from other parameters. Therefore, the result will either be dynamically computed, or statically known at compile (cough, synthesis) time.
The Verilog generated by Chisel has different module names for the various flavors of this module, as expected. For the case where the result is statically known, there is a module with just one output port and a set of internal wires that are assigned constants and then implement the arithmetic to drive that output.
Is it possible to ask Chisel or FIRRTL to go further and completely optimize this away, i.e. in the next level of hierarchy up, just replace the instantiated module with its constant and statically known result? (granted that these constant values should by optimized away during synthesis, but maybe there are complicated use cases where this kind of elaboration time optimization could be useful).
For simple things that Firrtl currently knows how to constant propagate, it already actually does this. The issue is that it currently doesn't const prop arithmetic operators. I am planning to expand what operators can be constant propagated in the Chisel 3.1 release expected around New Years.
Below is an example of 3.0 behavior constant propagating a logical AND and a MUX.
import chisel3._
class OptChild extends Module {
val io = IO(new Bundle {
val a = Input(UInt(32.W))
val b = Input(UInt(32.W))
val s = Input(Bool())
val z = Output(UInt(32.W))
})
when (io.s) {
io.z := io.a & "hffff0000".U
} .otherwise {
io.z := io.b & "h0000ffff".U
}
}
class Optimize extends Module {
val io = IO(new Bundle {
val out = Output(UInt())
})
val child = Module(new OptChild)
child.io.a := "hdeadbeef".U
child.io.b := "hbadcad00".U
child.io.s := true.B
io.out := child.io.z
}
object OptimizeTop extends App {
chisel3.Driver.execute(args, () => new Optimize)
}
The emitted Verilog looks like:
module Optimize(
input clock,
input reset,
output [31:0] io_out
);
assign io_out = 32'hdead0000;
endmodule