SystemVerilog generator

The SystemVerilogAxiLiteGenerator class creates a register file with AXI-Lite interface. See below for an example usage and a showcase of the code that can be generated.

Details and limitations

The SystemVerilog code generator in hdl-registers is a wrapper around PeakRDL-regblock. The hdl-registers representation of register data is translated internally to a PeakRDL representation before the PeakRDL exporter is called. All the features of hdl-registers are supported, except for the following:

  1. Register constants (of any type).

  2. Register arrays.

  3. Integer fields with signed range.

  4. Bit vector fields with a numeric interpretation other than unsigned.

  5. Pulse-on-write register modes.

It is quite likely that some or even all of these could be supported in the future. Some of the missing features are due to limitations in translation layer, while others stem from the PeakRDL tool.

Configuration

The RegisterCodeGenerator.create() and RegisterCodeGenerator.create_if_needed() methods of SystemVerilogAxiLiteGenerator can be supplied with a flatten_axi_lite argument. If this is set to True, the generated SystemVerilog module will have an input/output port for each individual AXI-Lite signal. If left as False, the AXI-Lite signals will be grouped into a single SystemVerilog interface.

If using the non-flattened interface, this interface file must be added to your simulation/build project.

Example

An example is used to illustrate the generator API and to showcase the code that can be generated.

Register definition TOML file

The TOML file used in this example sets up some very basic registers with a few fields.

Click to expand/collapse code.
Example TOML file.
 1################################################################################
 2[conf]
 3
 4mode = "r_w"
 5
 6enable.type = "bit"
 7enable.description = "Write as '1' to enable the module."
 8
 9tuser.type = "bit_vector"
10tuser.width = 8
11tuser.description = "Tag all output data with this."
12
13
14################################################################################
15[address]
16
17mode = "r_w"
18
19value.type = "bit_vector"
20value.width = 28
21value.description = "Data will be read from this base address."
22
23
24################################################################################
25[status]
26
27mode = "r"
28
29state.type = "enumeration"
30state.description = "Current state of the module."
31state.element.idle = "Is ready to receive new data."
32state.element.processing = "Is processing a chunk."
33state.element.sending_out = "Is waiting to send out data."
34
35overflow.type = "bit"
36overflow.description = "Is asserted if an overflow has occurred at any point."

Python file to generate register artifacts

The Python code below is used to parse the above TOML file and generate the SystemVerilog code.

Python code that parses the example TOML file and generates SystemVerilog code.
 1import sys
 2from pathlib import Path
 3
 4from hdl_registers.generator.systemverilog.axi_lite.register_file import (
 5    SystemVerilogAxiLiteGenerator,
 6)
 7from hdl_registers.parser.toml import from_toml
 8
 9THIS_DIR = Path(__file__).parent
10
11
12def main(output_folder: Path) -> None:
13    """
14    Create SystemVerilog register artifacts for an example module.
15    """
16    register_list = from_toml(
17        name="basic", toml_file=THIS_DIR.parent / "example_basic" / "regs_basic.toml"
18    )
19
20    SystemVerilogAxiLiteGenerator(
21        register_list=register_list, output_folder=output_folder
22    ).create_if_needed()
23
24
25if __name__ == "__main__":
26    main(output_folder=Path(sys.argv[1]))

Generated SystemVerilog register package

Below is the generated register package, which is used by the Generated SystemVerilog register file module. It must also be used wherever the register file is instantiated in order to get the correct types for the register and field values.

Example register package.
 1// -----------------------------------------------------------------------------
 2// This file is automatically generated by hdl-registers version 8.1.1-dev.
 3// Code generator SystemVerilogAxiLiteGenerator version 0.0.1.
 4// Generated 2025-11-25 20:53 from file regs_basic.toml at Git commit 3419a42a60fd.
 5// Register hash 2a2b3d670f42402eff5a314c1dde6eec58243b5c.
 6// -----------------------------------------------------------------------------
 7
 8// Generated by PeakRDL-regblock - A free and open-source SystemVerilog generator
 9//  https://github.com/SystemRDL/PeakRDL-regblock
