Processeur RISC-V 1C


Ce Projet en 2 mots est un projet de construction d’un processeur RISC-V, un processeur moderne et d’actualité. De faire la simulation sur un logiciel de simulation numérique. D’effectuer plusieurs tests d’instruction et de petits programmes sur le simulateur. Le transplanter ensuite sur du Hardware à l’aide d’une carte reconfigurable FPGA.

Le simulateur en question est appelé Digital. Ça nous a permis de faire l’implémentation, de faire des tests sur toutes les instructions implémentées, même de tester des petits programmes. Mais le plus important, c’est qu’il nous a permis de générer du code Verilog, qui sera après le médian pour la transplantation sur l’FPGA. Normalement, une Implémentation hardware se fait directement en utilisant le langage Verilog. Mais pour ma part, j’ai préféré une programmation visuelle qui facilite le débogage et le test me concernant. L’image en bas est une capture de l’ensemble du processeur (la majeur partie, c’est le DataPath), réalisé sous le simulateur Digital.

Cliquez sur l’image pour avoir un aperçu plus détaillé.

Implémentation


En voyant l’image en haut du processeur, Il est facile de constater que 90% de la figure représente le DataPath du processeur, Le reste c’est l’Unité de Commande. Le code couleur des composantes du processeur est assez explicite. On a le bleu clair pour les mémoires de masse, comme la RAM et la ROM. Le bleu foncé est pour l’Unité de Commande. Le jaune est pour le registre, ou la mémoire de petite taille. Le rouge est pour les circuits combinatoires, comme l’Unité Arithmétique et Logique ou les Additionneurs. La couleur blanche est pour les Multiplexeurs. Les petits triangles représentent des connexions qui ne sont pas dessinées, utilisés pour ne pas encombrer le schéma. On peut énumérer comme suite, la liste des composantes du processeur :

  1. Unité de Commande (CU) : C’est l’unité responsable de décoder l’instruction, et de déclencher les signaux de commande pour contrôler les autres composantes du DataPath.
  2. Program Counter (PC) : Le registre qui contient l’adresse de l’instruction actuellement en train de s’exécuter.
  3. ROM : Une mémoire morte qui contient la liste des instructions à exécuter.
  4. Regiter File (RF) : Un banc de registre qui contient tous les registres programmables (32 registres de 32 bits) du processeur.
  5. RAM : La mémoire centrale où les données des programmes sont stockées.
  6. Unité Arithmétique et Logique (ALU) : C’est l’unité responsable de toutes les opérations possibles arithmétiques et logiques du processeur.
  7. Additionneurs (+) : Circuits combinatoires qui font l’addition, Ils sont utilisés ici pour le calcul des adresses.
  8. Etendeurs de signe (sous forme de trapèze) : Circuits combinatoires pour convertir les entiers signés sur un nombre réduit de bits (12 bits par exemple) vers des représentations en 32 bits.
  9. Shifteur (sous forme de parallélogramme) : Circuit combinatoire pour le décalage des bits.
  10. Multiplexeurs (sous forme de trapézoïde isocèle) : Par l’intermédiaire des signaux de commande venant de l’Unité de Commande, ils permettent de contrôler le flux de données sur le DataPath.

Jeu d’instructions


Le Jeu d’Instruction choisi pour être implémenté sur ce processeur et le RV32I, à l’exception des 3 instructions ecall, ebreak, et fence, qui ne sont pas si indispensables pour l’exécution d’un programme normal. On peut voir l’ensemble des instructions dans le schéma en bas, ce sont les instructions en verre, en excluant les 3 déjà mentionnés. En sachant que le RISC-V est constitué de tel sort à être flexible pour inclure plusieurs parties de jeux d’instruction. Dans notre cas on a choisi d’implémenter que la base nécessaire. Ça veut dire, les instructions de base pour un traitement de données sur 32 bits.

Code Verilog


Digital a la capacité de générer automatiquement du code Verilog ou VHDL, dans notre cas on a fait la génération du code Verilog. Néanmoins, pour que le code soit compatible avec l’FPGA, des modifications ont été nécessaires à faire pour le rendre compatible. La plus importante, est le changement dans l’implantation de la mémoire RAM, car par défaut le code n’utilisait pas les blocs de mémoires intégrés dans l’FPGA nommés BRAM, la RAM était implémentée en utilisant les cellules logiques élémentaires. Ce qui a mené à l’utilisation d’un nombre très élevé des cellules logiques, rendant ainsi l’FPGA insuffisante pour contenir le processeur.

/*
 * Generated by Digital. Don't modify this file!
 * Any changes will be lost if this file is regenerated.
 */
