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
Related
I'm writing an architecture that makes extensive internal use of SyncReadMem, which is intended to represent SRAM memory banks. Our current synthesis toolchain, however, does not support SRAM properly, but is fine for registers and computational logic. So, whenever we're running a synthesis build, I pass in a flag that disables the elaboration of any SyncReadMem modules and treats them purely as IO signals:
class exampleModule (synthesis: Boolean) extends Module {
val io = IO(new Bundle {
val in = Input(UInt(32.W))
val out = Output(Uint(32.W))
val synth_bundle = if (synthesis) Some(new Bundle() {
val synth_out = Output(UInt(32.W))
val synth_in = Input(UInt(4.W))
}) else None
}
val mem_read = if (synthesis) {
val memory = SyncReadMem(1 << 32, UInt(4.W))
memory.read(io.in)
} else {
io.synth_bundle.get.synth_out := io.in
io.synth_bundle.get.synth_in
}
io.out := mem_read * 2.U
}
This, to my understanding, should properly elaborate all of my logic (ie not optimize anything out that I want to be there), and won't elaborate any of the memory whenever I have synthesis builds enabled.
The problem I'm running into is that for really hierarchical modules where a deeply ingrained module needs something like this, it requires every module above it to implement this synth_bundle style IO, requiring a bit of writing and adding no functionality. Is there some easier / more canonical way to do this?
Thank you
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))
//...
//...
}
If we run the following Chisel3 code
class Controller extends Module {
val io = IO(new Bundle {
})
val sff = Module(new SFF)
val frame: Vec[UInt] = Reg(Vec(ProcedureSpaceSize, Integer32Bit))
for(i <- 0 until ProcedureSpaceSize)
frame(i) := 99.U
sff.io.inputDataVector := frame
}
class SFF extends Module {
val io = IO(new Bundle {
val inputDataVector: Vec[UInt] = Input(Vec(ProcedureSpaceSize, Integer32Bit))
})
}
in REPL debug mode. First do
reset;step
peek sff.io_inputDataVector_0;peek sff.io_inputDataVector_1;peek sff.io_inputDataVector_2
The REPL returns
Error: exception Error: getValue(sff.io_inputDataVector_0) returns value not found
Error: exception Error: getValue(sff.io_inputDataVector_1) returns value not found
Error: exception Error: getValue(sff.io_inputDataVector_2) returns value not found
Then do
eval sff.io_inputDataVector_0
which will be a success, yielding
...
resolve dependencies
evaluate sff.io_inputDataVector_0 <= frame_0
evaluated sff.io_inputDataVector_0 <= 99.U<32>
Then perform the above peek again
peek sff.io_inputDataVector_0;peek sff.io_inputDataVector_1;peek sff.io_inputDataVector_2;
This time, it returns
peek sff.io_inputDataVector_0 99
peek sff.io_inputDataVector_1 99
peek sff.io_inputDataVector_2 99
which is more expected.
Why does the REPL act in this way? Or was there something I missed? Thanks!
*chisel-iotesters is in version 1.4.2, and chiseltest is in version 0.2.2. Both should be the newest version.
The firrtl interpreter REPL does not necessarily compute or store values that on a branch of a mux that is not used. This can lead to problems noted above like
Error: exception Error: getValue(sff.io_inputDataVector_0) returns value not found.
eval can be used to force unused branches to be evaluated anyway. The REPL is an experimental feature that has not had a lot of use.
treadle is the more modern chisel scala-based simulator. It is better supported and faster than the interpreter. It has a REPL of its own, but does not have an executeFirrtlRepl equivalent.
It must be run from the command line via the ./treadle.sh script in the root directory. One can also run sbt assembly to create a much faster launching jar that is placed in utils/bin. This REPL also has not been used a lot but I am interested on feedback that will make it better and easier to use.
I think the problem that you are seeing is that all your wires are being eliminated due to dead code elimination. There are a couple of things you should try to fix this.
Make sure you have meaningful connections of your wires. Hardware that does not ultimately affect an output is likely to get eliminated. In your example you do not have anything driving a top level output
You probably need your circuit to compute something with those registers. If the registers are initialized to 99 then constant propagation will likely eliminate them. I'm not sure what you are trying to get the circuit to do so it is hard to make a specific recommendation.
If you get the above done I think the repl will work as expected. I do have a question about which repl you are using (there are two: firrtl-interpreter and treadle) I recommend using the latter. It is more modern and better supported. It also has two commands that would be useful
show lofirrtl will show you the lowered firrtl, this is how you can see that lots of stuff from the high firrtl emitted by chisel3 has been changed.
symbol . shows you all symbols in circuit (. is a regex that matches everything.
Here is a somewhat random edit of your circuit that drives and output based on your frame Vec. This circuit will generate firrtl that will not eliminated the wires you are trying to see.
class Controller extends Module {
val io = IO(new Bundle {
val out = Output(UInt(32.W))
})
val sff = Module(new SFF)
val frame: Vec[UInt] = Reg(Vec(ProcedureSpaceSize, Integer32Bit))
when(reset.asBool()) {
for (i <- 0 until ProcedureSpaceSize) {
frame(i) := 99.U
}
}
frame.zipWithIndex.foreach { case (element, index) => element := element + index.U }
sff.io.inputDataVector := frame
io.out := sff.io.outputDataVector.reduce(_ + _)
}
class SFF extends Module {
val io = IO(new Bundle {
val inputDataVector: Vec[UInt] = Input(Vec(ProcedureSpaceSize, Integer32Bit))
val outputDataVector: Vec[UInt] = Output(Vec(ProcedureSpaceSize, Integer32Bit))
})
io.outputDataVector <> io.inputDataVector
}
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
}
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.