본문 바로가기

RISC-V/RISC-V 설계

[RISC-V] Single Cycle CPU 직접 설계하고 구현하기 4편 - 최상위 결선 및 시뮬레이션 검증

 지난 2, 3편을 통해 CPU의 브레인과 심장인 제어/연산 장치부터 데이터를 임시 저장하고 정밀 가공하는 주변 하위 모듈과 두 가지 메모리(Inst_Mem.v, Data_Mem.v)까지 독립적인 하드웨어 소스 코드를 모두 쪼개 보았습니다.

https://idkihg.tistory.com/180

 

[RISC-V] Single Cycle CPU 직접 설계하고 구현하기 2편 - 핵심 연산 및 제어 회로 설계

지난 포스팅에서는 RISC-V Single-Cycle CPU의 이론적인 데이터패스 구조와 전체를 통제하는 10가지 핵심 제어 신호(Control Signals)의 개념에 대해 알아보았습니다. 이번 포스팅부터는 실제 Verilog HDL 소스

idkihg.tistory.com

 

https://idkihg.tistory.com/181

 

[RISC-V] Single Cycle CPU 직접 설계하고 구현하기 2편 - 주변 하위 모듈 및 메모리 구조 설계

지난 1편에서는 CPU의 브레인과 심장을 담당하는 제어 장치(Control.v)와 산술논리연산장치(ALU.v)의 소스 코드를 밀도 있게 분석해 보았습니다.https://idkihg.tistory.com/180 [RISC-V] Single Cycle CPU 직접 설계

idkihg.tistory.com

 

 이제 이 파편화된 버스선들을 하나로 엮어 유기체로 숨을 불어넣고, 실제 명령어 시나리오 파형을 통해 완벽하게 돌아가는지 눈으로 검증할 시간입니다. 이번 시간에는 하위 컴포넌트들을 총집결하는 최상위 탑 모듈(RISC_V.v)과 CPU가 12가지 필수 명령어를 올바르게 수행하는지 자동 검증하는 테스트벤치(RISC_V_tb.v)의 소스 코드를 정밀하게 파헤쳐 보겠습니다!

1. 최상위 모듈 (RISC_V.v) PC 및 데이터패스 결선 분석

최상위 모듈은 설계된 하위 모듈들을 와이어(Wire) 선으로 촘촘히 연결하고, CPU의 상태를 유지하는 유일한 순차논리 블록인 PC(프로그램 카운터) 레지스터를 구동하는 기지입니다.

 

1.1. PC 업데이트 및 분기 선택 멀티플렉서 코드

// --- PC Register & Next PC MUX ---
reg  [31:0] PC;                // CPU 내의 유일한 상태 저장 레지스터
wire [31:0] PC_plus4;          // 현재 주소 + 4 (순차 실행)
wire [31:0] PC_next;           // MUX가 선택한 진짜 다음 주소
wire [31:0] PC_jump;           // Branch/Jump 성공 시의 타겟 주소

assign PC_plus4 = PC + 32'd4;  // 기본 4바이트 전진 주소 계산