module DIG_ROM_1024X32_Instructionsrom (
    input [9:0] A,
    input sel,
    output reg [31:0] D
);
    reg [31:0] my_rom [0:40];

    always @ (*) begin
        if (~sel)
            D = 32'h0;
        else if (A > 10'h28)
            D = 32'h0;
        else
            D = my_rom[A];
    end

    initial begin
        my_rom[0] = 32'h67800093;
        my_rom[1] = 32'h123450b7;
        my_rom[2] = 32'hfff00113;
        my_rom[3] = 32'habcde137;
        my_rom[4] = 32'h1101b3;
        my_rom[5] = 32'h40110233;
        my_rom[6] = 32'h1172b3;
        my_rom[7] = 32'h116333;
        my_rom[8] = 32'h1143b3;
        my_rom[9] = 32'h111433;
        my_rom[10] = 32'h1154b3;
        my_rom[11] = 32'h40115533;
        my_rom[12] = 32'habcde137;
        my_rom[13] = 32'h1125b3;
        my_rom[14] = 32'h113633;
        my_rom[15] = 32'h67810693;
        my_rom[16] = 32'h67817713;
        my_rom[17] = 32'h67816793;
        my_rom[18] = 32'h67814813;
        my_rom[19] = 32'h111893;
        my_rom[20] = 32'h215913;
        my_rom[21] = 32'h40315993;
        my_rom[22] = 32'h412a13;
        my_rom[23] = 32'h513a93;
        my_rom[24] = 32'hfa708ae3;
        my_rom[25] = 32'hfa8118e3;
        my_rom[26] = 32'hfa91d6e3;
        my_rom[27] = 32'hfaa274e3;
        my_rom[28] = 32'hfab2c2e3;
        my_rom[29] = 32'hfab360e3;
        my_rom[30] = 32'h1000a3;
        my_rom[31] = 32'h101123;
        my_rom[32] = 32'h102223;
        my_rom[33] = 32'h500083;
        my_rom[34] = 32'h704083;
        my_rom[35] = 32'h601083;
        my_rom[36] = 32'h405083;
        my_rom[37] = 32'h2083;
        my_rom[38] = 32'hf7dff06f;
        my_rom[39] = 32'hfffff017;
        my_rom[40] = 32'h67;
    end
endmodule


module Instructions_rom (
  input [31:0] addr,
  output [31:0] instr
);
  wire [9:0] s0;
  assign s0 = addr[11:2];
  // Instructions rom
  DIG_ROM_1024X32_Instructionsrom DIG_ROM_1024X32_Instructionsrom_i0 (
    .A( s0 ),
    .sel( 1'b1 ),
    .D( instr )
  );
endmodule
module DIG_RegisterFile
#(
    parameter Bits = 8,
    parameter AddrBits = 4
)
(
    input [(Bits-1):0] Din,
    input we,
    input [(AddrBits-1):0] Rw,
    input C,
    input [(AddrBits-1):0] Ra,
    input [(AddrBits-1):0] Rb,
    output [(Bits-1):0] Da,
    output [(Bits-1):0] Db
);

    reg [(Bits-1):0] memory[0:((1 << AddrBits)-1)];

    assign Da = memory[Ra];
    assign Db = memory[Rb];

    always @ (posedge C) begin
        if (we)
            memory[Rw] <= Din;
    end
endmodule


module register_file (
  input clock,
  input write,
  input [4:0] \A-addr ,
  input [4:0] \B-addr ,
  input [4:0] \wr-addr ,
  input [31:0] \write-reg ,
  output [31:0] A,
  output [31:0] B
);
  wire s0;
  assign s0 = (write & (\wr-addr [0] | \wr-addr [1] | \wr-addr [2] | \wr-addr [3] | \wr-addr [4]));
  // register file
  DIG_RegisterFile #(
    .Bits(32),
    .AddrBits(5)
  )
  DIG_RegisterFile_i0 (
    .Din( \write-reg  ),
    .we( s0 ),
    .Rw( \wr-addr  ),
    .C( clock ),
    .Ra( \A-addr  ),
    .Rb( \B-addr  ),
    .Da( A ),
    .Db( B )
  );
endmodule

module DIG_CounterPreset #(
    parameter Bits = 2,
    parameter maxValue = 4
)
(
    input C,
    input en,
    input clr,
    input dir,
    input [(Bits-1):0] in,
    input ld,
    output [(Bits-1):0] out,
    output ovf
);

    reg [(Bits-1):0] count = 'h0;

    function [(Bits-1):0] maxVal (input [(Bits-1):0] maxv);
        if (maxv == 0)
            maxVal = (1 << Bits) - 1;
        else
            maxVal = maxv;
    endfunction

    assign out = count;
    assign ovf = ((count == maxVal(maxValue) & dir == 1'b0)
                  | (count == 'b0 & dir == 1'b1))? en : 1'b0;

    always @ (posedge C) begin
        if (clr == 1'b1)
            count <= 'h0;
        else if (ld == 1'b1)
            count <= in;
        else if (en == 1'b1) begin
            if (dir == 1'b0) begin
                if (count == maxVal(maxValue))
                    count <= 'h0;
                else
                    count <= count + 1'b1;
            end
            else begin
                if (count == 'h0)
                    count <= maxVal(maxValue);
                else
                    count <= count - 1;
            end
        end
    end
endmodule


module program_counter (
  input clock,
  input reset,
  input write,
  input [31:0] \new-addr ,
  output [31:0] \instr-addr 
);
  // counter
  DIG_CounterPreset #(
    .Bits(32),
    .maxValue(0)
  )
  DIG_CounterPreset_i0 (
    .en( 1'b0 ),
    .C( clock ),
    .dir( 1'b0 ),
    .in( \new-addr  ),
    .ld( write ),
    .clr( reset ),
    .out( \instr-addr  )
  );
endmodule

module CompSigned #(
    parameter Bits = 1
)
(
    input [(Bits -1):0] a,
    input [(Bits -1):0] b,
    output \> ,
    output \= ,
    output \<
);
    assign \> = $signed(a) > $signed(b);
    assign \= = $signed(a) == $signed(b);
    assign \< = $signed(a) < $signed(b);
endmodule


module CompUnsigned #(
    parameter Bits = 1
)
(
    input [(Bits -1):0] a,
    input [(Bits -1):0] b,
    output \> ,
    output \= ,
    output \<
);
    assign \> = a > b;
    assign \= = a == b;
    assign \< = a < b;
endmodule


module Mux_2x1_NBits #(
    parameter Bits = 2
)
(
    input [0:0] sel,
    input [(Bits - 1):0] in_0,
    input [(Bits - 1):0] in_1,
    output reg [(Bits - 1):0] out
);
    always @ (*) begin
        case (sel)
            1'h0: out = in_0;
            1'h1: out = in_1;
            default:
                out = 'h0;
        endcase
    end
endmodule


module shifter_logical_left (
  input [31:0] in,
  input [4:0] shmt,
  output [31:0] out
);
  wire s0;
  wire s1;
  wire s2;
  wire s3;
  wire s4;
  wire [31:0] s5;
  wire [31:0] s6;
  wire [31:0] s7;
  wire [31:0] s8;
  wire [31:0] s9;
  wire [31:0] s10;
  wire [31:0] s11;
  wire [31:0] s12;
  wire [31:0] s13;
  assign s5[15:0] = 16'b0;
  assign s5[31:16] = in[15:0];
  assign s0 = shmt[0];
  assign s1 = shmt[1];
  assign s2 = shmt[2];
  assign s3 = shmt[3];
  assign s4 = shmt[4];
  Mux_2x1_NBits #(
    .Bits(32)
  )
  Mux_2x1_NBits_i0 (
    .sel( s4 ),
    .in_0( in ),
    .in_1( s5 ),
    .out( s6 )
  );
  assign s7[7:0] = 8'b0;
  assign s7[31:8] = s6[23:0];
  Mux_2x1_NBits #(
    .Bits(32)
  )
  Mux_2x1_NBits_i1 (
    .sel( s3 ),
    .in_0( s6 ),
    .in_1( s7 ),
    .out( s8 )
  );
  assign s9[3:0] = 4'b0;
  assign s9[31:4] = s8[27:0];
  Mux_2x1_NBits #(
    .Bits(32)
  )
  Mux_2x1_NBits_i2 (
    .sel( s2 ),
    .in_0( s8 ),
    .in_1( s9 ),
    .out( s10 )
  );
  assign s11[1:0] = 2'b0;
  assign s11[31:2] = s10[29:0];
  Mux_2x1_NBits #(
    .Bits(32)
  )
  Mux_2x1_NBits_i3 (
    .sel( s1 ),
    .in_0( s10 ),
    .in_1( s11 ),
    .out( s12 )
  );
  assign s13[0] = 1'b0;
  assign s13[31:1] = s12[30:0];
  Mux_2x1_NBits #(
    .Bits(32)
  )
  Mux_2x1_NBits_i4 (
    .sel( s0 ),
    .in_0( s12 ),
    .in_1( s13 ),
    .out( out )
  );
