RISC-V для FPGA - архитектура, микроархитектурные реализации

Многотактное процессорное ядро

Разбить на страницы
Показывать лекцию целиком

Попробуем немного улучшить предыдущий вариант процессорного ядра, проведя повторную ревизию модулей ядра, проанализировав систему команд и возможные преобразования сигналов и данных, которые необходимо будет сделать.

Подход к построению многотактного процессорного ядра выбран не совсем классический, введем изначально некоторое количество т.н. "неархитектурных регистров" которые будут хранить промежуточные состояния.

И так, еще раз наши микроархитектурные блоки однотактного варианта:

  • rv_pc - программный счетчик;
  • rv_mem - блок памяти (программная и оперативная;
  • rv_desh - дешифратор команд (слова-инструкции);
  • rv_imm - формирователь непосредственного значения из слова-интсрукции;
  • rv_reg_file - файл-регистр
  • rv_ops_mux - коммутатор операндов для АЛУ;
  • rv_cmp - формирователь сигнала разрешения перехода;
  • rv_alu_v - АЛУ;
  • rv_rez_mux - коммутатор результатов;
  • rv_csr - блок регистров специального назначения.
  • Пробуем рассмотреть возможные сигналы и действия на каждом из этапов. Промежуточные результаты каждого из этапов фиксируются в неархитектурных регистрах. Да, таким образом количество задействованных ресурсов возрастает, но в качестве упражнения такой подход может иметь место.

    Предлагается разбиение функциональности на несколько уровней:

  • L0 - выборка текущего значения программного счетчика (точнее пары значений - PC и РС+4).
  • L1 - выборка слова-инструкции из памяти.
  • L2 - дешифрация инструкции - выделение адресов операндов, типа инструкции, формирование управляющих сигналов.
  • L3 - выборка операндов из файл-регистров (в рассматриваемой микроархитектуре - это файл-регистр и блок регистров специальных функций).
  • L4 - работа АЛУ.
  • L5 - чтение/запись данных из оперативной памяти.
  • L6 - запись данных в файл-регистры и нового значения в программный счётчик.
  • Вся структура управляется кольцевым счетчиком поочередно разрешающим работу модулей каждого из уровней. Для этого модули процессорного ядра должны быть дополнены сигналами разрешения работы (особенно модули с элементами памяти). Такой модификации подвергнутся - программный счетчик, добавим разрешение на запись в файл-регистр (запись результата в порт Rd/регистра по адресу rd), разрешение на запись в блок регистров специальных функций.

    "Новый" программный счетчик:

    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
    );
    reg [WIDTH-1:0] pc_reg;
    always@(posedge clk or negedge rst_n)
      begin
        if(!rst_n)
          pc_reg   <= 32'h00;
        else
        if (en) begin
          if(pc_load==1'b1)
            pc_reg <= pc_next;
          else
            pc_reg <= pc_reg + 3'h4;
          end
        end
      //end///*
    assign pc = pc_reg;
    always @ *
      begin
      pc_plus <= pc_reg + 3'h4;
      end
    // */    
    Endmodule
    

    Модификация файл-регистра:

    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 we,
      input en
    );
      reg [DATA_WIDTH-1:0] ram[0:2**ADDR_WIDTH-1];
      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] <= Rd_input;
      end
    
    always @ (*)
      begin
        Rs1_out <= rs1_nonzero ? ram[rs1] : 32'h0;
        Rs2_out <= rs2_nonzero ? ram[rs2] : 32'h0;
      end
    endmodule
    

    И блок регистров специального назначения (пока это просто код-заглушка):

    module rv_csr
    #(
      parameter DATA_WIDTH=32, 
      parameter ADDR_WIDTH=12,
      parameter CSR_size = 32
      )
    ( input clk,
      input [(ADDR_WIDTH-1):0] csr_addr,
      input [(DATA_WIDTH-1):0] csr_in,
      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)
        32'h0 : begin
          if (csr_wren) begin
            csr_reg[0] <= csr_in;
          end 
          else begin
            csr_out <= csr_reg[0];
          end
        end
        default: begin
          csr_out<=32'h0;
        end
      endcase
    end
    
    endmodule
    

    Уровни "разделяются" промежуточными регистрами (обычные регистры хранения), которые фиксируют данные (регистры R0 - R7). Ряд сигналов просто "пробрасываются" по этапам (напрямую между регистрами) в том случае, если они не задействованы в них, но требуются на последующих.

    L0 - выборка текущего значения программного счетчика

    Текущее значение программного счетчика и его инкремента (PC+4) запоминаются в регистре R1. Выходными линиями, соответствующими PC, адресуется память (память программ). Также пара PC, PC+4 с выхода R1 будет подана на вход регистра R2. Программному счетчику добавлен вход разрешения работы (en).

    L1 - выборка слова-инструкции из памяти

    В регистре R2 фиксируется слово инструкции из памяти и текущие значения пары PC, PC+4. Выход регистра подается на декодер инструкции и на схемы формирования непосредственного значения (immediate). Значения счетчиков передаются на вход следующего регистра (R3).

    (рис 6.1) Выборка текущего значения программного счетчика
    wire [31:0]l0r1_PC;
    wire [31:0]l0r1_PCplus;
    wire [31:0]r1l1_PC;
    wire [31:0]r1l1_PCplus;
    
    rv_pc pc( // programm counter
      .clk(clk),
      .rst_n(rst),
      .en(en_level[6]),
      .pc_load(r6l6_PC_load),
      .pc_next(r6l6_Rez),
      .pc(l0r1_PC),
      .pc_plus(l0r1_PCplus)
    );
    wire [255:0] r1_in;
    assign r1_in = {l0r1_PC, l0r1_PCplus, 192'h0}; 
    wire [255:0] r1_out;
    wire [191:0] r1_null;
    assign {r1l1_PC, r1l1_PCplus, r1_null} = r1_out; 
    
    rv_r_reg R1(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[0]),
      .r_in(r1_in),
      .r_out(r1_out)
    );
    

    Адрес инструкции на память подается со сдвигом на два бита - таким образом учитывается, что реализованная в примере микроархитектура использует 32-битную память.

    (рис 6.2) Выборка слова-инструкции из памяти
    wire [31:0] l1r2_iw;
    wire [31:0] l1r2_PC;
    wire [31:0] l1r2_PCplus;
    
    rv_mem mem( // system memory
      .clk(clk),
      //.i_addr(32'h0), //(r1l1_PC),
      .i_addr(r1l1_PC>>2),
      .code_out(l1r2_iw),
      .d_addr(r5l5_Rez>>2),
      .d_out(l5r6_Mem_data),
      .d_in(r5l5_Rs2_reg),
      .we(r5l5_mem_wr),
      .en(en_level[5])
    );
    // L1_R2
    wire [31:0]r2l2_iw;
    wire [31:0]r2l2_PC;
    wire [31:0]r2l2_PCplus;
    wire [255:0] r2_in;
    wire [255:0] r2_out;
    assign r2_in = {l1r2_iw, r1l1_PC, r1l1_PCplus, 160'h0};
    wire [159:0] r2_null;
    assign {r2l2_iw, r2l2_PC, r2l2_PCplus, r2_null} = r2_out;
    rv_r_reg R2(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[1]),
      .r_in(r2_in),
      .r_out(r2_out)
    );
    

    L2 - дешифрация инструкции

    Из слова-инструкции идет выделение адресов регистров-операндов, типа (опкодов и функциональных полей) инструкции, формирование управляющих сигналов на запись в файл-регистры (регистров общего назначения и специальных регистров).

    (рис 6.3) Дешифровка инструкции
    wire [4:0] l2r3_rs1;
    wire [4:0] l2r3_rs2;
    wire [6:0] l2r3_op;
    wire [2:0] l2r3_fn3;
    wire [6:0] l2r3_fn7;
    wire [4:0] l2r3_rd;
    wire l2r3_rd_wr;
    wire l2r3_mem_wr;
    wire l2r3_csr_wr;
    wire [31:0] l2r3_imm;
    wire [31:0] l2r3_PC;
    wire [31:0] l2r3_PCplus;
    rv_desh desh( // instruction decoder
      .inst(r2l2_iw),
      .opcode(l2r3_op),
      .rs1(l2r3_rs1),
      .rs1_en(),
      .rs2(l2r3_rs2),
      .rs2_en(),
      .rd(l2r3_rd),
      .rd_wr(l2r3_rd_wr),
      .funct3(l2r3_fn3),
      .f3_en(),
      .funct7(l2r3_fn7),
      .f7_en(),
      .mem_en(),
      .mem_wr(l2r3_mem_wr),
      .csr_en(),
      .csr_wr(l2r3_csr_wr),
      .pc_load()
    );
    rv_imm immed(  // immediate decoder
      .inst(r2l2_iw),
      .imm(l2r3_imm)
    );
    wire [4:0] r3l3_rs1;
    wire [4:0] r3l3_rs2;
    wire [6:0] r3l3_op;
    wire [2:0] r3l3_fn3;
    wire [6:0] r3l3_fn7;
    wire [4:0] r3l3_rd;
    wire r3l3_rd_wr;
    wire r3l3_mem_wr;
    wire r3l3_csr_wr;
    wire [31:0] r3l3_imm;
    wire [31:0] r3l3_PC;
    wire [31:0] r3l3_PCplus;
    wire [255:0] r3_in;
    wire [255:0] r3_out;
    assign r3_in = {l2r3_rs1, l2r3_rs2, l2r3_op, l2r3_fn3, l2r3_fn7, l2r3_rd, l2r3_rd_wr,
        l2r3_mem_wr, l2r3_csr_wr, l2r3_imm,  r2l2_PC, r2l2_PCplus, 125'h0};
    wire [124:0] r3_null;
    assign {r3l3_rs1, r3l3_rs2, r3l3_op, r3l3_fn3, r3l3_fn7, r3l3_rd, r3l3_rd_wr,
        r3l3_mem_wr, r3l3_csr_wr, r3l3_imm,  r3l3_PC, r3l3_PCplus, r3_null} = r3_out;
    rv_r_reg R3(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[2]),
      .r_in(r3_in),
      .r_out(r3_out)
    );
    

    L3 - выборка операндов из файл-регистров

    Адреса регистров-операндов подаются на адресные входы файл-регистров (основного и регистров специальных функций). Оставшиеся адрес регистра-приёмника, числовая константа, программный счетчик, сигналы разрешения записью поступают на входы регистра R4.

    Выходы Rs1, Rs2 и выбранного CSR также записываются в регистр R4.

    (рис 6.4) Выборка операндов из файл-регистров
    wire [31:0] l3r4_Rs1_reg;
    wire [31:0] l3r4_Rs2_reg;
    wire [31:0] l3r4_CSR;
    
    rv_reg_file reg_file(   // register's file
      .clk(clk),
      .rs1(r3l3_rs1),
      .rs2(r3l3_rs2),
      .rd(r6l6_rd),
      .Rs1_out(l3r4_Rs1_reg),
      .Rs2_out(l3r4_Rs2_reg),
      .Rd_input(Rd_reg),
      .we(r6l6_rd_wr),
      .en(en_level[6])
    );
    rv_csr csr(
      .csr_addr(r3l3_imm[11:0]),
      .csr_in(r6l6_Rez),
      .csr_out(l3r4_CSR),
      .csr_wr(r6l6_csr_wr),
      .en(en_level[3])
    );
    assign Data_out = l3r4_CSR; //CSR;
    wire [31:0] r4l4_Rs1_reg;
    wire [31:0] r4l4_Rs2_reg;
    wire [6:0] r4l4_op;
    wire [2:0] r4l4_fn3;
    wire [6:0] r4l4_fn7;
    wire [4:0] r4l4_rd;
    wire r4l4_rd_wr;
    wire r4l4_mem_wr;
    wire r4l4_csr_wr;
    wire [31:0] r4l4_imm;
    wire [31:0] r4l4_PC;
    wire [31:0] r4l4_PCplus;
    wire [31:0] r4l4_CSR;
    wire [255:0] r4_in;
    wire [255:0] r4_out;
    assign r4_in = {l3r4_Rs1_reg, l3r4_Rs2_reg, r3l3_op, r3l3_fn3, r3l3_fn7, r3l3_rd, r3l3_rd_wr,
        r3l3_mem_wr, r3l3_csr_wr, r3l3_imm,  r3l3_PC, r3l3_PCplus, l3r4_CSR, 39'h0};
    wire [38:0] r4_null;
    assign {r4l4_Rs1_reg, r4l4_Rs2_reg, r4l4_op, r4l4_fn3, r4l4_fn7, r4l4_rd, r4l4_rd_wr,
        r4l4_mem_wr, r4l4_csr_wr, r4l4_imm,  r4l4_PC, r4l4_PCplus, r4l4_CSR, r4_null} = r4_out;
    
    rv_r_reg R4(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[3]),
      .r_in(r4_in),
      .r_out(r4_out)
    );
    

    L4 - этап работы арифметико-логического устройства( АЛУ)

    В рассматриваемой архитектуре АЛУ применяется для действий над операндами из файл-регистров, для вычисления адресов переходов, действий над считанными значениями регистров специального назначения. Из R4 на мультиплексор поступают возможные операнды, выбор двух операндов производится в зависимости от управляющих кодов (opcode, funct3, funct7).

    Параллельно Rs1, Rs2 и опкоды подаются на блок разрешения переноса (по факту - компаратор с входами разрешения).

    Все остальные сигналы плюс копия Rs2 транзитом подаются на вход регистра R5.

    L5 - чтение/запись данных из оперативной памяти

    На этом шаге производятся операции с памятью (памятью данных). Из регистра R5 выбираются результаты АЛУ, значение регистра Rs2, сигнал управления записью. Остальные сигналы транзитом подаются на вход регистра R6.

    L6 - запись данных в файл-регистры.

    Потенциальные результаты для записи в Rd подаются на вход мультиплексора, определяющего какое из значений будет записано в регистр Rd. Данные для записи также подаются на входы блока регистра специального назначения (CSR). Транзитом следует только новое значение программного счетчика и сигнал разрешения его модификации.

    (рис 6.5) Подача операндов на АЛУ
    wire [31:0] Op1;
    wire [31:0] Op2;
    
    rv_ops_mux ops_mux(
      .opcode(r4l4_op),
      .funct3(r4l4_fn3),
      .funct7(r4l4_fn7),
      .Rs1(r4l4_Rs1_reg),
      .Rs2(r4l4_Rs2_reg),
      .imm(r4l4_imm),
      .PC(r4l4_PC),
      .CSR(r4l4_CSR),
      .Op1(Op1),
      .Op2(Op2)
    );
    rv_cmp cmp(
      .opcode(r4l4_op),
      .funct3(r4l4_fn3),
      .Rs1(r4l4_Rs1_reg),
      .Rs2(r4l4_Rs2_reg),
      .pc_new(l4r5_PC_load)
    );
    rv_alu_v alu_v(
      .opcode(r4l4_op),
      .funct3(r4l4_fn3),
      .funct7(r4l4_fn7),
      .Op1(Op1),
      .Op2(Op2),
      .Rez(l4r5_Rez)
    );
    wire [31:0] l4r5_Rez;
    wire l4r5_PC_load;
    wire [31:0] r5l5_Rez;
    wire [31:0] r5l5_Rs2_reg;
    wire [6:0] r5l5_op;
    wire [2:0] r5l5_fn3;
    wire [6:0] r5l5_fn7;
    wire [4:0] r5l5_rd;
    wire r5l5_rd_wr;
    wire r5l5_mem_wr; 
    wire r5l5_csr_wr;
    wire [31:0] r5l5_imm;
    wire [31:0] r5l5_PC;
    wire [31:0] r5l5_PCplus;
    wire r5l5_PC_load;
    //L5
    wire [255:0] r5_in;
    wire [255:0] r5_out;
    assign r5_in = {l4r5_Rez, r4l4_Rs2_reg, r4l4_op, r4l4_fn3, r4l4_fn7, r4l4_rd, r4l4_rd_wr,
        r4l4_mem_wr, r4l4_csr_wr, r4l4_imm,  r4l4_PC, r4l4_PCplus, l4r5_PC_load, 70'h0};
    wire [69:0] r5_null;
    assign {r5l5_Rez, r5l5_Rs2_reg, r5l5_op, r5l5_fn3, r5l5_fn7, r5l5_rd, r5l5_rd_wr,
        r5l5_mem_wr, r5l5_csr_wr, r5l5_imm,  r5l5_PC, r5l5_PCplus, r5l5_PC_load, r5_null} = r5_out;
    rv_r_reg R5(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[4]),
      .r_in(r5_in),
      .r_out(r5_out)
    );
    
    (рис 6.6) Чтение/запись данных из оперативной памяти
    wire [31:0] l5r6_Mem_data;
    wire [31:0] r6l6_Mem_data;
    wire [31:0] r6l6_Rez;
    wire [31:0] r6l6_Rs2_reg;
    wire [6:0] r6l6_op;
    wire [2:0] r6l6_fn3;
    wire [6:0] r6l6_fn7;
    wire [4:0] r6l6_rd;
    wire r6l6_rd_wr;
    wire r6l6_mem_wr;
    wire r6l6_csr_wr; 
    wire [31:0] r6l6_imm;
    wire [31:0] r6l6_PC;
    wire [31:0] r6l6_PCplus;
    wire r6l6_PC_load;
    wire [255:0] r6_in;
    wire [255:0] r6_out;
    assign r6_in = {l5r6_Mem_data, r5l5_Rez,  r5l5_op, r5l5_fn3, r5l5_fn7, r5l5_rd, r5l5_rd_wr,
        r5l5_mem_wr, r5l5_csr_wr, r5l5_imm,  r5l5_PC, r5l5_PCplus, r5l5_PC_load, 70'h0};
    wire [69:0] r6_null;
    assign {r6l6_Mem_data, r6l6_Rez, r6l6_op, r6l6_fn3, r6l6_fn7, r6l6_rd, r6l6_rd_wr,
        r6l6_mem_wr, r6l6_csr_wr, r6l6_imm,  r6l6_PC, r6l6_PCplus, r6l6_PC_load, r6_null} = r6_out;
    rv_r_reg R6(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[5]),
      .r_in(r6_in),
      .r_out(r6_out)
    );
    wire [31:0] Rd_reg;
    rv_rez_mux rez_mux(
      .opcode(r6l6_op),
      .funct3(r6l6_fn3),
      .funct7(r6l6_fn7),
      .Rez(r6l6_Rez),
      .Pc_plus(r6l6_PCplus),
      .Mem_data(r6l6_Mem_data),
      .Imm(r6l6_imm),
      .Rd(Rd_reg)
    );
    
    (рис 6.7) Запись в файл-регистры
    wire [31:0] Rd_reg;
    
    rv_rez_mux rez_mux(
      .opcode(r6l6_op),
      .funct3(r6l6_fn3),
      .funct7(r6l6_fn7),
      .Rez(r6l6_Rez),
      .Pc_plus(r6l6_PCplus),
      .Mem_data(r6l6_Mem_data),
      .Imm(r6l6_imm),
      .Rd(Rd_reg)
    );
    

    Общая структура процессора представлена на рис.6.8.

    Регистры R1-R6 являются достаточно "широкими" - их разрядность с запасом взята 256 бит (да, немного расточительно с точки зрения ресурсов кристалла/FPGA, но достаточно наглядно). Кольцевой счетчик, выдающий сигналы разрешения и "широкие" регистры хранения промежуточных результатов.

    module rv_ring_reg
    #(
    parameter WIDTH=7
    )
    (
      input clk,
      input rst_n,
      output  [WIDTH-1:0] L_en
    );
    reg [WIDTH-1:0] ring;
    always@(posedge clk or negedge rst_n)
      begin
        if(!rst_n)
          ring   <= 8'b00000001;
        else
          ring <= {ring[WIDTH-2:0],ring[WIDTH-1]};
        end
    assign L_en = ring; 
    endmodule
    
    module rv_r_reg
    #(
      parameter WIDTH=256
      )
    (
      input clk,
      input rst_n,
      input en,
      input  [WIDTH - 1:0] r_in,
      output reg [WIDTH - 1:0] r_out
    );
    
    always@(posedge clk or negedge rst_n)
      begin
        if(!rst_n)
          r_out <= 0;
        else
          if(en) begin r_out <= r_in; end
        end
    endmodule
    
    (рис 6.8) Структура многотактного процессора

    Общий код процессора:

    `include "rv_pc.v"
    `include "rv_mem.v"
    `include "rv_desh.v"
    `include "rv_imm.v"
    `include "rv_reg_file_tst.v"
    `include "rv_ops_mux.v"
    `include "rv_cmp.v"
    `include "rv_alu_v.v"
    `include "rv_rez_mux.v"
    `include "rv_csr.v"
    `include "rv_r_reg.v"
    `include "rv_ring_reg.v"
    
    module rv_cpu_top
    #(
      parameter DATA_WIDTH=32,
      parameter ADDR_WIDTH=32
      )
    ( input clk,
      input rst,
    //  input [(ADDR_WIDTH-1):0] Data_In,
      output[(DATA_WIDTH-1):0] Data_out
    );
    
    // CPU modules 
    
    wire [6:0] en_level;
    
    rv_ring_reg ring_reg(
      .clk(clk),
      .rst_n(rst),
      .L_en(en_level)
    );
    // L0_R1
    wire [31:0]l0r1_PC;
    wire [31:0]l0r1_PCplus;
    wire [31:0]r1l1_PC;
    wire [31:0]r1l1_PCplus;
    
    rv_pc pc( // programm counter
      .clk(clk),
      .rst_n(rst),
      .en(en_level[6]),
    //  .pc_load(r7l7_PC_load),
    //  .pc_next(r7l7_Rez),
      .pc_load(r6l6_PC_load),
      .pc_next(r6l6_Rez),
      .pc(l0r1_PC),
      .pc_plus(l0r1_PCplus)
    );
    wire [255:0] r1_in;
    assign r1_in = {l0r1_PC, l0r1_PCplus, 192'h0}; 
    wire [255:0] r1_out;
    wire [191:0] r1_null;
    assign {r1l1_PC, r1l1_PCplus, r1_null} = r1_out; 
    
    rv_r_reg R1(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[0]),
      .r_in(r1_in),
      .r_out(r1_out)
    );
    // R1_L1
    wire [31:0] l1r2_iw;
    wire [31:0] l1r2_PC;
    wire [31:0] l1r2_PCplus;
    
    rv_mem mem( // system memory
      .clk(clk),
      //.i_addr(32'h0), //(r1l1_PC),
      .i_addr(r1l1_PC>>2),
      //.i_addr(l0r1_PC>>2),
      .code_out(l1r2_iw),
      .d_addr(r5l5_Rez>>2),
      .d_out(l5r6_Mem_data),
      .d_in(r5l5_Rs2_reg),
      .we(r5l5_mem_wr),
      .en(en_level[5])
    );
    // L1_R2
    wire [31:0]r2l2_iw;
    wire [31:0]r2l2_PC;
    wire [31:0]r2l2_PCplus;
    
    wire [255:0] r2_in;
    wire [255:0] r2_out;
    assign r2_in = {l1r2_iw, r1l1_PC, r1l1_PCplus, 160'h0};
    //assign r2_in = {l1r2_iw, l0r1_PC, l0r1_PCplus, 160'h0};
    wire [159:0] r2_null;
    assign {r2l2_iw, r2l2_PC, r2l2_PCplus, r2_null} = r2_out;
    
    rv_r_reg R2(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[1]),
      .r_in(r2_in),
      .r_out(r2_out)
    );
    // L2_R3
    wire [4:0] l2r3_rs1;
    wire [4:0] l2r3_rs2;
    wire [6:0] l2r3_op;
    wire [2:0] l2r3_fn3;
    wire [6:0] l2r3_fn7;
    wire [4:0] l2r3_rd;
    wire l2r3_rd_wr;
    wire l2r3_mem_wr;
    wire l2r3_csr_wr;
    wire [31:0] l2r3_imm;
    wire [31:0] l2r3_PC;
    wire [31:0] l2r3_PCplus;
    
    rv_desh desh( // instruction decoder
      .inst(r2l2_iw),
      .opcode(l2r3_op),
      .rs1(l2r3_rs1),
      .rs1_en(),
      .rs2(l2r3_rs2),
      .rs2_en(),
      .rd(l2r3_rd),
      .rd_wr(l2r3_rd_wr),
      .funct3(l2r3_fn3),
      .f3_en(),
      .funct7(l2r3_fn7),
      .f7_en(),
      .mem_en(),
      .mem_wr(l2r3_mem_wr),
      .csr_en(),
      .csr_wr(l2r3_csr_wr),
      .pc_load()
    );
    rv_imm immed(  // immediate decoder
      .inst(r2l2_iw),
      .imm(l2r3_imm)
    );
    wire [4:0] r3l3_rs1;
    wire [4:0] r3l3_rs2;
    wire [6:0] r3l3_op;
    wire [2:0] r3l3_fn3;
    wire [6:0] r3l3_fn7;
    wire [4:0] r3l3_rd;
    wire r3l3_rd_wr;
    wire r3l3_mem_wr;
    wire r3l3_csr_wr;
    wire [31:0] r3l3_imm;
    wire [31:0] r3l3_PC;
    wire [31:0] r3l3_PCplus;
    
    wire [255:0] r3_in;
    wire [255:0] r3_out;
    assign r3_in = {l2r3_rs1, l2r3_rs2, l2r3_op, l2r3_fn3, l2r3_fn7, l2r3_rd, l2r3_rd_wr,
        l2r3_mem_wr, l2r3_csr_wr, l2r3_imm,  r2l2_PC, r2l2_PCplus, 125'h0};
    wire [124:0] r3_null;
    assign {r3l3_rs1, r3l3_rs2, r3l3_op, r3l3_fn3, r3l3_fn7, r3l3_rd, r3l3_rd_wr,
        r3l3_mem_wr, r3l3_csr_wr, r3l3_imm,  r3l3_PC, r3l3_PCplus, r3_null} = r3_out;
    
    rv_r_reg R3(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[2]),
      .r_in(r3_in),
      .r_out(r3_out)
    );
    //L3_R4
    wire [31:0] l3r4_Rs1_reg;
    wire [31:0] l3r4_Rs2_reg;
    wire [31:0] l3r4_CSR;
    
    rv_reg_file reg_file(   // register's file
      .clk(clk),
      .rs1(r3l3_rs1),
      .rs2(r3l3_rs2),
      .rd(r6l6_rd),
      .Rs1_out(l3r4_Rs1_reg),
      .Rs2_out(l3r4_Rs2_reg),
      .Rd_input(Rd_reg),
      .we(r6l6_rd_wr),
      .en(en_level[6])
    );
    rv_csr csr(
      .csr_addr(r3l3_imm[11:0]),
      .csr_in(r6l6_Rez),
      .csr_out(l3r4_CSR),
      .csr_wr(r6l6_csr_wr),
      .en(en_level[3])
    );
    assign Data_out = l3r4_CSR; //CSR;
    wire [31:0] r4l4_Rs1_reg;
    wire [31:0] r4l4_Rs2_reg;
    wire [6:0] r4l4_op;
    wire [2:0] r4l4_fn3;
    wire [6:0] r4l4_fn7;
    wire [4:0] r4l4_rd;
    wire r4l4_rd_wr;
    wire r4l4_mem_wr;
    wire r4l4_csr_wr;
    wire [31:0] r4l4_imm;
    wire [31:0] r4l4_PC;
    wire [31:0] r4l4_PCplus;
    wire [31:0] r4l4_CSR;
    
    wire [255:0] r4_in;
    wire [255:0] r4_out;
    assign r4_in = {l3r4_Rs1_reg, l3r4_Rs2_reg, r3l3_op, r3l3_fn3, r3l3_fn7, r3l3_rd, r3l3_rd_wr,
        r3l3_mem_wr, r3l3_csr_wr, r3l3_imm,  r3l3_PC, r3l3_PCplus, l3r4_CSR, 39'h0};
    wire [38:0] r4_null;
    assign {r4l4_Rs1_reg, r4l4_Rs2_reg, r4l4_op, r4l4_fn3, r4l4_fn7, r4l4_rd, r4l4_rd_wr,
        r4l4_mem_wr, r4l4_csr_wr, r4l4_imm,  r4l4_PC, r4l4_PCplus, r4l4_CSR, r4_null} = r4_out;
    
    rv_r_reg R4(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[3]),
      .r_in(r4_in),
      .r_out(r4_out)
    );
    // L4
    
    wire [31:0] Op1;
    wire [31:0] Op2;
    
    rv_ops_mux ops_mux(
      .opcode(r4l4_op),
      .funct3(r4l4_fn3),
      .funct7(r4l4_fn7),
      .Rs1(r4l4_Rs1_reg),
      .Rs2(r4l4_Rs2_reg),
      .imm(r4l4_imm),
      .PC(r4l4_PC),
      .CSR(r4l4_CSR),
      .Op1(Op1),
      .Op2(Op2)
    );
    
    rv_cmp cmp(
      .opcode(r4l4_op),
      .funct3(r4l4_fn3),
      .Rs1(r4l4_Rs1_reg),
      .Rs2(r4l4_Rs2_reg),
      .pc_new(l4r5_PC_load)
    );
    
    rv_alu_v alu_v(
      .opcode(r4l4_op),
      .funct3(r4l4_fn3),
      .funct7(r4l4_fn7),
      .Op1(Op1),
      .Op2(Op2),
      .Rez(l4r5_Rez)
    );
    wire [31:0] l4r5_Rez;
    wire l4r5_PC_load;
    wire [31:0] r5l5_Rez;
    wire [31:0] r5l5_Rs2_reg;
    wire [6:0] r5l5_op;
    wire [2:0] r5l5_fn3;
    wire [6:0] r5l5_fn7;
    wire [4:0] r5l5_rd;
    wire r5l5_rd_wr;
    wire r5l5_mem_wr; 
    wire r5l5_csr_wr;
    wire [31:0] r5l5_imm;
    wire [31:0] r5l5_PC;
    wire [31:0] r5l5_PCplus;
    wire r5l5_PC_load;
    //L5
    wire [255:0] r5_in;
    wire [255:0] r5_out;
    assign r5_in = {l4r5_Rez, r4l4_Rs2_reg, r4l4_op, r4l4_fn3, r4l4_fn7, r4l4_rd, r4l4_rd_wr,
        r4l4_mem_wr, r4l4_csr_wr, r4l4_imm,  r4l4_PC, r4l4_PCplus, l4r5_PC_load, 70'h0};
    wire [69:0] r5_null;
    assign {r5l5_Rez, r5l5_Rs2_reg, r5l5_op, r5l5_fn3, r5l5_fn7, r5l5_rd, r5l5_rd_wr,
        r5l5_mem_wr, r5l5_csr_wr, r5l5_imm,  r5l5_PC, r5l5_PCplus, r5l5_PC_load, r5_null} = r5_out;
    
    rv_r_reg R5(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[4]),
      .r_in(r5_in),
      .r_out(r5_out)
    );
    // L6
    wire [31:0] l5r6_Mem_data;
    wire [31:0] r6l6_Mem_data;
    wire [31:0] r6l6_Rez;
    wire [31:0] r6l6_Rs2_reg;
    wire [6:0] r6l6_op;
    wire [2:0] r6l6_fn3;
    wire [6:0] r6l6_fn7;
    wire [4:0] r6l6_rd;
    wire r6l6_rd_wr;
    wire r6l6_mem_wr;
    wire r6l6_csr_wr; 
    wire [31:0] r6l6_imm;
    wire [31:0] r6l6_PC;
    wire [31:0] r6l6_PCplus;
    wire r6l6_PC_load;
    
    wire [255:0] r6_in;
    wire [255:0] r6_out;
    assign r6_in = {l5r6_Mem_data, r5l5_Rez,  r5l5_op, r5l5_fn3, r5l5_fn7, r5l5_rd, r5l5_rd_wr,
        r5l5_mem_wr, r5l5_csr_wr, r5l5_imm,  r5l5_PC, r5l5_PCplus, r5l5_PC_load, 70'h0};
    wire [69:0] r6_null;
    assign {r6l6_Mem_data, r6l6_Rez, r6l6_op, r6l6_fn3, r6l6_fn7, r6l6_rd, r6l6_rd_wr,
        r6l6_mem_wr, r6l6_csr_wr, r6l6_imm,  r6l6_PC, r6l6_PCplus, r6l6_PC_load, r6_null} = r6_out;
    
    rv_r_reg R6(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[5]),
      .r_in(r6_in),
      .r_out(r6_out)
    );
    // L7
    wire [31:0] Rd_reg;
    
    rv_rez_mux rez_mux(
      .opcode(r6l6_op),
      .funct3(r6l6_fn3),
      .funct7(r6l6_fn7),
      .Rez(r6l6_Rez),
      .Pc_plus(r6l6_PCplus),
      .Mem_data(r6l6_Mem_data),
      .Imm(r6l6_imm),
      .Rd(Rd_reg)
    );
    Endmodule
    

    Небольшая, не делающая ничего "умного" тестовая программа:

    lui x3,0x5
    srli x3,x3,12
    lui x1,0x1
    lui x4,0x7
    l1:
    add x5, x1, x4
    addi x3, x3,5
    jal x2,l1
    lui x3,0x99
    lui x4,0x77
    beq x0,x0,l1
    srli x1,x1,12
    lui x2,0x0
    lui x4,0x7
    add x5, x1, x4
    

    На рис.6.9 - она же в симуляторе RARS.

    (рис 6.9)

    Рис.6.10 - временные диаграммы исполнения кода. На ней представлены значения программного счетчика, адресных входов памяти команд, вход Rd файл-регистра, входы и выходы АЛУ, расшифровку кодов инструкции - поля opcode и funct3, funct7, значение immediate (да, они не всегда имеют значение, но при обработке будут игнорированы), сигналы разрешения работы каждого из этапов.

    (рис 6.9) Тестовая программа в симуляторе RARS (рис 6.10) Временные диаграммы исполнения программы в многотактном ядре

    На диаграммах видно выполнение математических операций, операций перехода - код доходит до адреса 1C и в соответствии с программной переходит на нулевой адрес.

    Очевидные недостатки представленной микроархитектуры:

  • большое количество "сквозных" многоразрядных сигналов, проходящих через все этапы и требующие для промежуточного хранения соответствующего количества бит в промежуточных регистрах (Rx).
  • регистры Rx в представленном коде фиксированной разрядности, без учета реально требуемого количества бит на каждом из этапов.
  • PS

    "Отладочный вариант" файл-регистра с дополнительными выходами, позволяющими на временных диаграммах наблюдать значения подключенных к ним выходов регистров.

    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 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];
      wire rd_nonzero;
      wire rs1_nonzero;
      wire rs2_nonzero;
    
      assign rd_nonzero = |rd;
      assign rs1_nonzero = |rs1;
      assign rs2_nonzero = |rs2;
    // read RAM content from file
    //initial
    //$readmemh("ram.txt",ram);
    
    always @ (posedge clk)
      begin
        if (en  we  rd_nonzero) ram[rd] <= Rd_input;
      end
    
    always @ (*)
      begin
        Rs1_out <= rs1_nonzero ? ram[rs1] : 32'h0;
        Rs2_out <= rs2_nonzero ? ram[rs2] : 32'h0;
        x1_out <= ram[1];
        x2_out <= ram[2];
        x3_out <= ram[3];
        x4_out <= ram[4];
        x5_out <= ram[5];
      end
    // */
    endmodule
    
    Страницы:

    Попробуем немного улучшить предыдущий вариант процессорного ядра, проведя повторную ревизию модулей ядра, проанализировав систему команд и возможные преобразования сигналов и данных, которые необходимо будет сделать.

    Подход к построению многотактного процессорного ядра выбран не совсем классический, введем изначально некоторое количество т.н. "неархитектурных регистров" которые будут хранить промежуточные состояния.

    И так, еще раз наши микроархитектурные блоки однотактного варианта:

  • rv_pc - программный счетчик;
  • rv_mem - блок памяти (программная и оперативная;
  • rv_desh - дешифратор команд (слова-инструкции);
  • rv_imm - формирователь непосредственного значения из слова-интсрукции;
  • rv_reg_file - файл-регистр
  • rv_ops_mux - коммутатор операндов для АЛУ;
  • rv_cmp - формирователь сигнала разрешения перехода;
  • rv_alu_v - АЛУ;
  • rv_rez_mux - коммутатор результатов;
  • rv_csr - блок регистров специального назначения.
  • Пробуем рассмотреть возможные сигналы и действия на каждом из этапов. Промежуточные результаты каждого из этапов фиксируются в неархитектурных регистрах. Да, таким образом количество задействованных ресурсов возрастает, но в качестве упражнения такой подход может иметь место.

    Предлагается разбиение функциональности на несколько уровней:

  • L0 - выборка текущего значения программного счетчика (точнее пары значений - PC и РС+4).
  • L1 - выборка слова-инструкции из памяти.
  • L2 - дешифрация инструкции - выделение адресов операндов, типа инструкции, формирование управляющих сигналов.
  • L3 - выборка операндов из файл-регистров (в рассматриваемой микроархитектуре - это файл-регистр и блок регистров специальных функций).
  • L4 - работа АЛУ.
  • L5 - чтение/запись данных из оперативной памяти.
  • L6 - запись данных в файл-регистры и нового значения в программный счётчик.
  • Вся структура управляется кольцевым счетчиком поочередно разрешающим работу модулей каждого из уровней. Для этого модули процессорного ядра должны быть дополнены сигналами разрешения работы (особенно модули с элементами памяти). Такой модификации подвергнутся - программный счетчик, добавим разрешение на запись в файл-регистр (запись результата в порт Rd/регистра по адресу rd), разрешение на запись в блок регистров специальных функций.

    "Новый" программный счетчик:

    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
    );
    reg [WIDTH-1:0] pc_reg;
    always@(posedge clk or negedge rst_n)
      begin
        if(!rst_n)
          pc_reg   <= 32'h00;
        else
        if (en) begin
          if(pc_load==1'b1)
            pc_reg <= pc_next;
          else
            pc_reg <= pc_reg + 3'h4;
          end
        end
      //end///*
    assign pc = pc_reg;
    always @ *
      begin
      pc_plus <= pc_reg + 3'h4;
      end
    // */    
    Endmodule
    

    Модификация файл-регистра:

    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 we,
      input en
    );
      reg [DATA_WIDTH-1:0] ram[0:2**ADDR_WIDTH-1];
      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] <= Rd_input;
      end
    
    always @ (*)
      begin
        Rs1_out <= rs1_nonzero ? ram[rs1] : 32'h0;
        Rs2_out <= rs2_nonzero ? ram[rs2] : 32'h0;
      end
    endmodule
    

    И блок регистров специального назначения (пока это просто код-заглушка):

    module rv_csr
    #(
      parameter DATA_WIDTH=32, 
      parameter ADDR_WIDTH=12,
      parameter CSR_size = 32
      )
    ( input clk,
      input [(ADDR_WIDTH-1):0] csr_addr,
      input [(DATA_WIDTH-1):0] csr_in,
      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)
        32'h0 : begin
          if (csr_wren) begin
            csr_reg[0] <= csr_in;
          end 
          else begin
            csr_out <= csr_reg[0];
          end
        end
        default: begin
          csr_out<=32'h0;
        end
      endcase
    end
    
    endmodule
    

    Уровни "разделяются" промежуточными регистрами (обычные регистры хранения), которые фиксируют данные (регистры R0 - R7). Ряд сигналов просто "пробрасываются" по этапам (напрямую между регистрами) в том случае, если они не задействованы в них, но требуются на последующих.

    L0 - выборка текущего значения программного счетчика

    Текущее значение программного счетчика и его инкремента (PC+4) запоминаются в регистре R1. Выходными линиями, соответствующими PC, адресуется память (память программ). Также пара PC, PC+4 с выхода R1 будет подана на вход регистра R2. Программному счетчику добавлен вход разрешения работы (en).

    L1 - выборка слова-инструкции из памяти

    В регистре R2 фиксируется слово инструкции из памяти и текущие значения пары PC, PC+4. Выход регистра подается на декодер инструкции и на схемы формирования непосредственного значения (immediate). Значения счетчиков передаются на вход следующего регистра (R3).

    (рис 6.1) Выборка текущего значения программного счетчика
    wire [31:0]l0r1_PC;
    wire [31:0]l0r1_PCplus;
    wire [31:0]r1l1_PC;
    wire [31:0]r1l1_PCplus;
    
    rv_pc pc( // programm counter
      .clk(clk),
      .rst_n(rst),
      .en(en_level[6]),
      .pc_load(r6l6_PC_load),
      .pc_next(r6l6_Rez),
      .pc(l0r1_PC),
      .pc_plus(l0r1_PCplus)
    );
    wire [255:0] r1_in;
    assign r1_in = {l0r1_PC, l0r1_PCplus, 192'h0}; 
    wire [255:0] r1_out;
    wire [191:0] r1_null;
    assign {r1l1_PC, r1l1_PCplus, r1_null} = r1_out; 
    
    rv_r_reg R1(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[0]),
      .r_in(r1_in),
      .r_out(r1_out)
    );
    

    Адрес инструкции на память подается со сдвигом на два бита - таким образом учитывается, что реализованная в примере микроархитектура использует 32-битную память.

    (рис 6.2) Выборка слова-инструкции из памяти
    wire [31:0] l1r2_iw;
    wire [31:0] l1r2_PC;
    wire [31:0] l1r2_PCplus;
    
    rv_mem mem( // system memory
      .clk(clk),
      //.i_addr(32'h0), //(r1l1_PC),
      .i_addr(r1l1_PC>>2),
      .code_out(l1r2_iw),
      .d_addr(r5l5_Rez>>2),
      .d_out(l5r6_Mem_data),
      .d_in(r5l5_Rs2_reg),
      .we(r5l5_mem_wr),
      .en(en_level[5])
    );
    // L1_R2
    wire [31:0]r2l2_iw;
    wire [31:0]r2l2_PC;
    wire [31:0]r2l2_PCplus;
    wire [255:0] r2_in;
    wire [255:0] r2_out;
    assign r2_in = {l1r2_iw, r1l1_PC, r1l1_PCplus, 160'h0};
    wire [159:0] r2_null;
    assign {r2l2_iw, r2l2_PC, r2l2_PCplus, r2_null} = r2_out;
    rv_r_reg R2(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[1]),
      .r_in(r2_in),
      .r_out(r2_out)
    );
    

    L2 - дешифрация инструкции

    Из слова-инструкции идет выделение адресов регистров-операндов, типа (опкодов и функциональных полей) инструкции, формирование управляющих сигналов на запись в файл-регистры (регистров общего назначения и специальных регистров).

    (рис 6.3) Дешифровка инструкции
    wire [4:0] l2r3_rs1;
    wire [4:0] l2r3_rs2;
    wire [6:0] l2r3_op;
    wire [2:0] l2r3_fn3;
    wire [6:0] l2r3_fn7;
    wire [4:0] l2r3_rd;
    wire l2r3_rd_wr;
    wire l2r3_mem_wr;
    wire l2r3_csr_wr;
    wire [31:0] l2r3_imm;
    wire [31:0] l2r3_PC;
    wire [31:0] l2r3_PCplus;
    rv_desh desh( // instruction decoder
      .inst(r2l2_iw),
      .opcode(l2r3_op),
      .rs1(l2r3_rs1),
      .rs1_en(),
      .rs2(l2r3_rs2),
      .rs2_en(),
      .rd(l2r3_rd),
      .rd_wr(l2r3_rd_wr),
      .funct3(l2r3_fn3),
      .f3_en(),
      .funct7(l2r3_fn7),
      .f7_en(),
      .mem_en(),
      .mem_wr(l2r3_mem_wr),
      .csr_en(),
      .csr_wr(l2r3_csr_wr),
      .pc_load()
    );
    rv_imm immed(  // immediate decoder
      .inst(r2l2_iw),
      .imm(l2r3_imm)
    );
    wire [4:0] r3l3_rs1;
    wire [4:0] r3l3_rs2;
    wire [6:0] r3l3_op;
    wire [2:0] r3l3_fn3;
    wire [6:0] r3l3_fn7;
    wire [4:0] r3l3_rd;
    wire r3l3_rd_wr;
    wire r3l3_mem_wr;
    wire r3l3_csr_wr;
    wire [31:0] r3l3_imm;
    wire [31:0] r3l3_PC;
    wire [31:0] r3l3_PCplus;
    wire [255:0] r3_in;
    wire [255:0] r3_out;
    assign r3_in = {l2r3_rs1, l2r3_rs2, l2r3_op, l2r3_fn3, l2r3_fn7, l2r3_rd, l2r3_rd_wr,
        l2r3_mem_wr, l2r3_csr_wr, l2r3_imm,  r2l2_PC, r2l2_PCplus, 125'h0};
    wire [124:0] r3_null;
    assign {r3l3_rs1, r3l3_rs2, r3l3_op, r3l3_fn3, r3l3_fn7, r3l3_rd, r3l3_rd_wr,
        r3l3_mem_wr, r3l3_csr_wr, r3l3_imm,  r3l3_PC, r3l3_PCplus, r3_null} = r3_out;
    rv_r_reg R3(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[2]),
      .r_in(r3_in),
      .r_out(r3_out)
    );
    

    L3 - выборка операндов из файл-регистров

    Адреса регистров-операндов подаются на адресные входы файл-регистров (основного и регистров специальных функций). Оставшиеся адрес регистра-приёмника, числовая константа, программный счетчик, сигналы разрешения записью поступают на входы регистра R4.

    Выходы Rs1, Rs2 и выбранного CSR также записываются в регистр R4.

    (рис 6.4) Выборка операндов из файл-регистров
    wire [31:0] l3r4_Rs1_reg;
    wire [31:0] l3r4_Rs2_reg;
    wire [31:0] l3r4_CSR;
    
    rv_reg_file reg_file(   // register's file
      .clk(clk),
      .rs1(r3l3_rs1),
      .rs2(r3l3_rs2),
      .rd(r6l6_rd),
      .Rs1_out(l3r4_Rs1_reg),
      .Rs2_out(l3r4_Rs2_reg),
      .Rd_input(Rd_reg),
      .we(r6l6_rd_wr),
      .en(en_level[6])
    );
    rv_csr csr(
      .csr_addr(r3l3_imm[11:0]),
      .csr_in(r6l6_Rez),
      .csr_out(l3r4_CSR),
      .csr_wr(r6l6_csr_wr),
      .en(en_level[3])
    );
    assign Data_out = l3r4_CSR; //CSR;
    wire [31:0] r4l4_Rs1_reg;
    wire [31:0] r4l4_Rs2_reg;
    wire [6:0] r4l4_op;
    wire [2:0] r4l4_fn3;
    wire [6:0] r4l4_fn7;
    wire [4:0] r4l4_rd;
    wire r4l4_rd_wr;
    wire r4l4_mem_wr;
    wire r4l4_csr_wr;
    wire [31:0] r4l4_imm;
    wire [31:0] r4l4_PC;
    wire [31:0] r4l4_PCplus;
    wire [31:0] r4l4_CSR;
    wire [255:0] r4_in;
    wire [255:0] r4_out;
    assign r4_in = {l3r4_Rs1_reg, l3r4_Rs2_reg, r3l3_op, r3l3_fn3, r3l3_fn7, r3l3_rd, r3l3_rd_wr,
        r3l3_mem_wr, r3l3_csr_wr, r3l3_imm,  r3l3_PC, r3l3_PCplus, l3r4_CSR, 39'h0};
    wire [38:0] r4_null;
    assign {r4l4_Rs1_reg, r4l4_Rs2_reg, r4l4_op, r4l4_fn3, r4l4_fn7, r4l4_rd, r4l4_rd_wr,
        r4l4_mem_wr, r4l4_csr_wr, r4l4_imm,  r4l4_PC, r4l4_PCplus, r4l4_CSR, r4_null} = r4_out;
    
    rv_r_reg R4(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[3]),
      .r_in(r4_in),
      .r_out(r4_out)
    );
    

    L4 - этап работы арифметико-логического устройства( АЛУ)

    В рассматриваемой архитектуре АЛУ применяется для действий над операндами из файл-регистров, для вычисления адресов переходов, действий над считанными значениями регистров специального назначения. Из R4 на мультиплексор поступают возможные операнды, выбор двух операндов производится в зависимости от управляющих кодов (opcode, funct3, funct7).

    Параллельно Rs1, Rs2 и опкоды подаются на блок разрешения переноса (по факту - компаратор с входами разрешения).

    Все остальные сигналы плюс копия Rs2 транзитом подаются на вход регистра R5.

    L5 - чтение/запись данных из оперативной памяти

    На этом шаге производятся операции с памятью (памятью данных). Из регистра R5 выбираются результаты АЛУ, значение регистра Rs2, сигнал управления записью. Остальные сигналы транзитом подаются на вход регистра R6.

    L6 - запись данных в файл-регистры.

    Потенциальные результаты для записи в Rd подаются на вход мультиплексора, определяющего какое из значений будет записано в регистр Rd. Данные для записи также подаются на входы блока регистра специального назначения (CSR). Транзитом следует только новое значение программного счетчика и сигнал разрешения его модификации.

    (рис 6.5) Подача операндов на АЛУ
    wire [31:0] Op1;
    wire [31:0] Op2;
    
    rv_ops_mux ops_mux(
      .opcode(r4l4_op),
      .funct3(r4l4_fn3),
      .funct7(r4l4_fn7),
      .Rs1(r4l4_Rs1_reg),
      .Rs2(r4l4_Rs2_reg),
      .imm(r4l4_imm),
      .PC(r4l4_PC),
      .CSR(r4l4_CSR),
      .Op1(Op1),
      .Op2(Op2)
    );
    rv_cmp cmp(
      .opcode(r4l4_op),
      .funct3(r4l4_fn3),
      .Rs1(r4l4_Rs1_reg),
      .Rs2(r4l4_Rs2_reg),
      .pc_new(l4r5_PC_load)
    );
    rv_alu_v alu_v(
      .opcode(r4l4_op),
      .funct3(r4l4_fn3),
      .funct7(r4l4_fn7),
      .Op1(Op1),
      .Op2(Op2),
      .Rez(l4r5_Rez)
    );
    wire [31:0] l4r5_Rez;
    wire l4r5_PC_load;
    wire [31:0] r5l5_Rez;
    wire [31:0] r5l5_Rs2_reg;
    wire [6:0] r5l5_op;
    wire [2:0] r5l5_fn3;
    wire [6:0] r5l5_fn7;
    wire [4:0] r5l5_rd;
    wire r5l5_rd_wr;
    wire r5l5_mem_wr; 
    wire r5l5_csr_wr;
    wire [31:0] r5l5_imm;
    wire [31:0] r5l5_PC;
    wire [31:0] r5l5_PCplus;
    wire r5l5_PC_load;
    //L5
    wire [255:0] r5_in;
    wire [255:0] r5_out;
    assign r5_in = {l4r5_Rez, r4l4_Rs2_reg, r4l4_op, r4l4_fn3, r4l4_fn7, r4l4_rd, r4l4_rd_wr,
        r4l4_mem_wr, r4l4_csr_wr, r4l4_imm,  r4l4_PC, r4l4_PCplus, l4r5_PC_load, 70'h0};
    wire [69:0] r5_null;
    assign {r5l5_Rez, r5l5_Rs2_reg, r5l5_op, r5l5_fn3, r5l5_fn7, r5l5_rd, r5l5_rd_wr,
        r5l5_mem_wr, r5l5_csr_wr, r5l5_imm,  r5l5_PC, r5l5_PCplus, r5l5_PC_load, r5_null} = r5_out;
    rv_r_reg R5(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[4]),
      .r_in(r5_in),
      .r_out(r5_out)
    );
    
    (рис 6.6) Чтение/запись данных из оперативной памяти
    wire [31:0] l5r6_Mem_data;
    wire [31:0] r6l6_Mem_data;
    wire [31:0] r6l6_Rez;
    wire [31:0] r6l6_Rs2_reg;
    wire [6:0] r6l6_op;
    wire [2:0] r6l6_fn3;
    wire [6:0] r6l6_fn7;
    wire [4:0] r6l6_rd;
    wire r6l6_rd_wr;
    wire r6l6_mem_wr;
    wire r6l6_csr_wr; 
    wire [31:0] r6l6_imm;
    wire [31:0] r6l6_PC;
    wire [31:0] r6l6_PCplus;
    wire r6l6_PC_load;
    wire [255:0] r6_in;
    wire [255:0] r6_out;
    assign r6_in = {l5r6_Mem_data, r5l5_Rez,  r5l5_op, r5l5_fn3, r5l5_fn7, r5l5_rd, r5l5_rd_wr,
        r5l5_mem_wr, r5l5_csr_wr, r5l5_imm,  r5l5_PC, r5l5_PCplus, r5l5_PC_load, 70'h0};
    wire [69:0] r6_null;
    assign {r6l6_Mem_data, r6l6_Rez, r6l6_op, r6l6_fn3, r6l6_fn7, r6l6_rd, r6l6_rd_wr,
        r6l6_mem_wr, r6l6_csr_wr, r6l6_imm,  r6l6_PC, r6l6_PCplus, r6l6_PC_load, r6_null} = r6_out;
    rv_r_reg R6(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[5]),
      .r_in(r6_in),
      .r_out(r6_out)
    );
    wire [31:0] Rd_reg;
    rv_rez_mux rez_mux(
      .opcode(r6l6_op),
      .funct3(r6l6_fn3),
      .funct7(r6l6_fn7),
      .Rez(r6l6_Rez),
      .Pc_plus(r6l6_PCplus),
      .Mem_data(r6l6_Mem_data),
      .Imm(r6l6_imm),
      .Rd(Rd_reg)
    );
    
    (рис 6.7) Запись в файл-регистры
    wire [31:0] Rd_reg;
    
    rv_rez_mux rez_mux(
      .opcode(r6l6_op),
      .funct3(r6l6_fn3),
      .funct7(r6l6_fn7),
      .Rez(r6l6_Rez),
      .Pc_plus(r6l6_PCplus),
      .Mem_data(r6l6_Mem_data),
      .Imm(r6l6_imm),
      .Rd(Rd_reg)
    );
    

    Общая структура процессора представлена на рис.6.8.

    Регистры R1-R6 являются достаточно "широкими" - их разрядность с запасом взята 256 бит (да, немного расточительно с точки зрения ресурсов кристалла/FPGA, но достаточно наглядно). Кольцевой счетчик, выдающий сигналы разрешения и "широкие" регистры хранения промежуточных результатов.

    module rv_ring_reg
    #(
    parameter WIDTH=7
    )
    (
      input clk,
      input rst_n,
      output  [WIDTH-1:0] L_en
    );
    reg [WIDTH-1:0] ring;
    always@(posedge clk or negedge rst_n)
      begin
        if(!rst_n)
          ring   <= 8'b00000001;
        else
          ring <= {ring[WIDTH-2:0],ring[WIDTH-1]};
        end
    assign L_en = ring; 
    endmodule
    
    module rv_r_reg
    #(
      parameter WIDTH=256
      )
    (
      input clk,
      input rst_n,
      input en,
      input  [WIDTH - 1:0] r_in,
      output reg [WIDTH - 1:0] r_out
    );
    
    always@(posedge clk or negedge rst_n)
      begin
        if(!rst_n)
          r_out <= 0;
        else
          if(en) begin r_out <= r_in; end
        end
    endmodule
    
    (рис 6.8) Структура многотактного процессора

    Общий код процессора:

    `include "rv_pc.v"
    `include "rv_mem.v"
    `include "rv_desh.v"
    `include "rv_imm.v"
    `include "rv_reg_file_tst.v"
    `include "rv_ops_mux.v"
    `include "rv_cmp.v"
    `include "rv_alu_v.v"
    `include "rv_rez_mux.v"
    `include "rv_csr.v"
    `include "rv_r_reg.v"
    `include "rv_ring_reg.v"
    
    module rv_cpu_top
    #(
      parameter DATA_WIDTH=32,
      parameter ADDR_WIDTH=32
      )
    ( input clk,
      input rst,
    //  input [(ADDR_WIDTH-1):0] Data_In,
      output[(DATA_WIDTH-1):0] Data_out
    );
    
    // CPU modules 
    
    wire [6:0] en_level;
    
    rv_ring_reg ring_reg(
      .clk(clk),
      .rst_n(rst),
      .L_en(en_level)
    );
    // L0_R1
    wire [31:0]l0r1_PC;
    wire [31:0]l0r1_PCplus;
    wire [31:0]r1l1_PC;
    wire [31:0]r1l1_PCplus;
    
    rv_pc pc( // programm counter
      .clk(clk),
      .rst_n(rst),
      .en(en_level[6]),
    //  .pc_load(r7l7_PC_load),
    //  .pc_next(r7l7_Rez),
      .pc_load(r6l6_PC_load),
      .pc_next(r6l6_Rez),
      .pc(l0r1_PC),
      .pc_plus(l0r1_PCplus)
    );
    wire [255:0] r1_in;
    assign r1_in = {l0r1_PC, l0r1_PCplus, 192'h0}; 
    wire [255:0] r1_out;
    wire [191:0] r1_null;
    assign {r1l1_PC, r1l1_PCplus, r1_null} = r1_out; 
    
    rv_r_reg R1(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[0]),
      .r_in(r1_in),
      .r_out(r1_out)
    );
    // R1_L1
    wire [31:0] l1r2_iw;
    wire [31:0] l1r2_PC;
    wire [31:0] l1r2_PCplus;
    
    rv_mem mem( // system memory
      .clk(clk),
      //.i_addr(32'h0), //(r1l1_PC),
      .i_addr(r1l1_PC>>2),
      //.i_addr(l0r1_PC>>2),
      .code_out(l1r2_iw),
      .d_addr(r5l5_Rez>>2),
      .d_out(l5r6_Mem_data),
      .d_in(r5l5_Rs2_reg),
      .we(r5l5_mem_wr),
      .en(en_level[5])
    );
    // L1_R2
    wire [31:0]r2l2_iw;
    wire [31:0]r2l2_PC;
    wire [31:0]r2l2_PCplus;
    
    wire [255:0] r2_in;
    wire [255:0] r2_out;
    assign r2_in = {l1r2_iw, r1l1_PC, r1l1_PCplus, 160'h0};
    //assign r2_in = {l1r2_iw, l0r1_PC, l0r1_PCplus, 160'h0};
    wire [159:0] r2_null;
    assign {r2l2_iw, r2l2_PC, r2l2_PCplus, r2_null} = r2_out;
    
    rv_r_reg R2(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[1]),
      .r_in(r2_in),
      .r_out(r2_out)
    );
    // L2_R3
    wire [4:0] l2r3_rs1;
    wire [4:0] l2r3_rs2;
    wire [6:0] l2r3_op;
    wire [2:0] l2r3_fn3;
    wire [6:0] l2r3_fn7;
    wire [4:0] l2r3_rd;
    wire l2r3_rd_wr;
    wire l2r3_mem_wr;
    wire l2r3_csr_wr;
    wire [31:0] l2r3_imm;
    wire [31:0] l2r3_PC;
    wire [31:0] l2r3_PCplus;
    
    rv_desh desh( // instruction decoder
      .inst(r2l2_iw),
      .opcode(l2r3_op),
      .rs1(l2r3_rs1),
      .rs1_en(),
      .rs2(l2r3_rs2),
      .rs2_en(),
      .rd(l2r3_rd),
      .rd_wr(l2r3_rd_wr),
      .funct3(l2r3_fn3),
      .f3_en(),
      .funct7(l2r3_fn7),
      .f7_en(),
      .mem_en(),
      .mem_wr(l2r3_mem_wr),
      .csr_en(),
      .csr_wr(l2r3_csr_wr),
      .pc_load()
    );
    rv_imm immed(  // immediate decoder
      .inst(r2l2_iw),
      .imm(l2r3_imm)
    );
    wire [4:0] r3l3_rs1;
    wire [4:0] r3l3_rs2;
    wire [6:0] r3l3_op;
    wire [2:0] r3l3_fn3;
    wire [6:0] r3l3_fn7;
    wire [4:0] r3l3_rd;
    wire r3l3_rd_wr;
    wire r3l3_mem_wr;
    wire r3l3_csr_wr;
    wire [31:0] r3l3_imm;
    wire [31:0] r3l3_PC;
    wire [31:0] r3l3_PCplus;
    
    wire [255:0] r3_in;
    wire [255:0] r3_out;
    assign r3_in = {l2r3_rs1, l2r3_rs2, l2r3_op, l2r3_fn3, l2r3_fn7, l2r3_rd, l2r3_rd_wr,
        l2r3_mem_wr, l2r3_csr_wr, l2r3_imm,  r2l2_PC, r2l2_PCplus, 125'h0};
    wire [124:0] r3_null;
    assign {r3l3_rs1, r3l3_rs2, r3l3_op, r3l3_fn3, r3l3_fn7, r3l3_rd, r3l3_rd_wr,
        r3l3_mem_wr, r3l3_csr_wr, r3l3_imm,  r3l3_PC, r3l3_PCplus, r3_null} = r3_out;
    
    rv_r_reg R3(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[2]),
      .r_in(r3_in),
      .r_out(r3_out)
    );
    //L3_R4
    wire [31:0] l3r4_Rs1_reg;
    wire [31:0] l3r4_Rs2_reg;
    wire [31:0] l3r4_CSR;
    
    rv_reg_file reg_file(   // register's file
      .clk(clk),
      .rs1(r3l3_rs1),
      .rs2(r3l3_rs2),
      .rd(r6l6_rd),
      .Rs1_out(l3r4_Rs1_reg),
      .Rs2_out(l3r4_Rs2_reg),
      .Rd_input(Rd_reg),
      .we(r6l6_rd_wr),
      .en(en_level[6])
    );
    rv_csr csr(
      .csr_addr(r3l3_imm[11:0]),
      .csr_in(r6l6_Rez),
      .csr_out(l3r4_CSR),
      .csr_wr(r6l6_csr_wr),
      .en(en_level[3])
    );
    assign Data_out = l3r4_CSR; //CSR;
    wire [31:0] r4l4_Rs1_reg;
    wire [31:0] r4l4_Rs2_reg;
    wire [6:0] r4l4_op;
    wire [2:0] r4l4_fn3;
    wire [6:0] r4l4_fn7;
    wire [4:0] r4l4_rd;
    wire r4l4_rd_wr;
    wire r4l4_mem_wr;
    wire r4l4_csr_wr;
    wire [31:0] r4l4_imm;
    wire [31:0] r4l4_PC;
    wire [31:0] r4l4_PCplus;
    wire [31:0] r4l4_CSR;
    
    wire [255:0] r4_in;
    wire [255:0] r4_out;
    assign r4_in = {l3r4_Rs1_reg, l3r4_Rs2_reg, r3l3_op, r3l3_fn3, r3l3_fn7, r3l3_rd, r3l3_rd_wr,
        r3l3_mem_wr, r3l3_csr_wr, r3l3_imm,  r3l3_PC, r3l3_PCplus, l3r4_CSR, 39'h0};
    wire [38:0] r4_null;
    assign {r4l4_Rs1_reg, r4l4_Rs2_reg, r4l4_op, r4l4_fn3, r4l4_fn7, r4l4_rd, r4l4_rd_wr,
        r4l4_mem_wr, r4l4_csr_wr, r4l4_imm,  r4l4_PC, r4l4_PCplus, r4l4_CSR, r4_null} = r4_out;
    
    rv_r_reg R4(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[3]),
      .r_in(r4_in),
      .r_out(r4_out)
    );
    // L4
    
    wire [31:0] Op1;
    wire [31:0] Op2;
    
    rv_ops_mux ops_mux(
      .opcode(r4l4_op),
      .funct3(r4l4_fn3),
      .funct7(r4l4_fn7),
      .Rs1(r4l4_Rs1_reg),
      .Rs2(r4l4_Rs2_reg),
      .imm(r4l4_imm),
      .PC(r4l4_PC),
      .CSR(r4l4_CSR),
      .Op1(Op1),
      .Op2(Op2)
    );
    
    rv_cmp cmp(
      .opcode(r4l4_op),
      .funct3(r4l4_fn3),
      .Rs1(r4l4_Rs1_reg),
      .Rs2(r4l4_Rs2_reg),
      .pc_new(l4r5_PC_load)
    );
    
    rv_alu_v alu_v(
      .opcode(r4l4_op),
      .funct3(r4l4_fn3),
      .funct7(r4l4_fn7),
      .Op1(Op1),
      .Op2(Op2),
      .Rez(l4r5_Rez)
    );
    wire [31:0] l4r5_Rez;
    wire l4r5_PC_load;
    wire [31:0] r5l5_Rez;
    wire [31:0] r5l5_Rs2_reg;
    wire [6:0] r5l5_op;
    wire [2:0] r5l5_fn3;
    wire [6:0] r5l5_fn7;
    wire [4:0] r5l5_rd;
    wire r5l5_rd_wr;
    wire r5l5_mem_wr; 
    wire r5l5_csr_wr;
    wire [31:0] r5l5_imm;
    wire [31:0] r5l5_PC;
    wire [31:0] r5l5_PCplus;
    wire r5l5_PC_load;
    //L5
    wire [255:0] r5_in;
    wire [255:0] r5_out;
    assign r5_in = {l4r5_Rez, r4l4_Rs2_reg, r4l4_op, r4l4_fn3, r4l4_fn7, r4l4_rd, r4l4_rd_wr,
        r4l4_mem_wr, r4l4_csr_wr, r4l4_imm,  r4l4_PC, r4l4_PCplus, l4r5_PC_load, 70'h0};
    wire [69:0] r5_null;
    assign {r5l5_Rez, r5l5_Rs2_reg, r5l5_op, r5l5_fn3, r5l5_fn7, r5l5_rd, r5l5_rd_wr,
        r5l5_mem_wr, r5l5_csr_wr, r5l5_imm,  r5l5_PC, r5l5_PCplus, r5l5_PC_load, r5_null} = r5_out;
    
    rv_r_reg R5(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[4]),
      .r_in(r5_in),
      .r_out(r5_out)
    );
    // L6
    wire [31:0] l5r6_Mem_data;
    wire [31:0] r6l6_Mem_data;
    wire [31:0] r6l6_Rez;
    wire [31:0] r6l6_Rs2_reg;
    wire [6:0] r6l6_op;
    wire [2:0] r6l6_fn3;
    wire [6:0] r6l6_fn7;
    wire [4:0] r6l6_rd;
    wire r6l6_rd_wr;
    wire r6l6_mem_wr;
    wire r6l6_csr_wr; 
    wire [31:0] r6l6_imm;
    wire [31:0] r6l6_PC;
    wire [31:0] r6l6_PCplus;
    wire r6l6_PC_load;
    
    wire [255:0] r6_in;
    wire [255:0] r6_out;
    assign r6_in = {l5r6_Mem_data, r5l5_Rez,  r5l5_op, r5l5_fn3, r5l5_fn7, r5l5_rd, r5l5_rd_wr,
        r5l5_mem_wr, r5l5_csr_wr, r5l5_imm,  r5l5_PC, r5l5_PCplus, r5l5_PC_load, 70'h0};
    wire [69:0] r6_null;
    assign {r6l6_Mem_data, r6l6_Rez, r6l6_op, r6l6_fn3, r6l6_fn7, r6l6_rd, r6l6_rd_wr,
        r6l6_mem_wr, r6l6_csr_wr, r6l6_imm,  r6l6_PC, r6l6_PCplus, r6l6_PC_load, r6_null} = r6_out;
    
    rv_r_reg R6(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[5]),
      .r_in(r6_in),
      .r_out(r6_out)
    );
    // L7
    wire [31:0] Rd_reg;
    
    rv_rez_mux rez_mux(
      .opcode(r6l6_op),
      .funct3(r6l6_fn3),
      .funct7(r6l6_fn7),
      .Rez(r6l6_Rez),
      .Pc_plus(r6l6_PCplus),
      .Mem_data(r6l6_Mem_data),
      .Imm(r6l6_imm),
      .Rd(Rd_reg)
    );
    Endmodule
    

    Небольшая, не делающая ничего "умного" тестовая программа:

    lui x3,0x5
    srli x3,x3,12
    lui x1,0x1
    lui x4,0x7
    l1:
    add x5, x1, x4
    addi x3, x3,5
    jal x2,l1
    lui x3,0x99
    lui x4,0x77
    beq x0,x0,l1
    srli x1,x1,12
    lui x2,0x0
    lui x4,0x7
    add x5, x1, x4
    

    На рис.6.9 - она же в симуляторе RARS.

    (рис 6.9)

    Рис.6.10 - временные диаграммы исполнения кода. На ней представлены значения программного счетчика, адресных входов памяти команд, вход Rd файл-регистра, входы и выходы АЛУ, расшифровку кодов инструкции - поля opcode и funct3, funct7, значение immediate (да, они не всегда имеют значение, но при обработке будут игнорированы), сигналы разрешения работы каждого из этапов.

    (рис 6.9) Тестовая программа в симуляторе RARS (рис 6.10) Временные диаграммы исполнения программы в многотактном ядре

    На диаграммах видно выполнение математических операций, операций перехода - код доходит до адреса 1C и в соответствии с программной переходит на нулевой адрес.

    Очевидные недостатки представленной микроархитектуры:

  • большое количество "сквозных" многоразрядных сигналов, проходящих через все этапы и требующие для промежуточного хранения соответствующего количества бит в промежуточных регистрах (Rx).
  • регистры Rx в представленном коде фиксированной разрядности, без учета реально требуемого количества бит на каждом из этапов.
  • PS

    "Отладочный вариант" файл-регистра с дополнительными выходами, позволяющими на временных диаграммах наблюдать значения подключенных к ним выходов регистров.

    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 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];
      wire rd_nonzero;
      wire rs1_nonzero;
      wire rs2_nonzero;
    
      assign rd_nonzero = |rd;
      assign rs1_nonzero = |rs1;
      assign rs2_nonzero = |rs2;
    // read RAM content from file
    //initial
    //$readmemh("ram.txt",ram);
    
    always @ (posedge clk)
      begin
        if (en  we  rd_nonzero) ram[rd] <= Rd_input;
      end
    
    always @ (*)
      begin
        Rs1_out <= rs1_nonzero ? ram[rs1] : 32'h0;
        Rs2_out <= rs2_nonzero ? ram[rs2] : 32'h0;
        x1_out <= ram[1];
        x2_out <= ram[2];
        x3_out <= ram[3];
        x4_out <= ram[4];
        x5_out <= ram[5];
      end
    // */
    endmodule
    
    Вернуться к учебному плану