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.
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 :
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 :
- 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
Aucun commentaire pour le moment.