endmodule

module shifter_logic_arithmetic_right (
  input [31:0] in,
  input [4:0] shmt,
  input arith,
  output [31:0] out
);
  wire s0;
  wire s1;
  wire s2;
  wire s3;
  wire s4;
  wire [31:0] s5;
  wire [31:0] s6;
  wire [31:0] s7;
  wire [15:0] s8;
  wire [31:0] s9;
  wire [31:0] s10;
  wire [31:0] s11;
  wire [31:0] s12;
  wire [31:0] s13;
  wire [31:0] s14;
  wire [31:0] s15;
  wire s16;
  wire s17;
  assign s0 = shmt[0];
  assign s1 = shmt[1];
  assign s2 = shmt[2];
  assign s3 = shmt[3];
  assign s4 = shmt[4];
  assign s17 = in[31];
  assign s5[30:0] = in[30:0];
  assign s5[31] = s17;
  assign s16 = (arith & s17);
  Mux_2x1_NBits #(
    .Bits(16)
  )
  Mux_2x1_NBits_i0 (
    .sel( s16 ),
    .in_0( 16'b0 ),
    .in_1( 16'b1111111111111111 ),
    .out( s8 )
  );
  assign s6[15:0] = s5[31:16];
  assign s6[31:16] = s8;
  Mux_2x1_NBits #(
    .Bits(32)
  )
  Mux_2x1_NBits_i1 (
    .sel( s4 ),
    .in_0( s5 ),
    .in_1( s6 ),
    .out( s7 )
  );
  assign s9[23:0] = s7[31:8];
  assign s9[31:24] = s8[7:0];
  Mux_2x1_NBits #(
    .Bits(32)
  )
  Mux_2x1_NBits_i2 (
    .sel( s3 ),
    .in_0( s7 ),
    .in_1( s9 ),
    .out( s10 )
  );
  assign s11[27:0] = s10[31:4];
  assign s11[31:28] = s8[11:8];
  Mux_2x1_NBits #(
    .Bits(32)
  )
  Mux_2x1_NBits_i3 (
    .sel( s2 ),
    .in_0( s10 ),
    .in_1( s11 ),
    .out( s12 )
  );
  assign s13[29:0] = s12[31:2];
  assign s13[31:30] = s8[13:12];
  Mux_2x1_NBits #(
    .Bits(32)
  )
  Mux_2x1_NBits_i4 (
    .sel( s1 ),
    .in_0( s12 ),
    .in_1( s13 ),
    .out( s14 )
  );
  assign s15[30:0] = s14[31:1];
  assign s15[31] = s8[14];
  Mux_2x1_NBits #(
    .Bits(32)
  )
  Mux_2x1_NBits_i5 (
    .sel( s0 ),
    .in_0( s14 ),
    .in_1( s15 ),
    .out( out )
  );
endmodule
module DIG_Add
#(
    parameter Bits = 1
)
(
    input [(Bits-1):0] a,
    input [(Bits-1):0] b,
    input c_i,
    output [(Bits - 1):0] s,
    output c_o
);
   wire [Bits:0] temp;

   assign temp = a + b + c_i;
   assign s = temp [(Bits-1):0];
   assign c_o = temp[Bits];
endmodule



module Mux_8x1
(
    input [2:0] sel,
    input in_0,
    input in_1,
    input in_2,
    input in_3,
    input in_4,
    input in_5,
    input in_6,
    input in_7,
    output reg out
);
    always @ (*) begin
        case (sel)
            3'h0: out = in_0;
            3'h1: out = in_1;
            3'h2: out = in_2;
            3'h3: out = in_3;
            3'h4: out = in_4;
            3'h5: out = in_5;
            3'h6: out = in_6;
            3'h7: out = in_7;
            default:
                out = 'h0;
        endcase
    end
endmodule