10
11package basic_register_file_axi_lite_pkg;
12
13    localparam BASIC_REGISTER_FILE_AXI_LITE_DATA_WIDTH = 32;
14    localparam BASIC_REGISTER_FILE_AXI_LITE_MIN_ADDR_WIDTH = 4;
15    localparam BASIC_REGISTER_FILE_AXI_LITE_SIZE = 'hc;
16
17    typedef struct {
18        logic [1:0] next;
19    } xtern__basic__status__state__in_t__in_t;
20
21    typedef struct {
22        logic next;
23    } xtern__basic__status__overflow__in_t__in_t;
24
25    typedef struct {
26        xtern__basic__status__state__in_t__in_t state;
27        xtern__basic__status__overflow__in_t__in_t overflow;
28    } xtern__basic__status__in_t__in_t;
29
30    typedef struct {
31        xtern__basic__status__in_t__in_t status;
32    } basic__in_t;
33
34    typedef struct {
35        logic value;
36    } xtern__basic__conf__enable__out_t__out_t;
37
38    typedef struct {
39        logic [7:0] value;
40    } xtern__basic__conf__tuser__out_t__out_t;
41
42    typedef struct {
43        xtern__basic__conf__enable__out_t__out_t enable;
44        xtern__basic__conf__tuser__out_t__out_t tuser;
45    } xtern__basic__conf__out_t__out_t;
46
47    typedef struct {
48        logic [27:0] value;
49    } xtern__basic__address__value__out_t__out_t;
50
51    typedef struct {
52        xtern__basic__address__value__out_t__out_t value;
53    } xtern__basic__address__out_t__out_t;
54
55    typedef struct {
56        xtern__basic__conf__out_t__out_t conf;
57        xtern__basic__address__out_t__out_t address;
58    } basic__out_t;
59
60    typedef enum logic [1:0] {
61        basic_status_state__idle = 'h0,
62        basic_status_state__processing = 'h1,
63        basic_status_state__sending_out = 'h2
64    } basic_status_state_e;
65endpackage

Generated SystemVerilog register file module

Below is the generated SystemVerilog AXI-Lite register file module.

