Advanced Feature: Read Groups
Relevant source files
The following files were used as context for generating this wiki page:
The Read Groups feature, introduced in version 1.32, provides an alternative architecture for generating the register read logic in the Verilog output. Instead of a single, monolithic case statement for all registers, this feature partitions registers into groups of a specified size. Each group has its own read logic, and a top-level multiplexer selects the output from the appropriate group.
This approach can be beneficial for designs with a large number of registers, potentially improving synthesis results by breaking down a large combinatorial block. However, it introduces a two-cycle latency on the read valid signal (bmi_rdvld). The feature is listed as one of the tool's capabilities.
Sources: regif.py:14, readme.md:46, setup.py:8
Configuration
The Read Groups feature is enabled and configured through a single cell in the input xxx_regif.xls file. If the group size is set to 0 or left empty, the standard, non-grouped read logic is generated.
Sources: regif.py:1219, regif.py:1331
Excel Configuration
The group size is defined in the main configuration sheet of the Excel template.
| Cell | Parameter | Description |
|---|---|---|
K5 | group_size | Defines the number of registers per read group. The value must be a power of 2 (e.g., 32, 64, 128). If 0 or empty, the feature is disabled. |
The script validates that the provided group_size is a power of 2. If it is not, the script will exit with an error.
Sources: regif.py:1203-1213, regif.py:1219
This diagram illustrates the configuration being read from the Excel file to enable the feature.
Sources: regif.py:1215-1227, regif.py:1330-1351
Generated Verilog Architecture
The generated Verilog for the read interface differs significantly depending on whether Read Groups are enabled.
Standard Read Logic (No Read Groups)
When group_size is 0, the script generates a single always block to handle all read operations. This block uses a case statement that decodes the bmi_addr to select the data from the corresponding register. The read valid signal bmi_rdvld is asserted in the same clock cycle that a valid address is matched.
This diagram shows the data flow for the standard read logic.
Sources: regif.py:214-228, regif.py:1352-1355
The Verilog template for this logic is defined as R_ALWAYS.
//register bus read interface
reg [{data_width}-1 :0] bmi_rdata ;
reg bmi_rdvld ;
always @(posedge bmi_clk or negedge bmi_rstn)
if(!bmi_rstn) begin
bmi_rdata <= {data_width}'b0 ;
bmi_rdvld <= 1'b0 ;
end else if(bmi_rd && ~bmi_wr) begin
case(bmi_addr)
// ... case items for each register
endcase
end else begin
bmi_rdvld <= 1'b0 ;
end
Sources: regif.py:214-228
Grouped Read Logic
When group_size is a non-zero power of 2, the architecture is partitioned.
- Group Read Blocks: For each group of registers, a dedicated
alwaysblock (using theR_ALWAYStemplate) is instantiated. These blocks generate group-specific read data and valid signals (e.g.,grp0_rdata,grp0_rdvld). - Top-Level MUX: A final
alwaysblock (RG_ALWAYS) acts as a multiplexer. It uses the outputs from the group-specific blocks to drive the finalbmi_rdataandbmi_rdvldsignals.
This partitioning introduces a two-cycle delay for bmi_rdvld relative to the bmi_rd signal, a fact the script explicitly warns about during generation.
This diagram shows the partitioned structure of the Read Group logic.
Sources: regif.py:230-250, regif.py:1330-1351
The core logic for the top-level MUX is defined in the RG_ALWAYS template.
//register bus read interface
reg [{data_width}-1 :0] bmi_rdata ;
reg bmi_rdvld ;
reg rd_dly1 ;
always @(posedge bmi_clk or negedge bmi_rstn)
if(!bmi_rstn) begin
rd_dly1 <= 1'b0 ;
end else begin
rd_dly1 <= bmi_rd && ~bmi_wr ;
end
always @(posedge bmi_clk or negedge bmi_rstn)
if(!bmi_rstn) begin
bmi_rdata <= {data_width}'b0 ;
bmi_rdvld <= 1'b0 ;
end else if(rd_dly1) begin
if (grp0_rdvld) begin bmi_rdvld <= grp0_rdvld; bmi_rdata <= grp0_rdata; end
else if(grp1_rdvld) begin bmi_rdvld <= grp1_rdvld; bmi_rdata <= grp1_rdata; end
// ... more else if for other groups
end else begin
bmi_rdvld <= 1'b0 ;
end
Sources: regif.py:230-250, regif.py:1344-1347
Code Generation in Regif.gen_segment
The gen_segment method within the Regif class is responsible for assembling the Verilog code. It contains the conditional logic that chooses between the standard and grouped read architectures.
Sources: regif.py:1330-1365
# regif.py:1330-1351
def gen_segment(self):
# ...
gs = self.group_size
read_part = ""
if(gs != 0):
grpreads = ""
for i in range(0, len(self.reg_objs), gs):
reads = ""
grpid = int(i/gs)
bmi = "grp%d" % grpid
for reg in self.reg_objs[i : i+gs]:
reads += reg.read(bmi)
read_part += R_ALWAYS.format(reads=reads, bmi=bmi, **self.__dict__)
if(i == 0):
grpreads += RG_RD.format(i = grpid)
else:
grpreads += RG_RDE.format(i = grpid)
read_part += RG_ALWAYS.format(grpreads=grpreads, bmi=bmi, **self.__dict__ )
print("Info! bmi_rdvld have 2 cycle delay when bmi_rd \n")
else:
reads = ""
for reg in self.reg_objs:
reads += reg.read()
read_part = R_ALWAYS.format(reads=reads, bmi="bmi", **self.__dict__)
# ...
This code snippet shows how the script iterates through the register objects (self.reg_objs) in chunks defined by gs (group_size). For each chunk, it generates a group-specific read block and then combines them with the top-level RG_ALWAYS multiplexer logic.
Summary
The Read Groups feature offers a trade-off between read latency and logic complexity.
| Feature | Standard Read Logic | Grouped Read Logic |
|---|---|---|
| Activation | group_size is 0 or empty | group_size is a power of 2 |
| Latency | 1 cycle from bmi_rd to bmi_rdvld | 2 cycles from bmi_rd to bmi_rdvld |
| Verilog Structure | Single always block with one large case statement | Multiple smaller always blocks (one per group) plus a final MUX always block |
| Generated Signals | bmi_rdata, bmi_rdvld | bmi_rdata, bmi_rdvld, rd_dly1, and grp{i}_rdata/grp{i}_rdvld for each group i |
By partitioning the read logic, the Read Groups feature can be a valuable tool for managing complexity and potentially improving timing in designs with a high register count, provided the additional two-cycle read latency is acceptable for the application.
Sources: regif.py:214-250, regif.py:1330-1355