How to access register in FPGA Verilog?

 In Verilog, “registers” are just flip-flops you create with sequential logic and then expose/read through signals or a bus. Below is a practical cheat-sheet covering declaring, writing, reading, memory-mapped access, register files/BRAM, and best practices.




1) A register = flip-flop updated on a clock

module simple_reg ( input wire clk, input wire rst_n, // active-low reset input wire we, // write enable input wire [7:0] d, output wire [7:0] q ); reg [7:0] r; // Verilog “reg” -> storage (flip-flop) assign q = r; // read: just wire it out always @(posedge clk or negedge rst_n) begin if (!rst_n) r <= 8'h00; // reset value else if (we) r <= d; // write on enable end endmodule

Key rules

  • Use non-blocking <= in sequential always @(posedge ...) blocks.

  • Reading a register is just using its signal (q=r).

SystemVerilog tip: use logic and always_ff (cleaner), but plain Verilog works fine.


2) Bit fields (read/modify/write)

Often you need to set/clear bits:

// set bit 3, clear bit 1 r <= (r | 8'b0000_1000) & ~8'b0000_0010;

Or mask writes from a bus:

r <= (r & ~wmask) | (d & wmask);

3) Memory-mapped registers (MMIO) – “access from software”

Expose a few registers on a simple bus: addr, wdata, rdata, we, re.

module mmio_regs #( parameter AW = 4 ) ( input wire clk, rst_n, input wire we, re, // write/read strobes input wire [AW-1:0] addr, // byte or word address (define it) input wire [31:0] wdata, input wire [3:0] wstrb, // byte enables output reg [31:0] rdata, // Expose fields to the rest of the design output wire enable_o, output wire [7:0] threshold_o, input wire [15:0] status_i ); // Address map (word-aligned) localparam ADDR_CTRL = 4'h0; // RW: [0]=enable, [8:1]=reserved localparam ADDR_THRESH = 4'h4; // RW: [7:0]=threshold localparam ADDR_STAT = 4'h8; // RO: [15:0]=status reg [31:0] reg_ctrl; reg [31:0] reg_thresh; // writes wire wr_ctrl = we && (addr==ADDR_CTRL); wire wr_thresh = we && (addr==ADDR_THRESH); // helper to apply byte strobes function [31:0] apply_wstrb; input [31:0] oldv, newv; input [3:0] st; begin apply_wstrb = oldv; if (st[0]) apply_wstrb[ 7:0] = newv[ 7:0]; if (st[1]) apply_wstrb[15:8] = newv[15:8]; if (st[2]) apply_wstrb[23:16] = newv[23:16]; if (st[3]) apply_wstrb[31:24] = newv[31:24]; end endfunction always @(posedge clk or negedge rst_n) begin if (!rst_n) begin reg_ctrl <= 32'h0; reg_thresh <= 32'h0000_0010; // default threshold = 16 end else begin if (wr_ctrl) reg_ctrl <= apply_wstrb(reg_ctrl, wdata, wstrb); if (wr_thresh) reg_thresh <= apply_wstrb(reg_thresh, wdata, wstrb); end end // reads (combinational) always @* begin case (addr) ADDR_CTRL: rdata = reg_ctrl; ADDR_THRESH: rdata = reg_thresh; ADDR_STAT: rdata = {16'h0, status_i}; // read-only view of a signal default: rdata = 32'hDEAD_BEEF; endcase end // expose useful fields assign enable_o = reg_ctrl[0]; assign threshold_o = reg_thresh[7:0]; endmodule

Notes

  • This style synthesizes well and is easy to adapt to APB/AHB/AXI-Lite/Wishbone front ends.

  • For AXI-Lite, the same registers live behind the AXI handshake logic.


4) Register files (arrays) and BRAM

Small register files (e.g., 32×32, 2 read / 1 write) can be “distributed RAM” (LUTs):

module regfile_2r1w ( input wire clk, input wire we, input wire [4:0] waddr, raddr_a, raddr_b, input wire [31:0] wdata, output reg [31:0] rdata_a, rdata_b ); reg [31:0] rf [0:31]; // write always @(posedge clk) begin if (we) rf[waddr] <= wdata; end // 2 combinational reads (many FPGAs infer distributed RAM for this) always @* rdata_a = rf[raddr_a]; always @* rdata_b = rf[raddr_b]; endmodule

Larger memories should infer block RAM (usually synchronous read):

module bram_1r1w #( parameter AW=8 // 256 words ) ( input wire clk, input wire we, input wire [AW-1:0] waddr, raddr, input wire [31:0] wdata, output reg [31:0] rdata ); reg [31:0] mem [0:(1<<AW)-1]; always @(posedge clk) begin if (we) mem[waddr] <= wdata; rdata <= mem[raddr]; // registered (sync) read -> BRAM inference end endmodule

Need ROM/init? Use $readmemh("init.hex", mem); in an initial block (synth-supported on most FPGAs).


5) Accessing a register from another module

  • Right way (synthesizable): pass it through ports (like q, or a bus).

  • Wrong for synthesis: hierarchical references top.u1.r (OK in testbenches only).

  • For debug, use vendor ILAs/SignalTap or expose status via MMIO.


6) Crossing clock domains (CDC)

If software/bus clock ≠ logic clock:

  • Use sync flops for single-bit controls (two-FF synchronizer).

  • For multi-bit data, use handshake (valid/ready) or dual-clock FIFOs.

  • Don’t directly sample one clock domain in another.


7) Common pitfalls

  • Mixing blocking and non-blocking in the same always block → racey hardware.

  • Multiple always blocks driving the same reg → multiple drivers.

  • Latches inferred (missing else/default) when you meant flops.

  • Assuming BRAM is async read—on most FPGAs it’s sync.

  • Forgetting reset behavior (some FPGAs allow power-up init, but be explicit).


8) Minimal testbench “peek/poke” (simulation only)

initial begin uut.r = 8'hA5; // hierarchical write for sim #10; $display("r=%h", uut.r); end

Use this only in tb; hardware can’t do that—use MMIO if you need software access.

评论

此博客中的热门博文

Detailed Explanation of STM32 HAL Library Clock System

How do you set up ADC (Analog-to-Digital Converter) in STM32?

How To Connect Stm32 To PC?