module Mux_8x1_NBits #(
    parameter Bits = 2
)
(
    input [2:0] sel,
    input [(Bits - 1):0] in_0,
    input [(Bits - 1):0] in_1,
    input [(Bits - 1):0] in_2,
    input [(Bits - 1):0] in_3,
    input [(Bits - 1):0] in_4,
    input [(Bits - 1):0] in_5,
    input [(Bits - 1):0] in_6,
    input [(Bits - 1):0] in_7,
    output reg [(Bits - 1):0] out
);
    always @ (*) begin
        case (sel)
            3'h0: out = in_0;
            3'h1: out = in_1;
            3'h2: out = in_2;
            3'h3: out = in_3;
            3'h4: out = in_4;
            3'h5: out = in_5;
            3'h6: out = in_6;
            3'h7: out = in_7;
            default:
                out = 'h0;
        endcase
    end
endmodule


module arethmetic_and_logic_unit (
  input [31:0] A,
  input [31:0] B,
  input [2:0] func,
  input _30_ ,
  input _5_ ,
  output [31:0] Sum,
  output Bcond
);
  wire [31:0] s0;
  wire sub;
  wire [31:0] s1;
  wire [31:0] s2;
  wire [31:0] s3;
  wire [31:0] s4;
  wire [31:0] s5;
  wire [31:0] s6;
  wire [31:0] s7;
  wire [31:0] s8;
  wire [31:0] s9;
  wire s10;
  wire s11;
  wire s12;
  wire [4:0] s13;
  wire [4:0] s14;
  wire s15;
  wire s16;
  wire s17;
  assign s2 = (A ^ B);
  assign s3 = (A & B);
  assign s4 = (A | B);
  assign s5 = ~ B;
  // slt
  CompSigned #(
    .Bits(32)
  )
  CompSigned_i0 (
    .a( A ),
    .b( B ),
    .\= ( s10 ),
    .\< ( s11 )
  );
  // sltu
  CompUnsigned #(
    .Bits(32)
  )
  CompUnsigned_i1 (
    .a( A ),
    .b( B ),
    .\< ( s12 )
  );
  assign sub = (_30_  & _5_ );
  assign s13 = B[4:0];
  assign s14 = B[4:0];
  Mux_2x1_NBits #(
    .Bits(32)
  )
  Mux_2x1_NBits_i2 (
    .sel( sub ),
    .in_0( B ),
    .in_1( s5 ),
    .out( s0 )
  );
  assign s7[0] = s11;
  assign s7[31:1] = 31'b0;
  assign s8[0] = s12;
  assign s8[31:1] = 31'b0;
  assign s15 = ~ s10;
  assign s16 = ~ s11;
  assign s17 = ~ s12;
  shifter_logical_left shifter_logical_left_i3 (
    .in( A ),
    .shmt( s14 ),
    .out( s6 )
  );
  shifter_logic_arithmetic_right shifter_logic_arithmetic_right_i4 (
    .in( A ),
    .shmt( s13 ),
    .arith( _30_  ),
    .out( s9 )
  );
  // adder
  DIG_Add #(
    .Bits(32)
  )
  DIG_Add_i5 (
    .a( A ),
    .b( s0 ),
    .c_i( sub ),
    .s( s1 )
  );
  Mux_8x1 Mux_8x1_i6 (
    .sel( func ),
    .in_0( s10 ),
    .in_1( s15 ),
    .in_2( 1'b0 ),
    .in_3( 1'b0 ),
    .in_4( s11 ),
    .in_5( s16 ),
    .in_6( s12 ),
    .in_7( s17 ),
    .out( Bcond )
  );
  Mux_8x1_NBits #(
    .Bits(32)
  )
  Mux_8x1_NBits_i7 (
    .sel( func ),
    .in_0( s1 ),
    .in_1( s6 ),
    .in_2( s7 ),
    .in_3( s8 ),
    .in_4( s2 ),
    .in_5( s9 ),
    .in_6( s4 ),
    .in_7( s3 ),
    .out( Sum )
  );
endmodule

module Mux_4x1_NBits #(
    parameter Bits = 2
)
(
    input [1:0] sel,
    input [(Bits - 1):0] in_0,
    input [(Bits - 1):0] in_1,
    input [(Bits - 1):0] in_2,
    input [(Bits - 1):0] in_3,
    output reg [(Bits - 1):0] out
);
    always @ (*) begin
        case (sel)
            2'h0: out = in_0;
            2'h1: out = in_1;
            2'h2: out = in_2;
            2'h3: out = in_3;
            default:
                out = 'h0;
        endcase
    end
endmodule

module DIG_BitExtender #(
    parameter inputBits = 2,
    parameter outputBits = 4
)
(
    input [(inputBits-1):0] in,
    output [(outputBits - 1):0] out
);
    assign out = {{(outputBits - inputBits){in[inputBits - 1]}}, in};
endmodule




module sign_extender (
  input [11:0] \12_bits ,
  output [31:0] \32_bits 
);
  // Sign Extend
  DIG_BitExtender #(
    .inputBits(12),
    .outputBits(32)
  )
  DIG_BitExtender_i0 (
    .in( \12_bits  ),
    .out( \32_bits  )
  );
endmodule

module adder (
  input [31:0] A,
  input [31:0] B,
  output [31:0] Sum
);
  DIG_Add #(
    .Bits(32)
  )
  DIG_Add_i0 (
    .a( A ),
    .b( B ),
    .c_i( 1'b0 ),
    .s( Sum )
  );
endmodule

module large_sign_extender (
  input [19:0] \20_bits ,
  output [31:0] \32_bits 
);
  // Sign Extend
  DIG_BitExtender #(
    .inputBits(20),
    .outputBits(32)
  )
  DIG_BitExtender_i0 (
    .in( \20_bits  ),
    .out( \32_bits  )
  );
endmodule

