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

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

< Лекция 7 || Лекция 8: 12345 || Лекция 9 >

На работу над многопоточным вариантом вдохновили процессоры семейства Xcore компании XMOS, специализирующейся на процессорах/микроконтроллерах для встраиваемых систем. Решения XMOS традиционно включают в себя элементы, которые традиционно требовали бы использования компонентов другого класса [1]. Там, где традиционно можно использовать микроконтроллер для управления конструкцией, DSP для обработки сигналов и, возможно, CPLD для подключения к сложному цифровому интерфейсу, процессоры XMOS могут выполнять эти три задачи в одном устройстве, используя единый программный процесс на основе программного обеспечения. Их характерные черты - аппаратная многопоточность, возможность масштабирования количества ядер/процессоров в системе, гибкие программно-конфигурируемые порты ввода-вывода. В ранних версиях своих процессоров XMOS применяли свои RISC ядра, были серии с комбинацией RISC-ядер и ARM-ядра. В 2023м году компания анонсировала вариант процессора с ядрами RISC-V (рис.8.1).

Вторым побуждающим мотивом экспериментов с является желание в решениях софт-ядер для FPGA уйти от необходимости введения в состав софт-процессора контроллера прерываний (кто любит обрабатывать прерывания, да еще и вложенные? Да - мало кто).

Двухядерный процессор XMOS с восьмипоточными RISC-V ядрами

Рис. 8.1. Двухядерный процессор XMOS с восьмипоточными RISC-V ядрами

Попробуем несколько модифицировать многотактный процессор, добавив ему поддержку многопоточности в виде "теневых" копий основных архитектурных регистров. В терминологии RISC-V аппаратно-поддерживаемый поток называется хартом (hart) [2-4]. Общая структура ядра пока останется почти прежней - рис.8.2.

Базовая структура ядра

Рис. 8.2. Базовая структура ядра

Микроархитектурные блоки:

rv_pc - программный счетчик;

rv_mem - блок памяти (программная и оперативная;

rv_desh - дешифратор команд (слова-инструкции);

rv_imm - формирователь непосредственного значения из слова-инструкции;

rv_reg_file - файл-регистр

rv_ops_mux - коммутатор операндов для АЛУ;

rv_cmp - формирователь сигнала разрешения перехода;

rv_alu_v - АЛУ;

rv_rez_mux - коммутатор результатов;

rv_csr - блок регистров специального назначения.

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

Блок CSR-регистров в таком варианте должен будет иметь два раздельных адресных входа - на чтение его данных и на запись данных в него (из-за того, что он оставлен общим для всех хартов и операции чтения/записи осуществляются на разных этапах):

module rv_csr
#(
  parameter DATA_WIDTH=32, 
  parameter ADDR_WIDTH=12,
  parameter CSR_size = 32
  )
( input clk,
  input [(ADDR_WIDTH-1):0] csr_addr_in,
  input [(DATA_WIDTH-1):0] csr_in,
  input [(ADDR_WIDTH-1):0] csr_addr_out,
  output reg [(DATA_WIDTH-1):0] csr_out,
  input csr_wr,
  input en
);
// csr register file
reg [ADDR_WIDTH-1:0] csr_reg[0:CSR_size-1];

always @ (posedge clk)
begin
  case (csr_addr_in)
    32'h0 : begin
      if (csr_wr&en) begin
        csr_reg[0] <= csr_in;
      end 
    end
    default: begin
      csr_reg[1] <= 32'h555;
    end
  endcase
  case (csr_addr_out)
    32'h0 : begin
      csr_out <= csr_reg[0];
    end
    default: begin
      csr_out<=32'hAAA;
    end
  endcase
end

endmodule

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

module rv_reg_file
#(
  parameter DATA_WIDTH=32, 
  parameter ADDR_WIDTH=5
  )
( input clk,
  input [(ADDR_WIDTH-1):0] rs1,
  input [(ADDR_WIDTH-1):0] rs2,
  input [(ADDR_WIDTH-1):0] rd,

  output reg [(DATA_WIDTH-1):0] Rs1_out,
  output reg [(DATA_WIDTH-1):0] Rs2_out,
  input [(DATA_WIDTH-1):0] Rd_input,
  input [2:0] hart_in, // hard to read out data from
  input [2:0] hart_out, // hard to write data to
  input we,
  input en,
  output reg [(DATA_WIDTH-1):0] x1_out,
  output reg [(DATA_WIDTH-1):0] x2_out,
  output reg [(DATA_WIDTH-1):0] x3_out,
  output reg [(DATA_WIDTH-1):0] x4_out,
  output reg [(DATA_WIDTH-1):0] x5_out
);
// RAM array
  reg [DATA_WIDTH-1:0] ram[0:2**ADDR_WIDTH-1][0:7];
  wire rd_nonzero;
  wire rs1_nonzero;
  wire rs2_nonzero;
  assign rd_nonzero = |rd;
  assign rs1_nonzero = |rs1;
  assign rs2_nonzero = |rs2;

always @ (posedge clk)
  begin
    if (en & we & rd_nonzero) ram[rd][hart_in] <= Rd_input;
  end

always @ (*)
  begin
    Rs1_out <= rs1_nonzero ? ram[rs1][hart_out] : 32'h0;
    Rs2_out <= rs2_nonzero ? ram[rs2][hart_out] : 32'h0;
    x1_out <= ram[1][hart_out];
    x2_out <= ram[2][hart_out];
    x3_out <= ram[3][hart_out];
    x4_out <= ram[4][hart_out];
    x5_out <= ram[5][hart_out];
  end
// */
Endmodule
< Лекция 7 || Лекция 8: 12345 || Лекция 9 >