// [주소 정렬 잠금장치] JALR 명령어일 때의 예외 처리 결선
assign PC_jump = (instruction[6:2] == 5'b11001) ? 
                 {ALUOUT[31:1], 1'b0} : ALUOUT; 

// PCSel 신호(Control Unit 지휘)에 따라 다음 실행 경로 전환 MUX
assign PC_next = PCSel ? PC_jump : PC_plus4;   

// --- PC Register Update (동기식 순차회로) ---
always @(posedge clk) begin
    if (rst)
        PC <= 32'h0000_0000;   // 리셋 인가 시 0번지 주소로 초기화
    else
        PC <= PC_next;         // 매 클럭 에지마다 다음 PC 상태로 업데이트
end
  • JALR 주소 마스킹 사양 처리: RISC-V 공식 아키텍처 스펙에 따르면 JALR 명령어가 계산해 낸 목적지 주소는 반드시 하위 주소 정렬을 위해 최하위 비트(LSB)를 0으로 강제 리셋해야 합니다. 위 코드의 assign PC_jump = ... 구문을 보시면 삼항 연산자와 비트 컨캣 연산자({ALUOUT[31:1], 1'b0})를 조합하여 이 예외 사양을 게이트 레벨로 완벽하게 하드웨어 결선해 둔 것을 볼 수 있습니다.

 

1.2. Write-Back 단계의 3-to-1 멀티플렉서 결선 코드

// 연산이 끝난 후 레지스터 파일(Reg.v)의 dataD로 되돌아갈 최종 버스 데이터 선택
assign dataD = (WBSel == 2'b00) ? ReadData  :  // 데이터 메모리에서 로드된 값
               (WBSel == 2'b01) ? ALUOUT    :  // ALU가 계산 완료한 연산 결과물
                                  PC_plus4;    // Jump앤링크 명령어의 복귀 주소 저장
  • 조합논리 MUX의 간결화: 1편에서 설계한 WBSel 제어 신호가 지휘하는 3대1 멀티플렉서입니다. 버리노그 삼항 연산자의 중첩 구조를 활용하면 지저분한 이중 always 문 없이 오직 한 줄의 전선 결선만으로 메모리 로드, ALU 결과, PC 복귀 주소 중 하나의 경로를 정밀 타격하여 레지스터 파일의 입력단에 안착시킬 수 있습니다.

 

2. 테스트벤치 (RISC_V_tb.v) 자동 검증 인프라 분석

 테스트벤치는 가상 시뮬레이션 공간에 탑 모듈인 CPU(RISCV dut)를 인스턴스화하여 띄우고, 실제 기계어 코드를 주입하여 하드웨어가 정답을 도출하는지 스스로 체크하는 지능형 검증 프레임워크입니다.

 

2.1. 삼값 논리를 이용한 자가 검증(Self-Checking) 태스크 코드

integer pass_cnt = 0;   
integer fail_cnt = 0;   

task check;
    input [127:0] name;        // 검증 중인 명령어 항목 이름
    input [31:0]  got;         // 하드웨어 레지스터 파일이 출력한 실제 값
    input [31:0]  exp;         // 소프트웨어 알고리즘으로 예측한 이론적 정답
    begin
        if (got === exp) begin 
            $display("  PASS  [%0s]  got=0x%08h", name, got); 
            pass_cnt = pass_cnt + 1;
        end else begin
            $display("  FAIL  [%0s]  got=0x%08h  exp=0x%08h", name, got, exp); 
            fail_cnt = fail_cnt + 1;
        end
    end
endtask
  • === (Case Equality) 연산자의 마법: 일반 비교기(==) 대신 일치 연산자(===)를 사용한 것은 대단히 정밀한 설계 기법입니다. 하드웨어 초기화 단계에서 뜰 수 있는 미정의 신호 상태(X)나 하이 임피던스 부유 상태(Z)의 결함까지 정밀하게 잡아내어, 우연히 정답과 비트가 맞아떨어져 발생하는 가짜 PASS(False Positive) 현상을 원천 차단합니다.

 

2.2. 계층 경로를 통한 명령어 강제 주입(Injection) 태스크 코드

task load_inst;
    input [9:0]  idx;          // 명령어 메모리(IMEM)의 레지스터 배열 주소 인덱스
    input [31:0] instr;        // 주입하고자 하는 32비트 기계어 코드
    begin
        // 계층 경로(Hierarchical Path)를 사용하여 탑 모듈 하위의 내밀한 배열 직접 조작!
        dut.inst_mem.MEM[idx] = instr; 
    end
endtask
  • 시뮬레이션 가속화 기법: 테스트벤치 내부에서 dut.inst_mem.MEM[idx]와 같이 온점(.) 지시어로 서브 모듈 내부 배열의 이름을 명시해 주었습니다. 무거운 외부 텍스트 파일(.hex)을 매번 파싱해 읽어올 필요 없이, 시뮬레이터 동작 도중 즉각적으로 원하는 기계어 비트를 주입할 수 있어 검증 가속화 환경을 구축하는 데 결정적인 역할을 수행합니다.

 

2.3. 테스트 시나리오 구동 및 실전 파행 검증 분석

실제 메인 시뮬레이션 구동 initial 블록 내부에서 하드웨어 리셋 시퀀스를 흔들고 명령어 주입 함수들이 유기적으로 결합하여 동작하는 시퀀스를 잘라보겠습니다.

initial begin
    $dumpfile("risc_v_tb.vcd"); // 웨이브폼 파형 분석용 VCD 파일 생성 등록
    $dumpvars(0, RISC_V_tb);    // 시뮬레이터 내부 전역 전선 추적 기록 가동
    clk = 0; rst = 0;           // 초기 파형 베이스 정렬

    // --- [T01] ADDI 명령어 테스트 시나리오 가동 ---
    $display("\n--- [T01] ADDI ---");
    clear_imem;                 // 명령어 메모리를 아무 동작도 안 하는 NOP(0x00000013)으로 초기화
    
    // 인코딩 태스크를 이용해 주소 0, 1, 2번에 기계어를 실시간 직접 주입
    load_inst(0, enc_I(12'd5,   5'd0, 5'd1, 3'b000, 7'b001_0011)); // addi x1, x0, 5 (x1 = 5)
    load_inst(1, enc_I(12'd10,  5'd0, 5'd2, 3'b000, 7'b001_0011)); // addi x2, x0, 10 (x2 = 10)
    load_inst(2, enc_I(12'hFFD, 5'd1, 5'd3, 3'b000, 7'b001_0011)); // addi x3, x1, -3 (x3 = 2)
    
    do_reset;                   // CPU 하드웨어 리셋 시퀀스 트리거 (PC=0번지 정렬 및 전 영역 클리어)
    run_cycles(3);              // 3클럭을 전진시켜 주입된 3개의 명령어를 단일 사이클 패스로 순차 실행
    
    // 계층 경로(RF 마크)를 통해 레지스터 파일의 실제 하드웨어 출력값과 소프트웨어 정답지 대조!
    check("ADDI x1=5",  dut.reg_file.x[1],  32'd5);
    check("ADDI x2=10", dut.reg_file.x[2],  32'd10);
    check("ADDI x3=2",  dut.reg_file.x[3],  32'd2);

    // ... 이후 [T02]부터 [T12] JALR 시나리오까지 정밀 루프 검증 반복 ...

 

2.4. 테스트 결과 확인

모든 서브 블록들의 결선을 마치고, 앞서 설계한 자가 검증(Self-Checking) 테스트벤치를 가동하여 시뮬레이션을 수행한 결과입니다. RV32I의 필수 명령어 세트인 기본 산술 연산부터 메모리 입출력, 그리고 까다로운 조건 분기 및 점프 명령어까지 총 12가지 단계별 시나리오([T01] ~ [T12])를 정밀 타격하여 검증을 진행했습니다.

============================================
  RISC-V Single-Cycle CPU Testbench Start  
============================================

--- [T01] ADDI ---
  PASS  [ADDI x1=5]  got=0x00000005
  PASS  [ADDI x2=10]  got=0x0000000a
  PASS  [ADDI x3=2]  got=0x00000002

--- [T02] ADD / [T03] SUB ---
  PASS  [ADD  x4=15]  got=0x0000000f
  PASS  [SUB  x5=5]  got=0x00000005

--- [T04] AND / OR / XOR ---
  PASS  [AND  x6=4]  got=0x00000004
  PASS  [OR   x7=7]  got=0x00000007
  PASS  [XOR  x8=3]  got=0x00000003

--- [T05] SLL / SRL / SRA ---
  PASS  [SLL  x9=32]  got=0x00000020
  PASS  [SRL  x10=4]  got=0x00000004
  PASS  [SRA  x11=-2]  got=0xfffffffe

--- [T06] SLT / SLTU ---
  PASS  [SLT  x12=1]  got=0x00000001
  PASS  [SLTU x13=0]  got=0x00000000

--- [T07] LUI / AUIPC ---
  PASS  [LUI  x14]  got=0xabcde000
  PASS  [AUIPC x15]  got=0x00004008

--- [T08] SW / LW ---
  PASS  [SW MEM[0]]  got=0x000000ab
  PASS  [LW  x16]  got=0x000000ab

--- [T09] BEQ taken ---
  PASS  [ken: x17 skipped]  got=0x00000000
  PASS  [EQ taken: x18=42]  got=0x0000002a

--- [T10] BNE / BLT / BGE ---
  PASS  [BNE: x19 skipped]  got=0x00000000
  PASS  [BNE: x20=2]  got=0x00000002
  PASS  [BLT: x21 skipped]  got=0x00000000
  PASS  [BLT: x22=3]  got=0x00000003
  PASS  [BGE: x23 skipped]  got=0x00000000
  PASS  [BGE: x24=4]  got=0x00000004

--- [T11] JAL ---
  PASS  [x25=4 (ret addr)]  got=0x00000004
  PASS  [JAL: x26 skipped]  got=0x00000000
  PASS  [JAL: x27=7]  got=0x00000007

--- [T12] JALR ---
  PASS  [x28=8 (ret addr)]  got=0x00000008
  PASS  [ALR: x29 skipped]  got=0x00000000
  PASS  [JALR: x30=9]  got=0x00000009

============================================
  寃곌낵: PASS 31 / FAIL 0 / TOTAL 31
============================================

 

  • 산술 및 논리 연산 완전 무결 검증: [T01]부터 [T06] 파트까지 가동했을 때, ALU 코어가 부호 확장 산술 우측 시프트(SRA, SRAI) 연산 시 최상위 비트 복사를 완벽히 수행하여 x110xfffffffe(-2) 정답을 정확히 산출해 냈음을 파형 데이터와 로그로 증명했습니다 . 부호 있는 대소 비교(SLT) 역시 정상 가동되어 x121이 올바르게 안착했습니다 .
  • 메모리 입출력(SW/LW) 연동 성공: [T08] 시나리오에서 레지스터의 데이터(0x000000ab)를 데이터 메모리의 0번지에 굽는 SW 명령어와, 이를 다시 꺼내어 x16 레지스터에 로드하는 LW 명령어가 단일 사이클 타임 내에 완벽한 데이터 주소 매핑 피드백을 형성하며 PASS 플래그를 획득했습니다 .
  • 조건 분기 및 점프 제어의 완벽한 흐름 통제: [T09]~[T12] 구역의 결과가 하이라이트입니다. 분기 조건이 충족되지 않았을 때 다음 명령어를 건너뛰는 skipped 라인들(x17, x19, x21, x23)이 완벽히 구동되어 레지스터 값이 0을 유지했습니다. 반면 분기 조건이 성공하거나 무조건 점프(JAL, JALR) 시에는 목적지 레지스터에 복귀 주소(PC + 4 값인 0x00000004, 0x00000008)를 정확하게 백업 링크한 후 타겟 주소로 런타임 분기가 성사되었음을 확인했습니다.

최종 집계 결과, TOTAL 31개의 테스트 항목 중 PASS 31 / FAIL 0 이라는 100%의 스펙 만족도를 달성하며, 우리가 Verilog로 구현한 RV32I 단일 사이클 하드웨어 데이터패스가 단 하나의 예외 논리 오차도 없이 설계 사양대로 완벽하게 작동하고 있음을 완벽히 증명해 냈습니다.

4. 설계 분석 요약

  • RISC_V 탑 모듈 결선: 독립 설계된 하위 모듈들을 와이어선 버스로 결합하고, JALR 주소 비트 마스킹과 Write-Back MUX 정렬 사양을 게이트 수준으로 정밀 탑재한 완전한 하드웨어 통합체입니다.
  • 테스트벤치 인프라: 계층 경로 지시어를 이용한 초고속 기계어 명령어 주입(load_inst)과 삼값 비교 연산자(===)를 기반으로 한 하드웨어 자가 검증(check) 프레임워크입니다.

 이것으로 CPU 설계의 최종 종착역인 최상위 모듈 결선과 12가지 명령어 세트 시나리오의 자동 검증 Verilog 소스 코드 분석을 모두 마치겠습니다. 3개의 글 동안 설계한 파일을 함께 첨부합니다. 자세한 코드는 아래 파일을 확인하시어, 직접 시뮬레이션도 돌려보시기 바랍니다.

RISCV.zip
0.01MB