module shifter (
  input [19:0] in,
  output [31:0] out
);
  assign out[11:0] = 12'b0;
  assign out[31:12] = in;
endmodule

// Rotates the bytes to match the correct ram components.
module strDataGen_inc (
  input [31:0] in,
  input [1:0] sh,
  output [7:0] D_0,
  output [7:0] D_1,
  output [7:0] D_2,
  output [7:0] D_3
);
  wire [31:0] s0;
  wire [31:0] s1;
  wire [31:0] s2;
  wire [31:0] s3;
  wire [7:0] s4;
  wire [7:0] s5;
  wire [7:0] s6;
  wire [7:0] s7;
  assign s4 = in[7:0];
  assign s5 = in[15:8];
  assign s6 = in[23:16];
  assign s7 = in[31:24];
  assign s0[7:0] = s7;
  assign s0[15:8] = s4;
  assign s0[23:16] = s5;
  assign s0[31:24] = s6;
  assign s1[7:0] = s6;
  assign s1[15:8] = s7;
  assign s1[23:16] = s4;
  assign s1[31:24] = s5;
  assign s2[7:0] = s5;
  assign s2[15:8] = s6;
  assign s2[23:16] = s7;
  assign s2[31:24] = s4;
  Mux_4x1_NBits #(
    .Bits(32)
  )
  Mux_4x1_NBits_i0 (
    .sel( sh ),
    .in_0( in ),
    .in_1( s0 ),
    .in_2( s1 ),
    .in_3( s2 ),
    .out( s3 )
  );
  assign D_0 = s3[7:0];
  assign D_1 = s3[15:8];
  assign D_2 = s3[23:16];
  assign D_3 = s3[31:24];
endmodule

// Creates the four store enable signals.
module strGen_inc (
  input [1:0] sh,
  input we,
  input [1:0] am,
  output str_0,
  output str_1,
  output str_2,
  output str_3
);
  wire [3:0] s0;
  wire [3:0] s1;
  wire [3:0] s2;
  wire [3:0] s3;
  wire [3:0] s4;
  wire s5;
  wire s6;
  wire s7;
  wire s8;
  wire [3:0] s9;
  Mux_4x1_NBits #(
    .Bits(4)
  )
  Mux_4x1_NBits_i0 (
    .sel( am ),
    .in_0( 4'b1111 ),
    .in_1( 4'b11 ),
    .in_2( 4'b1 ),
    .in_3( 4'b0 ),
    .out( s0 )
  );
  assign s5 = s0[0];
  assign s6 = s0[1];
  assign s7 = s0[2];
  assign s8 = s0[3];
  assign s1[0] = s8;
  assign s1[1] = s5;
  assign s1[2] = s6;
  assign s1[3] = s7;
  assign s2[0] = s7;
  assign s2[1] = s8;
  assign s2[2] = s5;
  assign s2[3] = s6;
  assign s3[0] = s6;
  assign s3[1] = s7;
  assign s3[2] = s8;
  assign s3[3] = s5;
  Mux_4x1_NBits #(
    .Bits(4)
  )
  Mux_4x1_NBits_i1 (
    .sel( sh ),
    .in_0( s0 ),
    .in_1( s1 ),
    .in_2( s2 ),
    .in_3( s3 ),
    .out( s4 )
  );
  Mux_2x1_NBits #(
    .Bits(4)
  )
  Mux_2x1_NBits_i2 (
    .sel( we ),
    .in_0( 4'b0 ),
    .in_1( s4 ),
    .out( s9 )
  );
  assign str_0 = s9[0];
  assign str_1 = s9[1];
  assign str_2 = s9[2];
  assign str_3 = s9[3];
endmodule
module DIG_RAMDualPort
#(
    parameter Bits = 8,
    parameter AddrBits = 4
)
(
  input [(AddrBits-1):0] A,
  input [(Bits-1):0] Din,
  input str,
  input C,
  input ld,
  output reg [(Bits-1):0] D
);
  reg [(Bits-1):0] memory[0:((1 << AddrBits) - 1)];

  //assign D = memory[A];
  
  always @ (negedge C) begin
		D <= memory[A];
  end

  always @ (posedge C) begin
    if (str)
      memory[A] <= Din;
  end
endmodule


// Rotates the output bytes to the right order.
module outDataGen_inc (
  input [7:0] in_0,
  input [7:0] in_1,
  input [7:0] in_2,
  input [7:0] in_3,
  input [1:0] sh,
  input sign, // signed output
  output [31:0] D_32,
  output [31:0] D_16,
  output [31:0] D_8
);
  wire [31:0] s0;
  wire [31:0] s1;
  wire [31:0] s2;
  wire [31:0] s3;
  wire [31:0] D_32_temp;
  wire [15:0] s4;
  wire [7:0] s5;
  wire [31:0] s6;
  wire [31:0] s7;
  wire [31:0] s8;
  wire [31:0] s9;
  assign s1[7:0] = in_1;
  assign s1[15:8] = in_2;
  assign s1[23:16] = in_3;
  assign s1[31:24] = in_0;
  assign s2[7:0] = in_2;
  assign s2[15:8] = in_3;
  assign s2[23:16] = in_0;
  assign s2[31:24] = in_1;
  assign s3[7:0] = in_3;
  assign s3[15:8] = in_0;
  assign s3[23:16] = in_1;
  assign s3[31:24] = in_2;
  assign s0[7:0] = in_0;
  assign s0[15:8] = in_1;
  assign s0[23:16] = in_2;
  assign s0[31:24] = in_3;
  Mux_4x1_NBits #(
    .Bits(32)
  )
  Mux_4x1_NBits_i0 (
    .sel( sh ),
    .in_0( s0 ),
    .in_1( s1 ),
    .in_2( s2 ),
    .in_3( s3 ),
    .out( D_32_temp )
  );
  assign s4 = D_32_temp[15:0];
  assign s5 = D_32_temp[7:0];
  assign s6[15:0] = s4;
  assign s6[31:16] = 16'b0;
  assign s7[7:0] = s5;
  assign s7[31:8] = 24'b0;
  DIG_BitExtender #(
    .inputBits(16),
    .outputBits(32)
  )
  DIG_BitExtender_i1 (
    .in( s4 ),
    .out( s8 )
  );
  DIG_BitExtender #(
    .inputBits(8),
    .outputBits(32)
  )
  DIG_BitExtender_i2 (
    .in( s5 ),
    .out( s9 )
  );
  Mux_2x1_NBits #(
    .Bits(32)
  )
  Mux_2x1_NBits_i3 (
    .sel( sign ),
    .in_0( s6 ),
    .in_1( s8 ),
    .out( D_16 )
  );
  Mux_2x1_NBits #(
    .Bits(32)
  )
  Mux_2x1_NBits_i4 (
    .sel( sign ),
    .in_0( s7 ),
    .in_1( s9 ),
    .out( D_8 )
  );
  assign D_32 = D_32_temp;
