Register Map / CSR Generator
Generate a synthesizable AXI4-Lite register block from a simple text description: Verilog RTL with per-field ports, a C header with offsets/masks/shifts, and a Markdown register-map document. RW, RO, W1C and WO registers with per-register write strobes.
Results
- Registers
- 4
- Address span
- 16 bytes (ADDR_WIDTH = 4)
- Hardware field ports
- 8
- Module name
- mycore_regs
Register map
| Offset | Register | Access | Reset | Fields |
|---|---|---|---|---|
| 0x000 | CTRL | RW | 0x00000000 | enable, irq_en, mode |
| 0x004 | STATUS | RO | 0x00000000 | busy, err_code |
| 0x008 | IRQ | W1C | 0x00000000 | done, error |
| 0x00C | DATA | WO | 0x00000000 | data |
Notes
- wstrb is honored on RW/WO writes; W1C clears use the full wdata word (use full-word writes for interrupt-status registers).
- Writes to unmapped or read-only offsets return SLVERR; reads of WO registers return 0.
- o_<reg>_wr pulses one cycle per accepted write - use it to trigger commands or push FIFOs.
- Keep the definition text in your repo and regenerate in CI via the JSON API (see /api).
// mycore register block - AXI4-Lite slave
// 4 registers, 16 bytes, generated by libfpga.com/tools/register-map
// Single outstanding transaction; SLVERR on unmapped addresses.
module mycore_regs (
input wire clk,
input wire rst_n,
// AXI4-Lite slave
input wire [3:0] s_axil_awaddr,
input wire s_axil_awvalid,
output reg s_axil_awready,
input wire [31:0] s_axil_wdata,
input wire [3:0] s_axil_wstrb,
input wire s_axil_wvalid,
output reg s_axil_wready,
output reg [1:0] s_axil_bresp,
output reg s_axil_bvalid,
input wire s_axil_bready,
input wire [3:0] s_axil_araddr,
input wire s_axil_arvalid,
output reg s_axil_arready,
output reg [31:0] s_axil_rdata,
output reg [1:0] s_axil_rresp,
output reg s_axil_rvalid,
input wire s_axil_rready,
// hardware interface
output wire o_ctrl_enable,
output wire o_ctrl_irq_en,
output wire [1:0] o_ctrl_mode,
output reg o_ctrl_wr,
input wire i_status_busy,
input wire [3:0] i_status_err_code,
input wire i_irq_done_set,
output wire o_irq_done,
input wire i_irq_error_set,
output wire o_irq_error,
output wire [31:0] o_data,
output reg o_data_wr
);
// word-address indices
localparam [1:0] ADR_CTRL = 0;
localparam [1:0] ADR_STATUS = 1;
localparam [1:0] ADR_IRQ = 2;
localparam [1:0] ADR_DATA = 3;
function [31:0] wmask(input [31:0] o, input [31:0] n, input [3:0] s);
wmask = {s[3] ? n[31:24] : o[31:24], s[2] ? n[23:16] : o[23:16],
s[1] ? n[15:8] : o[15:8], s[0] ? n[7:0] : o[7:0]};
endfunction
wire wr_en = s_axil_awvalid & s_axil_wvalid & ~s_axil_bvalid & ~s_axil_awready;
wire rd_en = s_axil_arvalid & ~s_axil_rvalid & ~s_axil_arready;
wire [1:0] wr_word = s_axil_awaddr[3:2];
wire [1:0] rd_word = s_axil_araddr[3:2];
reg [31:0] r_ctrl;
assign o_ctrl_enable = r_ctrl[0];
assign o_ctrl_irq_en = r_ctrl[1];
assign o_ctrl_mode = r_ctrl[3:2];
always @(posedge clk) begin // CTRL (RW)
if (!rst_n) begin
r_ctrl <= 32'h00000000;
o_ctrl_wr <= 1'b0;
end else begin
o_ctrl_wr <= 1'b0;
if (wr_en && wr_word == ADR_CTRL) begin
r_ctrl <= wmask(r_ctrl, s_axil_wdata, s_axil_wstrb) & 32'h0000000F;
o_ctrl_wr <= 1'b1;
end
end
end
wire [31:0] rv_status = {24'd0, i_status_err_code, 3'd0, i_status_busy};
reg [31:0] r_irq;
assign o_irq_done = r_irq[0];
assign o_irq_error = r_irq[1];
wire [31:0] set_irq = {30'd0, i_irq_error_set, i_irq_done_set};
always @(posedge clk) begin // IRQ (W1C)
if (!rst_n)
r_irq <= 32'h00000000;
else
r_irq <= (r_irq | set_irq) & ~((wr_en && wr_word == ADR_IRQ) ? s_axil_wdata : 32'd0) & 32'h00000003;
end
reg [31:0] r_data;
assign o_data = r_data[31:0];
always @(posedge clk) begin // DATA (WO)
if (!rst_n) begin
r_data <= 32'h00000000;
o_data_wr <= 1'b0;
end else begin
o_data_wr <= 1'b0;
if (wr_en && wr_word == ADR_DATA) begin
r_data <= wmask(r_data, s_axil_wdata, s_axil_wstrb) & 32'hFFFFFFFF;
o_data_wr <= 1'b1;
end
end
end
always @(posedge clk) begin // write handshake
if (!rst_n) begin
s_axil_awready <= 1'b0;
s_axil_wready <= 1'b0;
s_axil_bvalid <= 1'b0;
s_axil_bresp <= 2'b00;
end else begin
s_axil_awready <= 1'b0;
s_axil_wready <= 1'b0;
if (wr_en) begin
s_axil_awready <= 1'b1;
s_axil_wready <= 1'b1;
s_axil_bvalid <= 1'b1;
case (wr_word)
ADR_CTRL: s_axil_bresp <= 2'b00;
ADR_IRQ: s_axil_bresp <= 2'b00;
ADR_DATA: s_axil_bresp <= 2'b00;
default: s_axil_bresp <= 2'b10; // SLVERR
endcase
end
if (s_axil_bvalid && s_axil_bready) s_axil_bvalid <= 1'b0;
end
end
always @(posedge clk) begin // read handshake
if (!rst_n) begin
s_axil_arready <= 1'b0;
s_axil_rvalid <= 1'b0;
s_axil_rresp <= 2'b00;
s_axil_rdata <= 32'd0;
end else begin
s_axil_arready <= 1'b0;
if (rd_en) begin
s_axil_arready <= 1'b1;
s_axil_rvalid <= 1'b1;
s_axil_rresp <= 2'b00;
case (rd_word)
ADR_CTRL: s_axil_rdata <= r_ctrl;
ADR_STATUS: s_axil_rdata <= rv_status;
ADR_IRQ: s_axil_rdata <= r_irq;
ADR_DATA: s_axil_rdata <= 32'd0;
default: begin
s_axil_rdata <= 32'd0;
s_axil_rresp <= 2'b10; // SLVERR
end
endcase
end
if (s_axil_rvalid && s_axil_rready) s_axil_rvalid <= 1'b0;
end
end
endmodule
/* mycore register map - generated by libfpga.com/tools/register-map */
#ifndef MYCORE_REGS_H
#define MYCORE_REGS_H
#define MYCORE_CTRL_OFFS 0x000u /* RW, reset 0x00000000 */
#define MYCORE_CTRL_ENABLE_MASK 0x00000001u
#define MYCORE_CTRL_ENABLE_SHIFT 0u
#define MYCORE_CTRL_IRQ_EN_MASK 0x00000002u
#define MYCORE_CTRL_IRQ_EN_SHIFT 1u
#define MYCORE_CTRL_MODE_MASK 0x0000000Cu
#define MYCORE_CTRL_MODE_SHIFT 2u
#define MYCORE_STATUS_OFFS 0x004u /* RO, reset 0x00000000 */
#define MYCORE_STATUS_BUSY_MASK 0x00000001u
#define MYCORE_STATUS_BUSY_SHIFT 0u
#define MYCORE_STATUS_ERR_CODE_MASK 0x000000F0u
#define MYCORE_STATUS_ERR_CODE_SHIFT 4u
#define MYCORE_IRQ_OFFS 0x008u /* W1C, reset 0x00000000 */
#define MYCORE_IRQ_DONE_MASK 0x00000001u
#define MYCORE_IRQ_DONE_SHIFT 0u
#define MYCORE_IRQ_ERROR_MASK 0x00000002u
#define MYCORE_IRQ_ERROR_SHIFT 1u
#define MYCORE_DATA_OFFS 0x00Cu /* WO, reset 0x00000000 */
#endif /* MYCORE_REGS_H */
# mycore register map
## CTRL - 0x000 (RW, reset 0x00000000)
| Bits | Field | Description |
|---|---|---|
| 3:2 | mode | 0=idle 1=run 2=loopback |
| 1 | irq_en | Interrupt enable |
| 0 | enable | Core enable |
## STATUS - 0x004 (RO, reset 0x00000000)
| Bits | Field | Description |
|---|---|---|
| 7:4 | err_code | Last error code |
| 0 | busy | Transfer in progress |
## IRQ - 0x008 (W1C, reset 0x00000000)
| Bits | Field | Description |
|---|---|---|
| 1 | error | Transfer error |
| 0 | done | Transfer complete |
## DATA - 0x00C (WO, reset 0x00000000)
| Bits | Field | Description |
|---|---|---|
| 31:0 | data | (whole register) |
About this tool
Every IP block needs the same three artifacts kept in sync: the RTL that implements its registers, the C header the driver uses, and the documentation humans read. Writing them by hand means they drift — the classic bug where the driver's mask disagrees with the RTL by one bit. This generator makes the text description the single source of truth and emits all three. The Verilog is a standard single-outstanding AXI4-Lite slave: wstrb honored on RW/WO writes, SLVERR on unmapped addresses, a write strobe output per writable register (handy for command/FIFO registers), and W1C semantics where hardware sets bits and software clears them — the standard interrupt-status pattern. Keep the definition file in your repo and regenerate via the JSON API in CI.