Example register file module.
  1// -----------------------------------------------------------------------------
  2// This file is automatically generated by hdl-registers version 8.1.1-dev.
  3// Code generator SystemVerilogAxiLiteGenerator version 0.0.1.
  4// Generated 2025-11-25 20:53 from file regs_basic.toml at Git commit 3419a42a60fd.
  5// Register hash 2a2b3d670f42402eff5a314c1dde6eec58243b5c.
  6// -----------------------------------------------------------------------------
  7
  8// Generated by PeakRDL-regblock - A free and open-source SystemVerilog generator
  9//  https://github.com/SystemRDL/PeakRDL-regblock
 10
 11module basic_register_file_axi_lite (
 12        input wire clk,
 13        input wire rst,
 14
 15        axi4lite_intf.slave s_axil,
 16
 17        input basic_register_file_axi_lite_pkg::basic__in_t hwif_in,
 18        output basic_register_file_axi_lite_pkg::basic__out_t hwif_out
 19    );
 20
 21    //--------------------------------------------------------------------------
 22    // CPU Bus interface logic
 23    //--------------------------------------------------------------------------
 24    logic cpuif_req;
 25    logic cpuif_req_is_wr;
 26    logic [3:0] cpuif_addr;
 27    logic [31:0] cpuif_wr_data;
 28    logic [31:0] cpuif_wr_biten;
 29    logic cpuif_req_stall_wr;
 30    logic cpuif_req_stall_rd;
 31
 32    logic cpuif_rd_ack;
 33    logic cpuif_rd_err;
 34    logic [31:0] cpuif_rd_data;
 35
 36    logic cpuif_wr_ack;
 37    logic cpuif_wr_err;
 38
 39    `ifndef SYNTHESIS
 40        initial begin
 41            assert_bad_addr_width: assert($bits(s_axil.ARADDR) >= basic_register_file_axi_lite_pkg::BASIC_REGISTER_FILE_AXI_LITE_MIN_ADDR_WIDTH)
 42                else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits(s_axil.ARADDR), basic_register_file_axi_lite_pkg::BASIC_REGISTER_FILE_AXI_LITE_MIN_ADDR_WIDTH);
 43            assert_bad_data_width: assert($bits(s_axil.WDATA) == basic_register_file_axi_lite_pkg::BASIC_REGISTER_FILE_AXI_LITE_DATA_WIDTH)
 44                else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits(s_axil.WDATA), basic_register_file_axi_lite_pkg::BASIC_REGISTER_FILE_AXI_LITE_DATA_WIDTH);
 45        end
 46    `endif
 47
 48    // Max Outstanding Transactions: 2
 49    logic [1:0] axil_n_in_flight;
 50    logic axil_prev_was_rd;
 51    logic axil_arvalid;
 52    logic [3:0] axil_araddr;
 53    logic axil_ar_accept;
 54    logic axil_awvalid;
 55    logic [3:0] axil_awaddr;
 56    logic axil_wvalid;
 57    logic [31:0] axil_wdata;
 58    logic [3:0] axil_wstrb;
 59    logic axil_aw_accept;
 60    logic axil_resp_acked;
 61
 62    // Transaction request acceptance
 63    always_ff @(posedge clk) begin
 64        if(rst) begin
 65            axil_prev_was_rd <= '0;
 66            axil_arvalid <= '0;
 67            axil_araddr <= '0;
 68            axil_awvalid <= '0;
 69            axil_awaddr <= '0;
 70            axil_wvalid <= '0;
 71            axil_wdata <= '0;
 72            axil_wstrb <= '0;
 73            axil_n_in_flight <= '0;
 74        end else begin
 75            // AR* acceptance register
 76            if(axil_ar_accept) begin
 77                axil_prev_was_rd <= '1;
 78                axil_arvalid <= '0;
 79            end
 80            if(s_axil.ARVALID && s_axil.ARREADY) begin
 81                axil_arvalid <= '1;
 82                axil_araddr <= s_axil.ARADDR;
 83            end
 84
 85            // AW* & W* acceptance registers
 86            if(axil_aw_accept) begin
 87                axil_prev_was_rd <= '0;
 88                axil_awvalid <= '0;
 89                axil_wvalid <= '0;
 90            end
 91            if(s_axil.AWVALID && s_axil.AWREADY) begin
 92                axil_awvalid <= '1;
 93                axil_awaddr <= s_axil.AWADDR;
 94            end
 95            if(s_axil.WVALID && s_axil.WREADY) begin
 96                axil_wvalid <= '1;
 97                axil_wdata <= s_axil.WDATA;
 98                axil_wstrb <= s_axil.WSTRB;
 99            end
