Опубликован: 19.04.2025 | Доступ: свободный | Студентов: 1 / 0 | Длительность: 07:05:00
Лекция 6:

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

< Лекция 5 || Лекция 6: 1234 || Лекция 7 >

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

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

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

  • 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_wr&en) 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.

< Лекция 5 || Лекция 6: 1234 || Лекция 7 >