I have a floating point add chisel module that I want to use which has a few stages of pipelines. I want to make it stallable so that I can fit it into a pipeline which may not be able to consume the output data at any give time and therefore I would like the partially calculated adds to be stored in the module.
I was initially hoping that I could just add an enable signal and then just add it as another condition for updating the various registers, but unfortunately the module contains a lot of statements of the form Reg(next=xxx). I was curious to see what would happen if I just assigned the register to itself even though its input was already assigned through next=xxx so I made a test module and got some (in my opinion) strange results.
Here is the scala:
package Hello
import Chisel._
class Hello extends Module {
val io = new Bundle {
val in = UInt(INPUT, 8)
val en = Bool(INPUT)
val out = UInt(OUTPUT, 8)
}
val test_reg = Reg(next = io.in)
io.out := test_reg
when (!io.en) {
test_reg := test_reg
}
}
object Hello {
def main(args: Array[String]): Unit = {
}
}
and here is the resulting verilog:
module Hello(input clk,
input [7:0] io_in,
input io_en,
output[7:0] io_out
);
reg [7:0] test_reg;
wire[7:0] T0;
wire T1;
`ifndef SYNTHESIS
// synthesis translate_off
integer initvar;
initial begin
#0.002;
test_reg = {1{$random}};
end
// synthesis translate_on
`endif
assign io_out = test_reg;
assign T0 = T1 ? test_reg : io_in;
assign T1 = io_en ^ 1'h1;
always @(posedge clk) begin
if(T1) begin
test_reg <= test_reg;
end else begin
test_reg <= io_in;
end
end
endmodule
What is curious to me is that the verilog appears to be nearly implementing the enable in two different ways. It uses T1 (!en) to mux between test_reg and io_in and labels the output T0. If T0 was fed unconditionally as an input to test_reg, I think this would have the desired functionality. Instead T0 is completely ignored and T1 is used in the if else block to select whether the register should update or not.
Ultimately, this example seems like it still works correctly but now I am a little scared to used in the more complex floating point unit if it behaves kind of unexpectedly in the simple case.
Is there a more elegant way to halt the pipeline of a floating point add module? I initially liked the above approach because I could just add a when(!en) block at the end that just writes the output of all state to its input. I think another approach would be to replace any instance of Reg(next=xxx) with Reg() and then a when(en) {reg := next} block that updates the register. Ultimately I am trying to learn Chisel so I am wondering what the cleanest way to do it is.
For reference, the floating point add module I am talking about is: https://github.com/zhemao/chisel-float/blob/master/src/main/scala/FPAdd.scala
val test_reg = Reg(next = io.in)
io.out := test_reg
when (!io.en) {
test_reg := test_reg
}
I believe this is bad coding practice - either use Reg(next=...)
or specify the next value via test_reg := ...
but don't mix both! First, it's ambiguous to the reader which writer should take precedence (although the answer is "last writer wins"). Second, when the reader sees Reg(next=...)
he is probably not expecting to see that writer overwritten elsewhere in the code.
Write it as:
val test_reg = Reg(io.in.clone())
when (io.en) {
test_reg := io.in
}
It will still generate some unused signals, but the code's intent is much clearer.
always @(posedge clk) begin
if(io_en) begin
test_reg <= io_in;
end
end