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.
$ 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.
$ 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:
$ 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.