Zurück zur Startseite

Mehrfädiger RISC-V-Softprozessor für FPGA

Der Artikel beschreibt die Implementierung eines mehrfädigen RISC-V-Softprozessors für FPGA mit Schattenregistern und Hardware-Hart-Wechsel. Vollständiger Verilog-Code der Schlüsselmmodule wird bereitgestellt: reg_file, PC, CSR, hart_table. Der Ansatz vereinfacht die Interrupt-Behandlung und optimiert FPGA-Ressourcen.

Hardware-Multithreading RISC-V in FPGA: vollständige Implementierung
Advertisement 728x90

Hardware-Multithreading in einem RISC-V Soft-Core-Prozessor für FPGA implementieren

Eine multithreaded Mikroarchitektur für einen RISC-V-basierten Soft-Core-Prozessor nutzt Schattenregister, um Hart-Zustände zu speichern und ermöglicht so schnellen Kontextwechsel ohne Software-Eingriff. Dies vereinfacht die Handhabung asynchroner Ereignisse in FPGA-Projekten, minimiert die Belastung des Interrupt-Controllers und reduziert den Codeumfang. Die Implementierung zielt auf Systeme mit einem einzigen Privileg-Level und gemeinsamem Adressraum ab.

Grundlagen der Mikroarchitektur

Das Schlüsselelement sind Arrays von Schattenregistern für jeden Hart (unter Verwendung der RISC-V-Terminologie). Jeder Hart speichert seinen Zustand in Kopien der Register x0-x31 und des PCs. ALU, Speicher, Peripherie und CSR-Register bleiben gemeinsam genutzt. Datenschutz wird auf Software-Ebene implementiert.

Der Basis-Befehlssatz I+Zicsr wird unterstützt. Thread-Management erfolgt über CSR-Register und eine Hart-Tabelle (hart_table). Hart-Wechsel erfolgt zyklisch basierend auf dem System-Timer, wessen niederwertige Bits die Tabelle adressieren.

Google AdInline article slot

Hart-Aktivitätsbits steuern Schreibvorgänge auf PC, Register und Speicher und setzen die ALU-Pipeline für inaktive Threads zurück.

CSR-Register-Modul

Der CSR-Block ist für Multithreading-Kompatibilität in unabhängige Lese- und Schreibports unterteilt:

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

Register-Datei mit Hart-Unterstützung

Die multithreaded Register-Datei implementiert ein RAM-Array für 8 Harts. Lese-/Schreiboperationen werden durch hart_in/hart_out-Nummern adressiert:

Google AdInline article slot
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

Program Counter für Threads

Der PC wird als Register-Array implementiert, das aus einer pc.txt-Datei initialisiert wird. Inkrement- und Ladeoperationen erfolgen nur für den aktiven Hart:

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-Tabelle und Management

Die hart_table ist ein ROM mit 8 Einträgen (4 Bits: 1 Aktivitätsbit + 3 Hart-Nummernbits), adressiert durch den Timer. Der 64-Bit-System-Timer dient sowohl als zyklischer Zähler als auch als System-Chronometer.

Vollständige Modulliste:

Google AdInline article slot
  • rv_pc — multithreaded Program Counter
  • rv_mem — einheitlicher Speicher
  • rv_desh — Decoder mit Pipeline-Reset
  • rv_reg_file — Schattenregister
  • rv_alu_v — gepipelined ALU
  • rv_csr — gemeinsame Spezialregister
  • rv_hart_reg — Hart-Status
  • rv_timer — 64-Bit-Timer
  • rv_hart_table — Thread-Konfiguration

Implementierungsvorteile

  • Schneller Wechsel: hardwarebasiert, ohne Register zu speichern/wiederherstellen
  • IRQ-Vereinfachung: Threads statt Interrupt-Vektoren
  • RISC-V-Kompatibilität: Unterstützung des Hart-Konzepts
  • FPGA-Optimierung: gemeinsame Ressourcen, minimale Logik

Wichtige Punkte

  • Schattenregister speichern den vollständigen Architekturzustand jedes Harts
  • Gemeinsame CSRs erfordern separate R/W-Ports für korrekten Betrieb
  • Hart-Aktivität steuert alle Schreibvorgänge und setzt die Pipeline zurück
  • Der Timer adressiert die hart_table zyklisch, was Round-Robin-Scheduling sicherstellt
  • Speicherschutz und Synchronisation sind Aufgabe des OS/RTOS

— Editorial Team

Advertisement 728x90

Weiterlesen