endmodule

// A 32-bit memory that allows byte access and can handle non-aligned 
// memory addresses.
module RAM32Bit_gen0 (
  input [9:0] A, // The address to use.
  input [31:0] D_in, // The data to store.
  input we, // write enable
  input [1:0] am, // The addressing mode.
                  // 0: word;
                  // 1: half word;
                  // 2: byte
  input \signed , // if set, half words and bytes preserve sign.
  input C,
  output [31:0] D_out // The data output.

);
  wire [7:0] addr;
  wire A_0;
  wire A_1;
  wire [1:0] A_01;
  wire [7:0] s0;
  wire [7:0] D_0;
  wire str_0;
  wire [7:0] s1;
  wire [7:0] s2;
  wire [7:0] D_1;
  wire str_1;
  wire [7:0] s3;
  wire [7:0] s4;
  wire [7:0] D_2;
  wire str_2;
  wire [7:0] s5;
  wire [7:0] D_3;
  wire str_3;
  wire [7:0] s6;
  wire s7;
  wire [7:0] \addr+1 ;
  wire s8;
  wire [31:0] s9;
  wire [31:0] s10;
  wire [31:0] s11;
  assign addr = A[9:2];
  assign A_0 = A[0];
  assign A_1 = A[1];
  assign A_01 = A[1:0];
  DIG_Add #(
    .Bits(8)
  )
  DIG_Add_i0 (
    .a( 8'b1 ),
    .b( addr ),
    .c_i( 1'b0 ),
    .s( \addr+1  )
  );
  assign s7 = (A_0 | A_1);
  assign s8 = (A_0 & A_1);
  strDataGen_inc strDataGen_inc_i1 (
    .in( D_in ),
    .sh( A_01 ),
    .D_0( D_0 ),
    .D_1( D_1 ),
    .D_2( D_2 ),
    .D_3( D_3 )
  );
  strGen_inc strGen_inc_i2 (
    .sh( A_01 ),
    .we( we ),
    .am( am ),
    .str_0( str_0 ),
    .str_1( str_1 ),
    .str_2( str_2 ),
    .str_3( str_3 )
  );
  // R3
  DIG_RAMDualPort #(
    .Bits(8),
    .AddrBits(8)
  )
  DIG_RAMDualPort_i3 (
    .A( addr ),
    .Din( D_3 ),
    .str( str_3 ),
    .C( C ),
    .ld( 1'b1 ),
    .D( s6 )
  );
  Mux_2x1_NBits #(
    .Bits(8)
  )
  Mux_2x1_NBits_i4 (
    .sel( s7 ),
    .in_0( addr ),
    .in_1( \addr+1  ),
    .out( s0 )
  );
  Mux_2x1_NBits #(
    .Bits(8)
  )
  Mux_2x1_NBits_i5 (
    .sel( A_1 ),
    .in_0( addr ),
    .in_1( \addr+1  ),
    .out( s2 )
  );
  Mux_2x1_NBits #(
    .Bits(8)
  )
  Mux_2x1_NBits_i6 (
    .sel( s8 ),
    .in_0( addr ),
    .in_1( \addr+1  ),
    .out( s4 )
  );
  // R0
  DIG_RAMDualPort #(
    .Bits(8),
    .AddrBits(8)
  )
  DIG_RAMDualPort_i7 (
    .A( s0 ),
    .Din( D_0 ),
    .str( str_0 ),
    .C( C ),
    .ld( 1'b1 ),
    .D( s1 )
  );
  // R1
  DIG_RAMDualPort #(
    .Bits(8),
    .AddrBits(8)
  )
  DIG_RAMDualPort_i8 (
    .A( s2 ),
    .Din( D_1 ),
    .str( str_1 ),
    .C( C ),
    .ld( 1'b1 ),
    .D( s3 )
  );
  // R2
  DIG_RAMDualPort #(
    .Bits(8),
    .AddrBits(8)
  )
  DIG_RAMDualPort_i9 (
    .A( s4 ),
    .Din( D_2 ),
    .str( str_2 ),
    .C( C ),
    .ld( 1'b1 ),
    .D( s5 )
  );
  outDataGen_inc outDataGen_inc_i10 (
    .in_0( s1 ),
    .in_1( s3 ),
    .in_2( s5 ),
    .in_3( s6 ),
    .sh( A_01 ),
    .sign( \signed  ),
    .D_32( s9 ),
    .D_16( s10 ),
    .D_8( s11 )
  );
  Mux_4x1_NBits #(
    .Bits(32)
  )
  Mux_4x1_NBits_i11 (
    .sel( am ),
    .in_0( s9 ),
    .in_1( s10 ),
    .in_2( s11 ),
    .in_3( 32'b0 ),
    .out( D_out )
  );
endmodule

module data_ram (
  input clock,
  input write,
  input [31:0] address,
  input [31:0] \data-in ,
  input [2:0] func,
  output [31:0] \data-out 
);
  wire [9:0] s0;
  wire [1:0] s1;
  wire s2;
  wire s3;
  assign s2 = ~ func[2];
  assign s0 = address[9:0];
  assign s3 = func[0];
  assign s1[0] = s3;
  assign s1[1] = ~ (s3 | func[1]);
  RAM32Bit_gen0 RAM32Bit_gen0_i0 (
    .A( s0 ),
    .D_in( \data-in  ),
    .we( write ),
    .am( s1 ),
    .\signed ( s2 ),
    .C( clock ),
    .D_out( \data-out  )
  );
endmodule

module control_unit (
  input [4:0] instr,
  output [1:0] PC_mux,
  output RF_w,
  output A_mux,
  output [1:0] B_mux,
  output ALU_mux,
  output RAM_w,
  output [1:0] RF_mux,
  output SUM_mux
);
  wire s0;
  wire s1;
  wire SUM_mux_temp;
  wire s2;
  wire s3;
  wire s4;
  wire s5;
  wire s6;
  wire s7;
  wire s8;
  assign s4 = instr[0];
  assign s5 = instr[1];
  assign s3 = instr[2];
  assign s0 = instr[3];
  assign s6 = instr[4];
  assign s1 = ~ s0;
  assign s2 = ~ s3;
  assign s7 = ~ s6;
  assign s8 = ~ s5;
  assign SUM_mux_temp = ~ s4;
  assign PC_mux[0] = ((SUM_mux_temp & s6) | s5);
  assign PC_mux[1] = (SUM_mux_temp | s3);
  assign B_mux[0] = (s4 | s1);
  assign B_mux[1] = ((s4 & s3) | (s2 & s0 & s7));
  assign RF_mux[0] = ((s4 & s1) | (SUM_mux_temp & s3) | (s4 & s2));
  assign RF_mux[1] = s2;
  assign RF_w = (s4 | s3 | s1);
  assign RAM_w = (s2 & s0 & s7);
  assign ALU_mux = ((s4 & s3) | (s2 & s7));
  assign A_mux = (SUM_mux_temp | s2);
  assign SUM_mux = SUM_mux_temp;
endmodule

//////////////////////////////////////main entry ////////////////////////////
	

module cpu (
				input         clk_50mhz  ,
				input         rst_n      , 
				output[31:0]  alu_result   );
				
  wire [31:0] s0;
  wire [31:0] s1;
  wire [6:0] opcode_6_0 ;
  wire [4:0] rd_11_7 ;
  wire [19:0] imm_31_12 ;
  wire [7:0] imm_19_12 ;
  wire [11:0] imm_31_20 ;
  wire [2:0] funct3_14_12 ;
  wire [4:0] rs1_19_15 ;
  wire [4:0] rs2_24_20 ;
  wire [6:0] imm_31_25 ;
  wire _31_ ;
  wire RF_w;
  wire [31:0] s2;
  wire [31:0] s3;
  wire [31:0] s4;
  wire [31:0] s5;
  wire [31:0] s6;
  wire s7;
  wire [31:0] s8;
  wire [31:0] s9;
  wire [2:0] s10;
  wire _30_ ;
  wire _5_ ;
  wire [1:0] B_mux;
  wire [31:0] s11;
  wire [31:0] s12;
  wire [31:0] s13;
  wire [31:0] \PC+4 ;
  wire [31:0] s14;
  wire [31:0] s15;
  wire [11:0] s16;
  wire [31:0] s17;
  wire [31:0] s18;
  wire [19:0] s19;
  wire [31:0] s20;
  wire [31:0] s21;
  wire [31:0] s22;
  wire [1:0] PC_mux;
  wire [31:0] s23;
  wire RAM_w;
  wire [31:0] s24;
  wire [1:0] RF_mux;
  wire [11:0] s25;
  wire SUM_mux;
  wire A_mux;
  wire ALU_mux;
  wire [4:0] s26;
  
reg[31:0]  counter  ; 
reg        clk_1hz  ;

always @(posedge clk_50mhz or negedge rst_n)

begin
	if (!rst_n) 
		begin 
			counter <= 0    ;
			clk_1hz <= 1'b0 ;
		end 
	else 
		begin 
			if (counter == 25_000_000 - 1) 
				begin
					counter <= 0;         
					clk_1hz <= ~clk_1hz      ; 
				end 
			else
				begin
					counter <= counter + 1   ; 
				end
		end
end

assign  clk = clk_1hz  ;
  
  Instructions_rom Instructions_rom_i0 (
    .addr( s0 ),
    .instr( s1 )
  );
  register_file register_file_i1 (
    .clock( clk ),
    .write( RF_w ),
    .\A-addr ( rs1_19_15  ),
    .\B-addr ( rs2_24_20  ),
    .\wr-addr ( rd_11_7  ),
    .\write-reg ( s2 ),
    .A( s3 ),
    .B( s4 )
  );
  program_counter program_counter_i2 (
    .clock( clk ),
	 .reset( ~rst_n ),
    .write( 1'b1 ),
    .\new-addr ( s5 ),
    .\instr-addr ( s0 )
  );
  arethmetic_and_logic_unit arethmetic_and_logic_unit_i3 (
    .A( s8 ),
    .B( s9 ),
    .func( s10 ),
    ._30_ ( _30_  ),
    ._5_ ( _5_  ),
    .Sum( s6 ),
    .Bcond( s7 )
  );
  Mux_4x1_NBits #(
    .Bits(32)
  )
  Mux_4x1_NBits_i4 (
    .sel( B_mux ),
    .in_0( s4 ),
    .in_1( s11 ),
    .in_2( s12 ),
    .in_3( s13 ),
    .out( s9 )
  );
  // Sign extender
  sign_extender sign_extender_i5 (
    .\12_bits ( imm_31_20  ),
    .\32_bits ( s11 )
  );
  adder adder_i6 (
    .A( 32'b100 ),
    .B( s0 ),
    .Sum( \PC+4  )
  );
  adder adder_i7 (
    .A( s15 ),
    .B( s0 ),
    .Sum( s14 )
  );
  assign s16[3:0] = rd_11_7 [4:1];
  assign s16[9:4] = imm_31_25 [5:0];
  assign s16[10] = rd_11_7 [0];
  assign s16[11] = _31_ ;
  // Sign extender
  sign_extender sign_extender_i8 (
    .\12_bits ( s16 ),
    .\32_bits ( s17 )
  );
  assign s18[0] = 1'b0;
  assign s18[31:1] = s17[30:0];
  assign s19[9:0] = imm_31_20 [10:1];
  assign s19[10] = imm_31_20 [0];
  assign s19[18:11] = imm_19_12 ;
  assign s19[19] = _31_ ;
  large_sign_extender large_sign_extender_i9 (
    .\20_bits ( s19 ),
    .\32_bits ( s20 )
  );
  assign s21[0] = 1'b0;
  assign s21[31:1] = s20[30:0];
  assign s22[0] = 1'b0;
  assign s22[31:1] = s6[31:1];
  Mux_4x1_NBits #(
    .Bits(32)
  )
  Mux_4x1_NBits_i10 (
    .sel( PC_mux ),
    .in_0( s22 ),
    .in_1( s14 ),
    .in_2( \PC+4  ),
    .in_3( s23 ),
    .out( s5 )
  );
  Mux_2x1_NBits #(
    .Bits(32)
  )
  Mux_2x1_NBits_i11 (
    .sel( s7 ),
    .in_0( \PC+4  ),
    .in_1( s14 ),
    .out( s23 )
  );
  shifter shifter_i12 (
    .in( imm_31_12  ),
    .out( s13 )
  );
  data_ram data_ram_i13 (
    .clock( clk ),
    .write( RAM_w ),
    .address( s6 ),
    .\data-in ( s4 ),
    .func( funct3_14_12  ),
    .\data-out ( s24 )
  );
  Mux_4x1_NBits #(
    .Bits(32)
  )
  Mux_4x1_NBits_i14 (
    .sel( RF_mux ),
    .in_0( s13 ),
    .in_1( s6 ),
    .in_2( s24 ),
    .in_3( \PC+4  ),
    .out( s2 )
  );
  assign s25[4:0] = rd_11_7 ;
  assign s25[11:5] = imm_31_25 ;
  Mux_2x1_NBits #(
    .Bits(32)
  )
  Mux_2x1_NBits_i15 (
    .sel( SUM_mux ),
    .in_0( s21 ),
    .in_1( s18 ),
    .out( s15 )
  );
  Mux_2x1_NBits #(
    .Bits(32)
  )
  Mux_2x1_NBits_i16 (
    .sel( A_mux ),
    .in_0( s0 ),
    .in_1( s3 ),
    .out( s8 )
  );
  // Sign extender
  sign_extender sign_extender_i17 (
    .\12_bits ( s25 ),
    .\32_bits ( s12 )
  );
  Mux_2x1_NBits #(
    .Bits(3)
  )
  Mux_2x1_NBits_i18 (
    .sel( ALU_mux ),
    .in_0( funct3_14_12  ),
    .in_1( 3'b0 ),
    .out( s10 )
  );
  control_unit control_unit_i19 (
    .instr( s26 ),
    .PC_mux( PC_mux ),
    .RF_w( RF_w ),
    .A_mux( A_mux ),
    .B_mux( B_mux ),
    .ALU_mux( ALU_mux ),
    .RAM_w( RAM_w ),
    .RF_mux( RF_mux ),
    .SUM_mux( SUM_mux )
  );
  assign opcode_6_0  = s1[6:0];
  assign rd_11_7  = s1[11:7];
  assign imm_31_12  = s1[31:12];
  assign imm_19_12  = imm_31_12 [7:0];
  assign imm_31_20  = imm_31_12 [19:8];
  assign funct3_14_12  = imm_19_12 [2:0];
  assign rs1_19_15  = imm_19_12 [7:3];
  assign rs2_24_20  = imm_31_20 [4:0];
  assign imm_31_25  = imm_31_20 [11:5];
  assign _31_  = imm_31_25 [6];
  assign _30_  = imm_31_12 [18];
  assign s26 = opcode_6_0 [6:2];
  assign _5_  = opcode_6_0 [5];
  assign alu_result = s6;
endmodule

Impentation sur FPGA Cyclone IV


Une FPGA de type Intel Altera Cyclone IV a été utilisé pour l’implémentation du processeur sur hardware. La référence exacte de l’FPGA est EP4CE10, avec une fréquence de 50 MHz, 164 pins, 10k d’éléments logiques, et de 50 KB de mémoire. Pour pouvoir observer un aperçu de l’exécution du processeur sur l‘FPGA qui est très rapide, le code a été modifié de telle sorte de changer la fréquence de l’horloge de 50 Mhz à 1 hertz. Et les 4 premiers bits de la sortie de l’UAL sont affichés sur les 4 LED directement intégrées à l’FPGA. La démonstration en vidéo en bas montre cette exécution.

RISC-V sur une FPGA Cyclone IV

La compilation du code Verilog a été faite sur EDA d’Intel Quartus Prime Lite edition, et le résumé du résultat de la compilation est exposé sur l’image suivante :

Étant non expérimenté avec les FPGAs, je tiens à signaler que l’IA de Google Gemini 2.5, m’a beaucoup aidé dans le processus de l’implantation sur FPGA. Je lui ai même uploadé le code verilog de la conception du processeur, et elle a réussi à trouver un bug que je n’ai pas détecté avec les quelques tests unitaires que j’ai effectués.