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 To Connect Stm32 To PC?

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