100
101            // Keep track of in-flight transactions
102            if((axil_ar_accept || axil_aw_accept) && !axil_resp_acked) begin
103                axil_n_in_flight <= axil_n_in_flight + 1'b1;
104            end else if(!(axil_ar_accept || axil_aw_accept) && axil_resp_acked) begin
105                axil_n_in_flight <= axil_n_in_flight - 1'b1;
106            end
107        end
108    end
109
110    always_comb begin
111        s_axil.ARREADY = (!axil_arvalid || axil_ar_accept);
112        s_axil.AWREADY = (!axil_awvalid || axil_aw_accept);
113        s_axil.WREADY = (!axil_wvalid || axil_aw_accept);
114    end
115
116    // Request dispatch
117    always_comb begin
118        cpuif_wr_data = axil_wdata;
119        for(int i=0; i<4; i++) begin
120            cpuif_wr_biten[i*8 +: 8] = {8{axil_wstrb[i]}};
121        end
122        cpuif_req = '0;
123        cpuif_req_is_wr = '0;
124        cpuif_addr = '0;
125        axil_ar_accept = '0;
126        axil_aw_accept = '0;
127
128        if(axil_n_in_flight < 2'd2) begin
129            // Can safely issue more transactions without overwhelming response buffer
130            if(axil_arvalid && !axil_prev_was_rd) begin
131                cpuif_req = '1;
132                cpuif_req_is_wr = '0;
133                cpuif_addr = {axil_araddr[3:2], 2'b0};
134                if(!cpuif_req_stall_rd) axil_ar_accept = '1;
135            end else if(axil_awvalid && axil_wvalid) begin
136                cpuif_req = '1;
137                cpuif_req_is_wr = '1;
138                cpuif_addr = {axil_awaddr[3:2], 2'b0};
139                if(!cpuif_req_stall_wr) axil_aw_accept = '1;
140            end else if(axil_arvalid) begin
141                cpuif_req = '1;
142                cpuif_req_is_wr = '0;
143                cpuif_addr = {axil_araddr[3:2], 2'b0};
144                if(!cpuif_req_stall_rd) axil_ar_accept = '1;
145            end
146        end
147    end
148
149
150    // AXI4-Lite Response Logic
151    struct {
152        logic is_wr;
153        logic err;
154        logic [31:0] rdata;
155    } axil_resp_buffer[2];
156
157    logic [1:0] axil_resp_wptr;
158    logic [1:0] axil_resp_rptr;
159
160    always_ff @(posedge clk) begin
161        if(rst) begin
162            for(int i=0; i<2; i++) begin
163                axil_resp_buffer[i].is_wr <= '0;
164                axil_resp_buffer[i].err <= '0;
165                axil_resp_buffer[i].rdata <= '0;
166            end
167            axil_resp_wptr <= '0;
168            axil_resp_rptr <= '0;
169        end else begin
170            // Store responses in buffer until AXI response channel accepts them
171            if(cpuif_rd_ack || cpuif_wr_ack) begin
172                if(cpuif_rd_ack) begin
173                    axil_resp_buffer[axil_resp_wptr[0:0]].is_wr <= '0;
174                    axil_resp_buffer[axil_resp_wptr[0:0]].err <= cpuif_rd_err;
175                    axil_resp_buffer[axil_resp_wptr[0:0]].rdata <= cpuif_rd_data;
176
177                end else if(cpuif_wr_ack) begin
178                    axil_resp_buffer[axil_resp_wptr[0:0]].is_wr <= '1;
179                    axil_resp_buffer[axil_resp_wptr[0:0]].err <= cpuif_wr_err;
180                end
181                axil_resp_wptr <= axil_resp_wptr + 1'b1;
182            end
183
184            // Advance read pointer when acknowledged
185            if(axil_resp_acked) begin
186                axil_resp_rptr <= axil_resp_rptr + 1'b1;
187            end
188        end
189    end
190
191    always_comb begin
192        axil_resp_acked = '0;
193        s_axil.BVALID = '0;
194        s_axil.RVALID = '0;
195        if(axil_resp_rptr != axil_resp_wptr) begin
196            if(axil_resp_buffer[axil_resp_rptr[0:0]].is_wr) begin
197                s_axil.BVALID = '1;
198                if(s_axil.BREADY) axil_resp_acked = '1;
199            end else begin
200                s_axil.RVALID = '1;
201                if(s_axil.RREADY) axil_resp_acked = '1;
202            end
203        end
204
205        s_axil.RDATA = axil_resp_buffer[axil_resp_rptr[0:0]].rdata;
206        if(axil_resp_buffer[axil_resp_rptr[0:0]].err) begin
207            s_axil.BRESP = 2'b10;
208            s_axil.RRESP = 2'b10;
209        end else begin
210            s_axil.BRESP = 2'b00;
211            s_axil.RRESP = 2'b00;
212        end
213    end
214
215    logic cpuif_req_masked;
216
217    // Read & write latencies are balanced. Stalls not required
218    assign cpuif_req_stall_rd = '0;
219    assign cpuif_req_stall_wr = '0;
220    assign cpuif_req_masked = cpuif_req
221                            & !(!cpuif_req_is_wr & cpuif_req_stall_rd)
222                            & !(cpuif_req_is_wr & cpuif_req_stall_wr);
223
224    //--------------------------------------------------------------------------
225    // Address Decode
226    //--------------------------------------------------------------------------
227    typedef struct {
228        logic conf;
229        logic address;
230        logic status;
231    } decoded_reg_strb_t;
232    decoded_reg_strb_t decoded_reg_strb;
233    logic decoded_err;
234    logic decoded_req;
235    logic decoded_req_is_wr;
236    logic [31:0] decoded_wr_data;
237    logic [31:0] decoded_wr_biten;
238
239    always_comb begin
240        automatic logic is_valid_addr;
241        automatic logic is_invalid_rw;
242        is_valid_addr = '1; // No error checking on valid address access
243        is_invalid_rw = '0;
244        decoded_reg_strb.conf = cpuif_req_masked & (cpuif_addr == 4'h0);
245        decoded_reg_strb.address = cpuif_req_masked & (cpuif_addr == 4'h4);
246        decoded_reg_strb.status = cpuif_req_masked & (cpuif_addr == 4'h8) & !cpuif_req_is_wr;
247        decoded_err = (~is_valid_addr | is_invalid_rw) & decoded_req;
248    end
249
250    // Pass down signals to next stage
251    assign decoded_req = cpuif_req_masked;
252    assign decoded_req_is_wr = cpuif_req_is_wr;
253    assign decoded_wr_data = cpuif_wr_data;
254    assign decoded_wr_biten = cpuif_wr_biten;
255
256    //--------------------------------------------------------------------------
257    // Field logic
258    //--------------------------------------------------------------------------
259    typedef struct {
260        struct {
261            struct {
262                logic next;
263                logic load_next;
264            } enable;
265            struct {
266                logic [7:0] next;
267                logic load_next;
268            } tuser;
269        } conf;
270        struct {
271            struct {
272                logic [27:0] next;
273                logic load_next;
274            } value;
275        } address;
276    } field_combo_t;
277    field_combo_t field_combo;
278
279    typedef struct {
280        struct {
281            struct {
282                logic value;
283            } enable;
284            struct {
285                logic [7:0] value;
286            } tuser;
287        } conf;
288        struct {
289            struct {
290                logic [27:0] value;
291            } value;
292        } address;
293    } field_storage_t;
294    field_storage_t field_storage;
295
296    // Field: basic.conf.enable
297    always_comb begin
298        automatic logic [0:0] next_c;
299        automatic logic load_next_c;
300        next_c = field_storage.conf.enable.value;
301        load_next_c = '0;
302        if(decoded_reg_strb.conf && decoded_req_is_wr) begin // SW write
303            next_c = (field_storage.conf.enable.value & ~decoded_wr_biten[0:0]) | (decoded_wr_data[0:0] & decoded_wr_biten[0:0]);
304            load_next_c = '1;
305        end
306        field_combo.conf.enable.next = next_c;
307        field_combo.conf.enable.load_next = load_next_c;
308    end
309    always_ff @(posedge clk) begin
310        if(rst) begin
311            field_storage.conf.enable.value <= 1'h0;
312        end else begin
313            if(field_combo.conf.enable.load_next) begin
314                field_storage.conf.enable.value <= field_combo.conf.enable.next;
315            end
316        end
317    end
318    assign hwif_out.conf.enable.value = field_storage.conf.enable.value;
319    // Field: basic.conf.tuser
320    always_comb begin
321        automatic logic [7:0] next_c;
322        automatic logic load_next_c;
323        next_c = field_storage.conf.tuser.value;
324        load_next_c = '0;
325        if(decoded_reg_strb.conf && decoded_req_is_wr) begin // SW write
326            next_c = (field_storage.conf.tuser.value & ~decoded_wr_biten[8:1]) | (decoded_wr_data[8:1] & decoded_wr_biten[8:1]);
327            load_next_c = '1;
328        end
329        field_combo.conf.tuser.next = next_c;
330        field_combo.conf.tuser.load_next = load_next_c;
331    end
332    always_ff @(posedge clk) begin
333        if(rst) begin
334            field_storage.conf.tuser.value <= 8'h0;
335        end else begin
336            if(field_combo.conf.tuser.load_next) begin
337                field_storage.conf.tuser.value <= field_combo.conf.tuser.next;
338            end
339        end
340    end
341    assign hwif_out.conf.tuser.value = field_storage.conf.tuser.value;
342    // Field: basic.address.value
343    always_comb begin
344        automatic logic [27:0] next_c;
345        automatic logic load_next_c;
346        next_c = field_storage.address.value.value;
347        load_next_c = '0;
348        if(decoded_reg_strb.address && decoded_req_is_wr) begin // SW write
349            next_c = (field_storage.address.value.value & ~decoded_wr_biten[27:0]) | (decoded_wr_data[27:0] & decoded_wr_biten[27:0]);
350            load_next_c = '1;
351        end
352        field_combo.address.value.next = next_c;
353        field_combo.address.value.load_next = load_next_c;
354    end
355    always_ff @(posedge clk) begin
356        if(rst) begin
357            field_storage.address.value.value <= 28'h0;
358        end else begin
359            if(field_combo.address.value.load_next) begin
360                field_storage.address.value.value <= field_combo.address.value.next;
361            end
362        end
363    end
364    assign hwif_out.address.value.value = field_storage.address.value.value;
365
366    //--------------------------------------------------------------------------
367    // Write response
368    //--------------------------------------------------------------------------
369    assign cpuif_wr_ack = decoded_req & decoded_req_is_wr;
370    // Writes are always granted with no error response
371    assign cpuif_wr_err = '0;
372
373    //--------------------------------------------------------------------------
374    // Readback
375    //--------------------------------------------------------------------------
376
377    logic readback_err;
378    logic readback_done;
379    logic [31:0] readback_data;
380
381    // Assign readback values to a flattened array
382    logic [31:0] readback_array[3];
383    assign readback_array[0][0:0] = (decoded_reg_strb.conf && !decoded_req_is_wr) ? field_storage.conf.enable.value : '0;
384    assign readback_array[0][8:1] = (decoded_reg_strb.conf && !decoded_req_is_wr) ? field_storage.conf.tuser.value : '0;
385    assign readback_array[0][31:9] = '0;
386    assign readback_array[1][27:0] = (decoded_reg_strb.address && !decoded_req_is_wr) ? field_storage.address.value.value : '0;
387    assign readback_array[1][31:28] = '0;
388    assign readback_array[2][1:0] = (decoded_reg_strb.status && !decoded_req_is_wr) ? hwif_in.status.state.next : '0;
389    assign readback_array[2][2:2] = (decoded_reg_strb.status && !decoded_req_is_wr) ? hwif_in.status.overflow.next : '0;
390    assign readback_array[2][31:3] = '0;
391
392    // Reduce the array
393    always_comb begin
394        automatic logic [31:0] readback_data_var;
395        readback_done = decoded_req & ~decoded_req_is_wr;
396        readback_err = '0;
397        readback_data_var = '0;
398        for(int i=0; i<3; i++) readback_data_var |= readback_array[i];
399        readback_data = readback_data_var;
400    end
401
402    assign cpuif_rd_ack = readback_done;
403    assign cpuif_rd_data = readback_data;
404    assign cpuif_rd_err = readback_err;
405endmodule