But that's not why we're here today. Instead I will talk about a bad coding style that is so prevalent that I feel it needs some mentioning. Consider the following verilog code of a component that is most likely pretty worthless in practice, but will serve well as our example
module rst_all (input clk, input rst, input valid_i, input [3:0] data_i, output reg valid_o, output reg [3:0] data_o); reg [2:0] count; reg [3:0] data_r; always @(posedge clk) begin if (rst) begin data_o <= 4'd0; data_r <= 4'd0; valid_o <= 1'b0; count <= 3'd0; end else begin if (valid_i) count <= count + 1; data_r <= data_i; data_o <= data_r + count; valid_o <= valid_i; end end endmodule
All our registers are being reset, as can be seen in the following screenshot from the elaborated code in Vivado.
Now, as good RTL designers we want to minimize the load on the reset network, so we start looking for things that don't really need to be reset. Both data_o and data_r are unnecessary to reset if we only look at the data when valid_o is asserted. Let's remove them and try again
module rst_bad (input clk, input rst, input valid_i, input [3:0] data_i, output reg valid_o, output reg [3:0] data_o); reg [2:0] count; reg [3:0] data_r; always @(posedge clk) begin if (rst) begin //data_o <= 4'd0; //data_r <= 4'd0; valid_o <= 1'b0; count <= 3'd0; end else begin if (valid_i) count <= count + 1; data_r <= data_i; data_o <= data_r + count; valid_o <= valid_i; end end endmodule
Whoa.. what just happened The reset inputs are gone, but we suddenly have two new muxes in the design and clock enable pins on the flip flops. And what's worse, we still have the same load on the reset net. How is this possible?!?! Well, the thing is that we haven't specified what to do with the data_r and data_o signals when rst is low. Therefore, according to verilog rules (the same thing applies to VHDL designs too), we must keep the old value. This is most likely not what we wanted to do. Still, I see this design pattern all the time, and no one is warning about it. What should we do then? One way is to replicate the assignments to data_r and data_o also in the reset section, but that's pretty awkward. The real solution is much simpler, but will probably cause heart attacks among conservative RTL designers.
We put the reset handling at the end of the process. Oh yes, I just did! And no one can stop me! It looks like this by the way
module rst_good (input clk, input rst, input valid_i, input [3:0] data_i, output reg valid_o, output reg [3:0] data_o); reg [2:0] count; reg [3:0] data_r; always @(posedge clk) begin begin if (valid_i) count <= count + 1; data_r <= data_i; data_o <= data_r + count; valid_o <= valid_i; end if (rst) begin //data_o <= 4'd0; //data_r <= 4'd0; valid_o <= 1'b0; count <= 3'd0; end end endmodule
and the generated design looks like this.
Problem solved!
A few additional notes:
- I have put together a FuseSoC core with the source and some notes on how to simulate and build the designs mentioned here in a git repo
- This was an example with synchronous resets, but the same thing is true for asynchronous.
- I have no idea why Vivado chooses to instantiate the muxes however, as it could just use the CE port directly. Other tools do that. My guess is that CE maybe is always active high, and the mux serves as an inverter.
- If you only target FPGA and the reset is just a power-on-reset, you don't need to reset at all. Just provide an init value if you must. Don't be afraid. It works, and will save some load on your reset net.
- There are many many more things to say about resets. But we stop here for today