FPGA용 RISC-V 소프트 코어 프로세서에서 하드웨어 멀티스레딩 구현하기
RISC-V 기반 소프트 코어 프로세서의 멀티스레드 마이크로아키텍처는 하트 상태를 저장하기 위해 섀도우 레지스터를 사용하여 소프트웨어 개입 없이 빠른 컨텍스트 전환을 가능하게 합니다. 이는 FPGA 프로젝트에서 비동기 이벤트 처리를 단순화하고, 인터럽트 컨트롤러 부하를 최소화하며, 코드 양을 줄입니다. 이 구현은 단일 권한 수준과 공유 주소 공간을 가진 시스템을 대상으로 합니다.
마이크로아키텍처 기본 원리
핵심 요소는 각 하트(RISC-V 용어 사용)에 대한 섀도우 레지스터 배열입니다. 각 하트는 레지스터 x0-x31과 PC의 복사본에 자신의 상태를 저장합니다. ALU, 메모리, 주변 장치 및 CSR 레지스터는 공유됩니다. 데이터 보호는 소프트웨어 수준에서 구현됩니다.
기본 명령어 세트 I+Zicsr가 지원됩니다. 스레드 관리는 CSR 레지스터와 하트 테이블(hart_table)을 통해 처리됩니다. 하트 전환은 시스템 타이머를 기반으로 순환적으로 발생하며, 그 하위 비트가 테이블을 주소 지정합니다.
하트 활동 비트는 PC, 레지스터 및 메모리에 대한 쓰기를 제어하고 비활성 스레드에 대한 ALU 파이프라인을 재설정합니다.
CSR 레지스터 모듈
CSR 블록은 멀티스레딩 호환성을 위해 독립적인 읽기 및 쓰기 포트로 나뉩니다:
module rv_csr
#(
parameter DATA_WIDTH=32,
parameter ADDR_WIDTH=12,
parameter CSR_size = 32
)
(input clk,
input [(ADDR_WIDTH-1):0] csr_addr_in,
input [(DATA_WIDTH-1):0] csr_in,
input [(ADDR_WIDTH-1):0] csr_addr_out,
output reg [(DATA_WIDTH-1):0] csr_out,
input csr_wr,
input en
);
// csr register file
reg [ADDR_WIDTH-1:0] csr_reg[0:CSR_size-1];
always @ (posedge clk)
begin
case (csr_addr_in)
32'h0 : begin
if (csr_wr&en) begin
csr_reg[0] <= csr_in;
end
end
default: begin
csr_reg[1] <= 32'h555;
end
endcase
case (csr_addr_out)
32'h0 : begin
csr_out <= csr_reg[0];
end
default: begin
csr_out<=32'hAAA;
end
endcase
end
endmodule
하트 지원 레지스터 파일
멀티스레드 레지스터 파일은 8개의 하트를 위한 RAM 배열을 구현합니다. 읽기/쓰기 작업은 hart_in/hart_out 번호로 주소 지정됩니다:
module rv_reg_file
#(
parameter DATA_WIDTH=32,
parameter ADDR_WIDTH=5
)
( input clk,
input [(ADDR_WIDTH-1):0] rs1,
input [(ADDR_WIDTH-1):0] rs2,
input [(ADDR_WIDTH-1):0] rd,
output reg [(DATA_WIDTH-1):0] Rs1_out,
output reg [(DATA_WIDTH-1):0] Rs2_out,
input [(DATA_WIDTH-1):0] Rd_input,
input [2:0] hart_in, // hart to read out data from
input [2:0] hart_out, // hart to write data to
input we,
input en,
output reg [(DATA_WIDTH-1):0] x1_out,
output reg [(DATA_WIDTH-1):0] x2_out,
output reg [(DATA_WIDTH-1):0] x3_out,
output reg [(DATA_WIDTH-1):0] x4_out,
output reg [(DATA_WIDTH-1):0] x5_out
);
// RAM array
reg [DATA_WIDTH-1:0] ram[0:2**ADDR_WIDTH-1][0:7];
wire rd_nonzero;
wire rs1_nonzero;
wire rs2_nonzero;
assign rd_nonzero = |rd;
assign rs1_nonzero = |rs1;
assign rs2_nonzero = |rs2;
always @ (posedge clk)
begin
if (en & we & rd_nonzero) ram[rd][hart_in] <= Rd_input;
end
always @ (*)
begin
Rs1_out <= rs1_nonzero ? ram[rs1][hart_out] : 32'h0;
Rs2_out <= rs2_nonzero ? ram[rs2][hart_out] : 32'h0;
x1_out <= ram[1][hart_out];
x2_out <= ram[2][hart_out];
x3_out <= ram[3][hart_out];
x4_out <= ram[4][hart_out];
x5_out <= ram[5][hart_out];
end
// */
endmodule
스레드용 프로그램 카운터
PC는 pc.txt 파일에서 초기화된 레지스터 배열로 구현됩니다. 증가 및 로드 작업은 활성 하트에 대해서만 발생합니다:
module rv_pc
#(
parameter WIDTH=32
)
(
input clk,
input rst_n,
input en,
input pc_load,
input [WIDTH-1:0] pc_next,
output [WIDTH-1:0] pc,
output reg [WIDTH-1:0] pc_plus,
input [2:0] hart_in,
input [2:0] hart_out
);
reg [WIDTH-1:0] pc_reg[0:7];
// read initial PCs data values
initial
$readmemh("pc.txt",pc_reg);
always@(posedge clk or negedge rst_n)
begin
if (en) begin
if(pc_load==1'b1)
pc_reg[hart_in] <= pc_next;
else
pc_reg[hart_in] <= pc_reg[hart_in] + 3'h4;
end
end
assign pc = pc_reg[hart_out];
always @ *
begin
pc_plus <= pc_reg[hart_out] + 3'h4;
end
endmodule
하트 테이블 및 관리
hart_table은 타이머로 주소 지정되는 8개의 항목(4비트: 1 활동 비트 + 3 하트 번호 비트)을 가진 ROM입니다. 64비트 시스템 타이머는 순환 카운터이자 시스템 크로노미터 역할을 합니다.
전체 모듈 목록:
- rv_pc — 멀티스레드 프로그램 카운터
- rv_mem — 통합 메모리
- rv_desh — 파이프라인 재설정 디코더
- rv_reg_file — 섀도우 레지스터
- rv_alu_v — 파이프라인 ALU
- rv_csr — 공유 특수 레지스터
- rv_hart_reg — 하트 상태
- rv_timer — 64비트 타이머
- rv_hart_table — 스레드 구성
구현 장점
- 빠른 전환: 레지스터 저장/복원 없이 하드웨어 기반
- IRQ 단순화: 인터럽트 벡터 대신 스레드 사용
- RISC-V 호환성: 하트 개념 지원
- FPGA 최적화: 공유 자원, 최소 논리
핵심 포인트
- 섀도우 레지스터는 각 하트의 전체 아키텍처 상태를 저장합니다.
- 공유 CSR은 정확한 작동을 위해 별도의 R/W 포트가 필요합니다.
- 하트 활동은 모든 쓰기를 제어하고 파이프라인을 재설정합니다.
- 타이머는 hart_table을 순환적으로 주소 지정하여 라운드 로빈 스케줄링을 보장합니다.
- 메모리 보호 및 동기화는 OS/RTOS의 책임입니다.
— Editorial Team
아직 댓글이 없습니다.