There are multiple ways to do this.
Yes, you can use a generate here, the problem is all you can do is generate the blocks in a loop. Then you need to connect them and that can be just as messy as not doing anything at all.
You could also use a script, this is nice to quickly put something almost bug free, but the problem I had here is often people forget of the script, lose it, make manual changes on the output, then someone regenerates the file and wipes away changes and so on. Another problem with register scripts is you often have one or two registers that just do not fit the standard writing way, like, for example, some complex write-only reg that initiates some state.
So for register files I usually use macros. Then all simple registers, perhaps 90% of then, are coded by macros and the few more complex ones I just write the RTL for them.
Ex:
I create a set of modules, one for each register type: rwreg, roreg, rwregpl (rw reg with parallel load).
I specify a format. Each register should have a parameter with its address, one with the default value, and one with a mask (if a bit is 1 it is valid if it is 0 it is not valid). Then I define the ins/outs. Each one needs a clock, write enable, write value, address, and then two outputs, a val (current value), and a rdata (current value if addr == ADDR parameter or 0 if it does not match).
Here is a simple example of a r/w reg...
module rwreg #(ADDRS = ..., ) (input clk, input rst_n, ....)
reg [31:0] regis;
// here would be a simple R/W reg. Note that the MASK is anded with the data in and data out. This makes sure synthesis will wipe away unused bits.
always @(posedge clk or negedge rst_n)
if (!rst_n) regis <= DEF
else if (wr_en && addr == ADDRS) regis <= datin & MASK;
end
// val is the current value. rdata is the value if the register is selected, or zero otherwise.
assign val = regis & MASK;
assign rdata = (addr == ADDRS) ? regis & MASK : 32'd0;
Then I create a macro, one for each type. Here is an example using Verilog2001:
' define REGPLACE (inst, rdo, val, ADDRS, DEFS, MASKS) \
rwreg \
#(.ADDR(ADDRS), .DEF(DEFS), .MASK(MASKS)) name(.clk(CLK), .datin(data_in), .addr(addr), .wr(wr_en), .rdata(rdo), .val(val));
' define ROREGPLACE(...
...
Then just instantiate them together with any register mapped signals:
' REGPLACE(i_rstval, rstval_rdata, rstval, RSTVAL_ADDR, RSTVAL_DEF)
assign rstval_rst = rstval[RSTVAL_RST_Pos];
...
' REGPLACE(i_someotherreg, someotherreg, someotherreg_val, SOMEOTHERREG_ADDR, SOMEOTHERREG_DEF)
' REGPLACERO(...)
...
If you have SystemVerilog you can reduce this a bit because of the doubletick:
'define REGPLACE(name, NAME) \
rwreg #(.ADDR(NAME''_ADDR), .DEF(NAME''_DEF), .MASK(NAME''_MASK) i_name (.clk(CLK), .datin(data_in), .addr(addr), .wr(wr_en), .rdata(name''_rdata), .val(name));
...
then instantiate:
' REGPLACE(rstval, RSTVAL)
' REGPLACE(someotherreg, SOMEOTHERREG)
...
Next you connect all lines to your output read cone. Note that because we use the address to enable the rdata lines to be non-zero, we then can simply OR them all together as only one will be non-zero:
assign prdata =
rstval_rdata |
someotherreg_rdata |
...
And you might want to undef the macros at the end of the file to not conflit with other files:
' undef RWREG
...
Finally you create a file with the defaults. This one I often use a simple script, but it can be done by hand. The nice here is this part is usually very consistent so the script can be quite simple. Just put a major warning for people to not change the file by hand or someone will slap the culprits silly,
parameter RSTVAL_ADDR = 12'h000
parameter RSTVAL_DEF = 32'h00000000
parameter RSTVAL_MASK = 32'h00000001
parameter RSTVAL_RST_Pos = 0;
parameter SOMEOTHERREG_ADDR = 12'h004
parameter SOMEOTHERREG_DEF = 32'h00000000
...