Retour à l'accueil

Processeur logiciel RISC-V multithreadé pour FPGA

L'article décrit l'implémentation d'un processeur logiciel RISC-V multithreadé pour FPGA avec registres d'ombre et commutation de hart matérielle. Le code Verilog complet des modules clés est fourni : reg_file, PC, CSR, hart_table. L'approche simplifie la gestion des interruptions et optimise les ressources FPGA.

Multithreading matériel RISC-V dans FPGA : implémentation complète
Advertisement 728x90

Implémentation du Multithreading Matériel dans un Processeur Soft-Core RISC-V pour FPGA

Une microarchitecture multithread pour un processeur soft-core basé sur RISC-V utilise des registres fantômes pour stocker les états des harts, permettant une commutation de contexte rapide sans intervention logicielle. Cela simplifie la gestion des événements asynchrones dans les projets FPGA, en minimisant la charge du contrôleur d'interruptions et en réduisant le volume de code. L'implémentation cible les systèmes avec un seul niveau de privilège et un espace d'adressage partagé.

Principes Fondamentaux de la Microarchitecture

L'élément clé est constitué de tableaux de registres fantômes pour chaque hart (selon la terminologie RISC-V). Chaque hart sauvegarde son état dans des copies des registres x0-x31 et du PC. L'UAL, la mémoire, les périphériques et les registres CSR restent partagés. La protection des données est implémentée au niveau logiciel.

Le jeu d'instructions de base I+Zicsr est pris en charge. La gestion des threads est assurée via les registres CSR et une table de harts (hart_table). La commutation de hart se produit cycliquement en fonction du minuteur système, ses bits de poids faible adressant la table.

Google AdInline article slot

Les bits d'activité des harts contrôlent les écritures dans le PC, les registres et la mémoire, et réinitialisent le pipeline de l'UAL pour les threads inactifs.

Module de Registres CSR

Le bloc CSR est divisé en ports de lecture et d'écriture indépendants pour la compatibilité multithread :

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

Fichier de Registres avec Support des Harts

Le fichier de registres multithread implémente un tableau RAM pour 8 harts. Les opérations de lecture/écriture sont adressées par les numéros hart_in/hart_out :

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

Compteur de Programme pour les Threads

Le PC est implémenté comme un tableau de registres initialisé à partir d'un fichier pc.txt. Les opérations d'incrémentation et de chargement n'ont lieu que pour le hart actif :

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

Table de Harts et Gestion

La hart_table est une ROM avec 8 entrées (4 bits : 1 bit d'activité + 3 bits de numéro de hart), adressée par le minuteur. Le minuteur système 64 bits sert à la fois de compteur cyclique et de chronomètre système.

Liste complète des modules :

Google AdInline article slot
  • rv_pc — compteur de programme multithread
  • rv_mem — mémoire unifiée
  • rv_desh — décodeur avec réinitialisation du pipeline
  • rv_reg_file — registres fantômes
  • rv_alu_v — UAL pipelinée
  • rv_csr — registres spéciaux partagés
  • rv_hart_reg — état des harts
  • rv_timer — minuteur 64 bits
  • rv_hart_table — configuration des threads

Avantages de l'Implémentation

  • Commutation Rapide : basée sur le matériel, sans sauvegarde/restauration des registres
  • Simplification des IRQ : threads au lieu de vecteurs d'interruption
  • Compatibilité RISC-V : support du concept de hart
  • Optimisation FPGA : ressources partagées, logique minimale

Points Clés

  • Les registres fantômes stockent l'état architectural complet de chaque hart
  • Les CSR partagés nécessitent des ports R/W séparés pour un fonctionnement correct
  • L'activité des harts contrôle toutes les écritures et réinitialise le pipeline
  • Le minuteur adresse la hart_table cycliquement, assurant une planification round-robin
  • La protection mémoire et la synchronisation relèvent de la responsabilité de l'OS/RTOS

— Editorial Team

Advertisement 728x90

Lire ensuite