[Verilog 문법] 3.3 메모리/배열

YUN·2026년 1월 23일

디지털 회로 설계

목록 보기
12/20
post-thumbnail

1. 메모리

2차원으로 표현되는 저장 공간

RAM, ROM, 레지스터 파일을 설계할때 주로 메모리가 사용된다.

2. 1 차원 배열 VS 2차원 배열

// 1차원: 단일 레지스터
reg [7:0] single_reg;          // 8비트 레지스터 1개

// 2차원: 메모리 배열
reg [7:0] memory [0:15];       // 8비트 레지스터 16개
//   ↑ 데이터 폭    ↑ 깊이(주소 범위)

Verilog에서 단일 레지스터는 1차원 배열, 메모리는 2차원 배열로 구현한다.

3. 메모리 선언 방식

메모리(=2차원 배열) 선언에도 여러가지 방식(스타일)이 존재한다.

// 방식 1: [시작:끝]
reg [7:0] mem_a [0:255];      // 256개 엔트리 (주소 0~255)

// 방식 2: [끝:시작] - 비권장
reg [7:0] mem_b [255:0];      // 동일하지만 혼란 유발

// 방식 3: 파라미터 활용 ✅ // 매우 권장되는 방식
parameter DEPTH = 256;
parameter WIDTH = 8;
reg [WIDTH-1:0] mem [0:DEPTH-1]; // WIDTH 폭의 레지스터 DEPTH 개 생성

4. 배열 인덱싱

reg [7:0] mem [0:15]; // 메모리 = 8비트 레지스터 16개
reg [3:0] addr; // 레지스터 16개이므로 주소 표현에 4비트 필요
reg [7:0] data; // 8비트 레지스터이므로 데이터 8비트

// ✅ 올바른 접근
data = mem[0];           // 고정 인덱스
data = mem[addr];        // 변수 인덱스
mem[addr] = 8'hFF;       // 쓰기

// ❌ 불가능한 접근
data = mem[addr][3:0];   // 2단계 인덱싱 (일부 도구 미지원)

// ✅ 대안: 임시 변수 사용
wire [7:0] temp = mem[addr];
data = temp[3:0];        // 하위 4비트만 추출

위와 같이 배열의 특정 비트에 접근할 수 있다.

5. 활용 : 동기식 RAM 설계

(1) 단일 포트 RAM

포트(주소/데이터/제어/클럭 세트)가 1개라서, 한 클럭 사이클에 메모리에 대해 한 “접근”만 할 수 있는 RAM

module single_port_ram #(
    parameter DATA_WIDTH = 8,
    parameter ADDR_WIDTH = 4
)(
    input                    clk,
    input                    we,      // Write Enable
    input  [ADDR_WIDTH-1:0]  addr,
    input  [DATA_WIDTH-1:0]  din,
    output reg [DATA_WIDTH-1:0] dout
);
    // 메모리 선언
    localparam DEPTH = 1 << ADDR_WIDTH;  // 2^ADDR_WIDTH
    reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];
    
    always @(posedge clk) begin
        if (we)
            mem[addr] <= din;    // 쓰기
        dout <= mem[addr];       // 읽기 (1 클럭 지연)
    end
endmodule

(2) 듀얼 포트 RAM

포트가 2개라서, 한 클럭 사이클에 두 번의 접근이 가능한 RAM

module dual_port_ram #(
    parameter DATA_WIDTH = 8, // 데이터 크기
    parameter ADDR_WIDTH = 4 // 주소 크기
)(
    input                    clk,
    // 포트 A (Read/Write)
    input                    we_a,//포트 A의 write enable 신호
    input  [ADDR_WIDTH-1:0]  addr_a, //A포트 주소
    input  [DATA_WIDTH-1:0]  din_a, //A포트 입력 데이터
    output reg [DATA_WIDTH-1:0] dout_a, //A포트 출력 데이터
    // 포트 B (Read Only)
    input  [ADDR_WIDTH-1:0]  addr_b, //B 포트 주소
    output reg [DATA_WIDTH-1:0] dout_b // B포트 출력 데이터
);
    localparam DEPTH = 1 << ADDR_WIDTH; // ADDR_WIDTH로 메모리 DEPTH 설정
    reg [DATA_WIDTH-1:0] mem [0:DEPTH-1]; // 메모리 생성
    
    // 포트 A
    always @(posedge clk) begin
        if (we_a) //포트 A 쓰기신호가 1이면
            mem[addr_a] <= din_a; //입력된 주소의 메모리에 입력된 값을 저장
        dout_a <= mem[addr_a]; // 입력된 주소의 메모리에 있던 기존 값 출력
    end
    
    // 포트 B
    always @(posedge clk) begin
        dout_b <= mem[addr_b]; //b포트로 입력받은 주소에 있던 값을 출력
    end
endmodule

(3) 레지스터 파일


        ┌──────────────────────────┐
  clk ──┤                          │
   we ──┤     Register File        │
waddr ──┤       (32 x 32)          │
wdata ──┤                          │
        │                          ├── rdata1
raddr1 ─┤                          │
raddr2 ─┤                          ├── rdata2
        └──────────────────────────┘

위 구조의 레지스터 파일을 구현해볼 것이다.


module register_file #(
    parameter DATA_WIDTH = 32, //데이터 비트
    parameter REG_COUNT  = 32, //레지스터 개수
    parameter ADDR_WIDTH = 5 //주소 비트
)(
    input                       clk, //클럭 신호
    input                       we, //write enable
    input  [ADDR_WIDTH-1:0]     waddr, //write address
    input  [DATA_WIDTH-1:0]     wdata, //write data
    input  [ADDR_WIDTH-1:0]     raddr1, //read address 1
    input  [ADDR_WIDTH-1:0]     raddr2, //read address 2
    output [DATA_WIDTH-1:0]     rdata1, //read data 1
    output [DATA_WIDTH-1:0]     rdata2 //read data 2
);
    reg [DATA_WIDTH-1:0] regs [0:REG_COUNT-1]; //32비트 레지스터 32개로 이루어진 메모리 생성
    
    // 동기 쓰기
    always @(posedge clk) begin
        if (we && waddr != 0) //쓰기 신호, 쓰기 주소 둘다 0이 아닌 경우
            regs[waddr] <= wdata; //쓰기 주소에 해당하는 레지스터에 wdata 저장
    end
    
    // 비동기 읽기 (조합 논리)
    // raddr1이 0이 아니면 raddr1 주소에 저장된 데이터를 radat1로 출력
    assign rdata1 = (raddr1 == 0) ? 0 : regs[raddr1];
    // raddr2가 0이 아니면 raddr2 주소에 저장된 데이터를 radat2로 출력
    assign rdata2 = (raddr2 == 0) ? 0 : regs[raddr2];
    
    initial begin
        regs[0] = 0; //초기화
    end
endmodule

위와 같이 구현할 수 있다.

profile
안녕하세요. 전자공학부 학부생의 공부 기록입니다.

0개의 댓글