SystemVerilog Support in Axion-HDL v1.2.0

v1.2.0 with SystemVerilog support is here. You can now point Axion at a .sv file the same way you would a .vhd file: annotate your signals with @axion comments and generate a full AXI4-Lite register block — in VHDL, SystemVerilog, or both.

1. Annotate your SystemVerilog module

The annotation syntax is identical to the VHDL workflow. Place an @axion_def comment near the top of the file for the module-level config (base address, CDC), then tag each register signal with an inline // @axion comment.

// @axion_def BASE_ADDR=0x0000 CDC_EN

module pwm_controller (
    input  logic        clk,
    input  logic        rst_n,
    input  logic        pwm_clk,
    output logic        pwm_out
);

    logic [31:0] ctrl_reg;    // @axion RW W_STROBE DESC="PWM control: [0]=enable, [1]=polarity, [7:4]=prescaler"
    logic [31:0] period_reg;  // @axion RW DESC="PWM period count (in pwm_clk cycles)"
    logic [31:0] duty_reg;    // @axion RW DESC="PWM duty cycle count"
    logic [31:0] status_reg;  // @axion RO R_STROBE DESC="Status: [0]=active, [1]=fault_latched"

    assign pwm_out = ctrl_reg[0];

endmodule

CDC_EN in the @axion_def line tells Axion to generate 2-stage synchronizers between the AXI clock and the module clock. The W_STROBE on ctrl_reg produces a one-cycle pulse on write, useful for triggering reconfiguration logic. status_reg is read-only — Axion will refuse write transactions to it at the AXI level and return a SLVERR response.

2. Run the generator

Pass the .sv file with -s and add --systemverilog (or --sv) alongside --vhdl if you want both outputs. --c-header and --yaml work exactly as before.

terminal
$ axion-hdl -s pwm_controller.sv -o output/ --vhdl --systemverilog --c-header --yaml --doc

Axion-HDL v1.2.0
--------------------------------------------------
SystemVerilog source file added: pwm_controller.sv

Starting analysis of SystemVerilog files...
  Parsing: pwm_controller.sv
Found 1 modules from SystemVerilog files.

Module: pwm_controller  |  CDC: Enabled (Stages: 2)  |  Base: 0x0000

Signal Name    Abs.Addr   Access   Strobes
ctrl_reg       0x00000000 RW       WR
period_reg     0x00000004 RW       None
duty_reg       0x00000008 RW       None
status_reg     0x0000000C RO       RD

Total Registers: 4

 pwm_controller_axion_reg.vhd
 pwm_controller_axion_reg.sv
 pwm_controller_regs.h
 pwm_controller_regs.yaml
 index.html (register map docs)

→ Generation completed successfully!

3. What gets generated

SystemVerilog register module

The _axion_reg.sv output is a self-contained AXI4-Lite slave. It uses an always_ff/always_comb state machine, typedef enum for states, and parametric port widths — idiomatic SystemVerilog that passes clean through common linters.

Because CDC was enabled, Axion inserts synchronizer chains for every register crossing clock domains. RO registers (status_reg) get a 2-stage input sync from module_clk to axi_aclk; RW registers get a 2-stage output sync in the opposite direction.

// Output Synchronizers (AXI -> Module)
// CDC for ctrl_reg (RW)
logic [31:0] ctrl_reg_sync [2];

always_ff @(posedge module_clk or negedge axi_aresetn) begin
    if (!axi_aresetn) begin
        ctrl_reg_sync <= '{default: '0};
    end else begin
        ctrl_reg_sync[0] <= ctrl_reg_reg;
        ctrl_reg_sync[1] <= ctrl_reg_sync[0];
    end
end

// Input Synchronizers (Module -> AXI)
// CDC for status_reg (RO)
logic [31:0] status_reg_sync [2];

always_ff @(posedge axi_aclk or negedge axi_aresetn) begin
    if (!axi_aresetn) begin
        status_reg_sync <= '{default: '0};
    end else begin
        status_reg_sync[0] <= status_reg;
        status_reg_sync[1] <= status_reg_sync[0];
    end
end

C header

The generated header includes address offsets, absolute addresses, read/write macros, and a packed struct — identical to what you'd get from a YAML or VHDL source.

/* Register Address Offsets */
#define PWM_CONTROLLER_CTRL_REG_OFFSET    0x00000000  /* PWM control: [0]=enable, [1]=polarity, [7:4]=prescaler */
#define PWM_CONTROLLER_PERIOD_REG_OFFSET  0x00000004  /* PWM period count (in pwm_clk cycles) */
#define PWM_CONTROLLER_DUTY_REG_OFFSET    0x00000008  /* PWM duty cycle count */
#define PWM_CONTROLLER_STATUS_REG_OFFSET  0x0000000C  /* Status: [0]=active, [1]=fault_latched */

/* Register Structure */
typedef struct {
    volatile uint32_t ctrl_reg;    /* 0x00 - RW */
    volatile uint32_t period_reg;  /* 0x04 - RW */
    volatile uint32_t duty_reg;    /* 0x08 - RW */
    volatile uint32_t status_reg;  /* 0x0C - RO */
} pwm_controller_regs_t;

#define PWM_CONTROLLER_REGS  ((volatile pwm_controller_regs_t*)(PWM_CONTROLLER_BASE_ADDR))

YAML register map

The YAML output captures everything Axion parsed: addresses, access types, CDC config, strobe flags. You can feed it back into Axion as a source later, or use it as documentation ground truth.

module: pwm_controller
base_addr: '0x0000'
config:
  cdc_en: true
  cdc_stage: 2
registers:
- name: ctrl_reg
  addr: '0x00'
  access: RW
  width: 32
  w_strobe: true
  description: 'PWM control: [0]=enable, [1]=polarity, [7:4]=prescaler'
- name: period_reg
  addr: '0x04'
  access: RW
  width: 32
  description: PWM period count (in pwm_clk cycles)
- name: duty_reg
  addr: '0x08'
  access: RW
  width: 32
  description: PWM duty cycle count
- name: status_reg
  addr: '0x0C'
  access: RO
  width: 32
  r_strobe: true
  description: 'Status: [0]=active, [1]=fault_latched'

4. Mixing sources

You can mix .sv, .vhd, and YAML files in the same run. Axion detects the format from the file extension and processes each independently. This is useful when you have a mixed VHDL/SV project and want a unified register generation step in your Makefile.

terminal
$ axion-hdl -s uart.vhd -s pwm_controller.sv -s extra_regs.yaml \
            -o output/ --vhdl --sv --c-header

The --sv flag is a shorthand for --systemverilog. Both are accepted. When you pass --all, SystemVerilog output is included automatically.

Upgrade

If you already have Axion installed, upgrading is a one-liner:

terminal
$ pip install --upgrade axion-hdl
Successfully installed axion-hdl-1.2.0

No breaking changes from v1.1.x. Existing VHDL and YAML workflows are unaffected.

Try the live demo Documentation ← All posts