На работу над многопоточным вариантом вдохновили процессоры семейства Xcore компании XMOS, специализирующейся на процессорах/микроконтроллерах для встраиваемых систем. Решения XMOS традиционно включают в себя элементы, которые традиционно требовали бы использования компонентов другого класса [1]. Там, где традиционно можно использовать микроконтроллер для управления конструкцией, DSP для обработки сигналов и, возможно, CPLD для подключения к сложному цифровому интерфейсу, процессоры XMOS могут выполнять эти три задачи в одном устройстве, используя единый программный процесс на основе программного обеспечения. Их характерные черты - аппаратная многопоточность, возможность масштабирования количества ядер/процессоров в системе, гибкие программно-конфигурируемые порты ввода-вывода. В ранних версиях своих процессоров XMOS применяли свои RISC ядра, были серии с комбинацией RISC-ядер и ARM-ядра. В 2023м году компания анонсировала вариант процессора с ядрами RISC-V (рис.8.1).
Вторым побуждающим мотивом экспериментов с является желание в решениях софт-ядер для FPGA уйти от необходимости введения в состав софт-процессора контроллера прерываний (кто любит обрабатывать прерывания, да еще и вложенные? Да - мало кто).
(рис 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_wren) 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
Программный счетчик также будет массивом регистров.
module rv_pc
#(
parameter WIDTH=32
)
(
input clk,
input rst_n,
input en,
input pc_load,
input [WIDTH-1:0] pc_next,
output [WIDTH-1:0] pc,
output reg [WIDTH-1:0] pc_plus,
input [2:0] hart_in,
input [2:0] hart_out
);
reg [WIDTH-1:0] pc_reg[0:7];
// read initial PCs data values
initial
$readmemh("pc.txt",pc_reg);
always@(posedge clk or negedge rst_n)
begin
if (en) begin
if(pc_load==1'b1)
pc_reg[hart_in] <= pc_next;
else
pc_reg[hart_in] <= pc_reg[hart_in] + 3'h4;
end
end
//end///*
assign pc = pc_reg[hart_out];
always @ *
begin
pc_plus <= pc_reg[hart_out] + 3'h4;
end
// */
endmodule
В файле "pc.txt" - начальные значения регистров.
Как и ожидалось, "ленивый" вариант преобразования многотактного процессора в многопоточный простой заменой файл-регистра и счетчика не работоспособен, требуются некоторый механизм управления.
Для управления выборкой данных хартов введём группу регистров - hart-reg, объединенных в линейку. Выходные сигналы - номер харта и бит активности харта. При сбросе регистры инициируются нулями и биты активности хартов сброшены.
На линейку в процессе функционирования должна подаваться последовательность хартов с кодами активности. Сама последовательность может, например, хранится в отдельной небольшой памяти - назовем ее hart_table. Hart_table адресуется циклическим счетчиком.
Для текущего примера создадим простенький "системный таймер" разрядностью 64-бита (на самом деле можно любой приемлемой разрядности, в данном случае 64 бита выбрано по аналогии с традиционными таймерами-счетчиками в реализациях RISC-V архитектуры). Младшие разряды счетчика будут адресовать память таблицы хартов. Сам счетчик потом можно будет использовать в качестве счетчика тактов работы с момента старта системы, например.
Биты активности харта также должны будут использованы для управления записью в такие модули процессорного ядра, как программный счетчик, файл-регистр, запись в память (если харт не активен - запись не будет произведена). Также инверсный сигнал активности харта подается на вход "сброса конвейера" АЛУ (да, для данной реализации будет взята версия АЛУ конвейеризированного процессорного ядра).
Уровни "разделяются" промежуточными регистрами (обычные регистры хранения), которые фиксируют данные (регистры R0 - R6). Ряд сигналов просто "пробрасываются" по этапам (напрямую между регистрами) в том случае, если они не задействованы в них, но требуются на последующих - всё как и было в простом многотактном варианте.
Общая структура многопоточного процессора представлена на рис.8.3.
(рис 8.3) Структура многопоточного процессора
Описание памяти таблицы хартов - в текущем варианте - память на 8 записей, выходная разрядность 4 бита, старший бит - бит активности харта, младшие три - номер харта:
module rv_hart_table
#(
parameter DATA_WIDTH=4,
parameter ADDR_WIDTH=3
)
( input clk,
input [(ADDR_WIDTH-1):0] h_addr,
output reg [(DATA_WIDTH-1):0] h_out,
input we,
input en
);
// ROM array
reg [DATA_WIDTH-1:0] rom [0:2**ADDR_WIDTH-1] ;
// read ROM content from file
initial
$readmemh("hart_table.txt",rom);
always @ (*)
begin
h_out <= rom[h_addr];
end
endmodule
Системный таймер:
module rv_timer
#(
parameter DATA_WIDTH=64
)
( input clk,
input rst,
output [(DATA_WIDTH-1):0] timer
);
reg [(DATA_WIDTH-1):0] cnt;
always@(posedge clk or negedge rst)
begin
if(!rst)
cnt <= 64'h0;
else
cnt <= cnt + 1'b1;
end
assign timer = cnt;
endmodule
Соединение таблицы хартов со счетчиком - рис.8.4.
(рис 8.4) Соединение таблицы хартов со счетчиком |
wire [63:0] tmr_out; rv_timer tmr( .clk(clk), .rst(rst), .timer(tmr_out) ); //========================================================== rv_hart_table hart_table( .clk(clk), .h_addr(tmr_out[2:0]), .h_out(hart0_out) ); //========================================================== wire [3:0] hart0_out; |
Соединение линейки регистров для хранения хартов:
wire [3:0] hart1_out; rv_hart_reg H1( .clk(clk), .rst(rst), .en(1'b1), .h_in(hart0_out), .h_out(hart1_out) ); wire [3:0] hart2_out; rv_hart_reg H2( .clk(clk), .rst(rst), .en(1'b1), .h_in(hart1_out), .h_out(hart2_out) ); wire [3:0] hart3_out; rv_hart_reg H3( .clk(clk), .rst(rst), .en(1'b1), .h_in(hart2_out), .h_out(hart3_out) ); wire [3:0] hart4_out; rv_hart_reg H4( .clk(clk), .rst(rst), .en(1'b1), .h_in(hart3_out), .h_out(hart4_out) ); wire [3:0] hart5_out; rv_hart_reg H5( .clk(clk), .rst(rst), .en(1'b1), .h_in(hart4_out), .h_out(hart5_out) ); wire [3:0] hart6_out; rv_hart_reg H6( .clk(clk), .rst(rst), .en(1'b1), .h_in(hart5_out), .h_out(hart6_out) );
Общий код процессора:
`include "rv_pc.v"
`include "rv_mem.v"
`include "rv_desh_pipe.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_hart_reg.v"
`include "rv_timer.v"
`include "rv_hart_table.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 [63:0] tmr_out;
rv_timer tmr(
.clk(clk),
.rst(rst),
.timer(tmr_out)
);
//===============================================================
rv_hart_table hart_table(
.clk(clk),
.h_addr(tmr_out[2:0]),
.h_out(hart0_out)
);
// ==============================================================
wire [3:0] hart0_out;
wire [3:0] hart1_out;
rv_hart_reg H1(
.clk(clk),
.rst(rst),
.en(1'b1),
.h_in(hart0_out),
.h_out(hart1_out)
);
wire [3:0] hart2_out;
rv_hart_reg H2(
.clk(clk),
.rst(rst),
.en(1'b1),
.h_in(hart1_out),
.h_out(hart2_out)
);
wire [3:0] hart3_out;
rv_hart_reg H3(
.clk(clk),
.rst(rst),
.en(1'b1),
.h_in(hart2_out),
.h_out(hart3_out)
);
wire [3:0] hart4_out;
rv_hart_reg H4(
.clk(clk),
.rst(rst),
.en(1'b1),
.h_in(hart3_out),
.h_out(hart4_out)
);
wire [3:0] hart5_out;
rv_hart_reg H5(
.clk(clk),
.rst(rst),
.en(1'b1),
.h_in(hart4_out),
.h_out(hart5_out)
);
wire [3:0] hart6_out;
rv_hart_reg H6(
.clk(clk),
.rst(rst),
.en(1'b1),
.h_in(hart5_out),
.h_out(hart6_out)
);
// ==============================================================
// 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(hart6_out[3]),
.pc_load(r6l6_PC_load),
.pc_next(r6l6_Rez),
.pc(l0r1_PC),
.pc_plus(l0r1_PCplus),
.hart_in(hart6_out[2:0]),
.hart_out(hart0_out[2:0])
);
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(1'b1),
.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(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(hart5_out[3])
);
// 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(1'b1),
.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(),
.pipe_rst(~hart2_out[3])
);
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(1'b1),
.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(hart6_out[3]),
.hart_in(hart6_out[2:0]),
.hart_out(hart3_out[2:0])
);
rv_csr csr(
.clk(~clk),
.csr_addr_out(r3l3_imm[11:0]),
.csr_addr_in(r6l6_imm[11:0]),
.csr_in(r6l6_Rez),
.csr_out(l3r4_CSR),
.csr_wr(r6l6_csr_wr),
.en(hart6_out[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(1'b1),
.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(1'b1),
.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(1'b1),
.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)
);
Endmodule
Традиционно небольшая, тестовая программа:
lui x1,0x1 srli x1,x1,12 l1: addi x1, x1, 1 jal x2,l1 lui x1,0x99 lui x1,0x2 srli x1,x1,12 l2: addi x1, x1, 2 jal x2,l2 lui x1,0x99 lui x1,0x3 srli x1,x1,12 l3: addi x1, x1, 3 jal x2,l3 lui x1,0x99 lui x1,0x4 srli x1,x1,12 l4: addi x1, x1, 4 jal x2,l4 lui x1,0x99 lui x1,0x5 srli x1,x1,12 l5: addi x1, x1, 5 jal x2,l5 lui x1,0x99 lui x1,0x6 srli x1,x1,12 l6: addi x1, x1, 6 jal x2,l6 lui x1,0x99 lui x1,0x7 srli x1,x1,12 l7: addi x1, x1, 7 jal x2,l7 lui x1,0x99 lui x1,0x8 srli x1,x1,12 l8: addi x1, x1, 8 jal x2,l8 lui x1,0x99
На рис.8.5 - она же в симуляторе RARS.
(рис 8.5)
В программе заданы несколько коротких бесконечных цикла, работающих с регистрами х1, х2. Регистр х1 инициируется определенным значением, регистр х2 будет использован, как регистр связи. При простом линейном исполнении программа зациклится на метке l1.
Дамп программы:
Address Code Basic Line Source 0x00400000 0x000010b7 lui x1,1 1 lui x1,0x1 0x00400004 0x00c0d093 srli x1,x1,12 2 srli x1,x1,12 0x00400008 0x00108093 addi x1,x1,1 4 addi x1, x1, 1 0x0040000c 0xffdff16f jal x2,0xfffffffc 5 jal x2,l1 0x00400010 0x000990b7 lui x1,0x00000099 6 lui x1,0x99 0x00400014 0x000020b7 lui x1,2 8 lui x1,0x2 0x00400018 0x00c0d093 srli x1,x1,12 9 srli x1,x1,12 0x0040001c 0x00208093 addi x1,x1,2 11 addi x1, x1, 2 0x00400020 0xffdff16f jal x2,0xfffffffc 12 jal x2,l2 0x00400024 0x000990b7 lui x1,0x00000099 13 lui x1,0x99 0x00400028 0x000030b7 lui x1,3 15 lui x1,0x3 0x0040002c 0x00c0d093 srli x1,x1,12 16 srli x1,x1,12 0x00400030 0x00308093 addi x1,x1,3 18 addi x1, x1, 3 0x00400034 0xffdff16f jal x2,0xfffffffc 19 jal x2,l3 0x00400038 0x000990b7 lui x1,0x00000099 20 lui x1,0x99 0x0040003c 0x000040b7 lui x1,4 22 lui x1,0x4 0x00400040 0x00c0d093 srli x1,x1,12 23 srli x1,x1,12 0x00400044 0x00408093 addi x1,x1,4 25 addi x1, x1, 4 0x00400048 0xffdff16f jal x2,0xfffffffc 26 jal x2,l4 0x0040004c 0x000990b7 lui x1,0x00000099 27 lui x1,0x99 0x00400050 0x000050b7 lui x1,5 29 lui x1,0x5 0x00400054 0x00c0d093 srli x1,x1,12 30 srli x1,x1,12 0x00400058 0x00508093 addi x1,x1,5 32 addi x1, x1, 5 0x0040005c 0xffdff16f jal x2,0xfffffffc 33 jal x2,l5 0x00400060 0x000990b7 lui x1,0x00000099 34 lui x1,0x99 0x00400064 0x000060b7 lui x1,6 36 lui x1,0x6 0x00400068 0x00c0d093 srli x1,x1,12 37 srli x1,x1,12 0x0040006c 0x00608093 addi x1,x1,6 39 addi x1, x1, 6 0x00400070 0xffdff16f jal x2,0xfffffffc 40 jal x2,l6 0x00400074 0x000990b7 lui x1,0x00000099 41 lui x1,0x99 0x00400078 0x000070b7 lui x1,7 43 lui x1,0x7 0x0040007c 0x00c0d093 srli x1,x1,12 44 srli x1,x1,12 0x00400080 0x00708093 addi x1,x1,7 46 addi x1, x1, 7 0x00400084 0xffdff16f jal x2,0xfffffffc 47 jal x2,l7 0x00400088 0x000990b7 lui x1,0x00000099 48 lui x1,0x99 0x0040008c 0x000080b7 lui x1,8 50 lui x1,0x8 0x00400090 0x00c0d093 srli x1,x1,12 51 srli x1,x1,12 0x00400094 0x00808093 addi x1,x1,8 53 addi x1, x1, 8 0x00400098 0xffdff16f jal x2,0xfffffffc 54 jal x2,l8 0x0040009c 0x000990b7 lui x1,0x00000099 55 lui x1,0x99
Для исполнения на многопоточном процессоре массив программных счетчиков инициируется значениями адресов начала каждого из циклов - в данном примере - адреса:
0x0, 0x14, 0x28, 0x3С, 0x50, 0x64, 0x78, 0x8C.
Таблица хартов - перечисление номеров хартов с 0 по 7 в 4-битном представлении. Например, если активен должен быть харт с номером 0, то в таблице он будет представлен цифрой 8 (4`b1000), если с номером 3, то 0xB (4`b1011) и так далее.
(рис 8.5) Тестовая программа в симуляторе RARS
Тест 1:
| PC.txt | Hart_table.txt |
|---|---|
@00 0 14 28 3c 50 64 78 8c |
@00 8 1 2 3 4 5 6 7 |
Рис. 8.6-8.7 - временные диаграммы исполнения кода.
На ней представлены значения:
Для регистра Х1 0-го активного харта видна сначала его загрузка значением 0х1000, потом сдвиг его до 0х1.
(рис 8.6) Временные диаграммы исполнения программы в многопоточном ядре - активен харт0
(рис 8.7) Временные диаграммы исполнения программы в многопоточном ядре - активен харт0
Тест 3 - распределение адресов тоже самое, активируем 2й харт (номер 1):
| PC.txt | Hart_table.txt |
|---|---|
@00 0 14 28 3c 50 64 78 8c |
@00 8 9 2 3 4 5 6 7 |
(рис 8.8) Временные диаграммы исполнения программы в многопоточном ядре - активены харт0 и харт1
Итоговые микроархитектурные блоки многопоточного варианта процессора:
На работу над многопоточным вариантом вдохновили процессоры семейства Xcore компании XMOS, специализирующейся на процессорах/микроконтроллерах для встраиваемых систем. Решения XMOS традиционно включают в себя элементы, которые традиционно требовали бы использования компонентов другого класса [1]. Там, где традиционно можно использовать микроконтроллер для управления конструкцией, DSP для обработки сигналов и, возможно, CPLD для подключения к сложному цифровому интерфейсу, процессоры XMOS могут выполнять эти три задачи в одном устройстве, используя единый программный процесс на основе программного обеспечения. Их характерные черты - аппаратная многопоточность, возможность масштабирования количества ядер/процессоров в системе, гибкие программно-конфигурируемые порты ввода-вывода. В ранних версиях своих процессоров XMOS применяли свои RISC ядра, были серии с комбинацией RISC-ядер и ARM-ядра. В 2023м году компания анонсировала вариант процессора с ядрами RISC-V (рис.8.1).
Вторым побуждающим мотивом экспериментов с является желание в решениях софт-ядер для FPGA уйти от необходимости введения в состав софт-процессора контроллера прерываний (кто любит обрабатывать прерывания, да еще и вложенные? Да - мало кто).
(рис 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_wren) 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
Программный счетчик также будет массивом регистров.
module rv_pc
#(
parameter WIDTH=32
)
(
input clk,
input rst_n,
input en,
input pc_load,
input [WIDTH-1:0] pc_next,
output [WIDTH-1:0] pc,
output reg [WIDTH-1:0] pc_plus,
input [2:0] hart_in,
input [2:0] hart_out
);
reg [WIDTH-1:0] pc_reg[0:7];
// read initial PCs data values
initial
$readmemh("pc.txt",pc_reg);
always@(posedge clk or negedge rst_n)
begin
if (en) begin
if(pc_load==1'b1)
pc_reg[hart_in] <= pc_next;
else
pc_reg[hart_in] <= pc_reg[hart_in] + 3'h4;
end
end
//end///*
assign pc = pc_reg[hart_out];
always @ *
begin
pc_plus <= pc_reg[hart_out] + 3'h4;
end
// */
endmodule
В файле "pc.txt" - начальные значения регистров.
Как и ожидалось, "ленивый" вариант преобразования многотактного процессора в многопоточный простой заменой файл-регистра и счетчика не работоспособен, требуются некоторый механизм управления.
Для управления выборкой данных хартов введём группу регистров - hart-reg, объединенных в линейку. Выходные сигналы - номер харта и бит активности харта. При сбросе регистры инициируются нулями и биты активности хартов сброшены.
На линейку в процессе функционирования должна подаваться последовательность хартов с кодами активности. Сама последовательность может, например, хранится в отдельной небольшой памяти - назовем ее hart_table. Hart_table адресуется циклическим счетчиком.
Для текущего примера создадим простенький "системный таймер" разрядностью 64-бита (на самом деле можно любой приемлемой разрядности, в данном случае 64 бита выбрано по аналогии с традиционными таймерами-счетчиками в реализациях RISC-V архитектуры). Младшие разряды счетчика будут адресовать память таблицы хартов. Сам счетчик потом можно будет использовать в качестве счетчика тактов работы с момента старта системы, например.
Биты активности харта также должны будут использованы для управления записью в такие модули процессорного ядра, как программный счетчик, файл-регистр, запись в память (если харт не активен - запись не будет произведена). Также инверсный сигнал активности харта подается на вход "сброса конвейера" АЛУ (да, для данной реализации будет взята версия АЛУ конвейеризированного процессорного ядра).
Уровни "разделяются" промежуточными регистрами (обычные регистры хранения), которые фиксируют данные (регистры R0 - R6). Ряд сигналов просто "пробрасываются" по этапам (напрямую между регистрами) в том случае, если они не задействованы в них, но требуются на последующих - всё как и было в простом многотактном варианте.
Общая структура многопоточного процессора представлена на рис.8.3.
(рис 8.3) Структура многопоточного процессора
Описание памяти таблицы хартов - в текущем варианте - память на 8 записей, выходная разрядность 4 бита, старший бит - бит активности харта, младшие три - номер харта:
module rv_hart_table
#(
parameter DATA_WIDTH=4,
parameter ADDR_WIDTH=3
)
( input clk,
input [(ADDR_WIDTH-1):0] h_addr,
output reg [(DATA_WIDTH-1):0] h_out,
input we,
input en
);
// ROM array
reg [DATA_WIDTH-1:0] rom [0:2**ADDR_WIDTH-1] ;
// read ROM content from file
initial
$readmemh("hart_table.txt",rom);
always @ (*)
begin
h_out <= rom[h_addr];
end
endmodule
Системный таймер:
module rv_timer
#(
parameter DATA_WIDTH=64
)
( input clk,
input rst,
output [(DATA_WIDTH-1):0] timer
);
reg [(DATA_WIDTH-1):0] cnt;
always@(posedge clk or negedge rst)
begin
if(!rst)
cnt <= 64'h0;
else
cnt <= cnt + 1'b1;
end
assign timer = cnt;
endmodule
Соединение таблицы хартов со счетчиком - рис.8.4.
(рис 8.4) Соединение таблицы хартов со счетчиком |
wire [63:0] tmr_out; rv_timer tmr( .clk(clk), .rst(rst), .timer(tmr_out) ); //========================================================== rv_hart_table hart_table( .clk(clk), .h_addr(tmr_out[2:0]), .h_out(hart0_out) ); //========================================================== wire [3:0] hart0_out; |
Соединение линейки регистров для хранения хартов:
wire [3:0] hart1_out; rv_hart_reg H1( .clk(clk), .rst(rst), .en(1'b1), .h_in(hart0_out), .h_out(hart1_out) ); wire [3:0] hart2_out; rv_hart_reg H2( .clk(clk), .rst(rst), .en(1'b1), .h_in(hart1_out), .h_out(hart2_out) ); wire [3:0] hart3_out; rv_hart_reg H3( .clk(clk), .rst(rst), .en(1'b1), .h_in(hart2_out), .h_out(hart3_out) ); wire [3:0] hart4_out; rv_hart_reg H4( .clk(clk), .rst(rst), .en(1'b1), .h_in(hart3_out), .h_out(hart4_out) ); wire [3:0] hart5_out; rv_hart_reg H5( .clk(clk), .rst(rst), .en(1'b1), .h_in(hart4_out), .h_out(hart5_out) ); wire [3:0] hart6_out; rv_hart_reg H6( .clk(clk), .rst(rst), .en(1'b1), .h_in(hart5_out), .h_out(hart6_out) );
Общий код процессора:
`include "rv_pc.v"
`include "rv_mem.v"
`include "rv_desh_pipe.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_hart_reg.v"
`include "rv_timer.v"
`include "rv_hart_table.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 [63:0] tmr_out;
rv_timer tmr(
.clk(clk),
.rst(rst),
.timer(tmr_out)
);
//===============================================================
rv_hart_table hart_table(
.clk(clk),
.h_addr(tmr_out[2:0]),
.h_out(hart0_out)
);
// ==============================================================
wire [3:0] hart0_out;
wire [3:0] hart1_out;
rv_hart_reg H1(
.clk(clk),
.rst(rst),
.en(1'b1),
.h_in(hart0_out),
.h_out(hart1_out)
);
wire [3:0] hart2_out;
rv_hart_reg H2(
.clk(clk),
.rst(rst),
.en(1'b1),
.h_in(hart1_out),
.h_out(hart2_out)
);
wire [3:0] hart3_out;
rv_hart_reg H3(
.clk(clk),
.rst(rst),
.en(1'b1),
.h_in(hart2_out),
.h_out(hart3_out)
);
wire [3:0] hart4_out;
rv_hart_reg H4(
.clk(clk),
.rst(rst),
.en(1'b1),
.h_in(hart3_out),
.h_out(hart4_out)
);
wire [3:0] hart5_out;
rv_hart_reg H5(
.clk(clk),
.rst(rst),
.en(1'b1),
.h_in(hart4_out),
.h_out(hart5_out)
);
wire [3:0] hart6_out;
rv_hart_reg H6(
.clk(clk),
.rst(rst),
.en(1'b1),
.h_in(hart5_out),
.h_out(hart6_out)
);
// ==============================================================
// 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(hart6_out[3]),
.pc_load(r6l6_PC_load),
.pc_next(r6l6_Rez),
.pc(l0r1_PC),
.pc_plus(l0r1_PCplus),
.hart_in(hart6_out[2:0]),
.hart_out(hart0_out[2:0])
);
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(1'b1),
.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(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(hart5_out[3])
);
// 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(1'b1),
.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(),
.pipe_rst(~hart2_out[3])
);
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(1'b1),
.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(hart6_out[3]),
.hart_in(hart6_out[2:0]),
.hart_out(hart3_out[2:0])
);
rv_csr csr(
.clk(~clk),
.csr_addr_out(r3l3_imm[11:0]),
.csr_addr_in(r6l6_imm[11:0]),
.csr_in(r6l6_Rez),
.csr_out(l3r4_CSR),
.csr_wr(r6l6_csr_wr),
.en(hart6_out[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(1'b1),
.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(1'b1),
.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(1'b1),
.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)
);
Endmodule
Традиционно небольшая, тестовая программа:
lui x1,0x1 srli x1,x1,12 l1: addi x1, x1, 1 jal x2,l1 lui x1,0x99 lui x1,0x2 srli x1,x1,12 l2: addi x1, x1, 2 jal x2,l2 lui x1,0x99 lui x1,0x3 srli x1,x1,12 l3: addi x1, x1, 3 jal x2,l3 lui x1,0x99 lui x1,0x4 srli x1,x1,12 l4: addi x1, x1, 4 jal x2,l4 lui x1,0x99 lui x1,0x5 srli x1,x1,12 l5: addi x1, x1, 5 jal x2,l5 lui x1,0x99 lui x1,0x6 srli x1,x1,12 l6: addi x1, x1, 6 jal x2,l6 lui x1,0x99 lui x1,0x7 srli x1,x1,12 l7: addi x1, x1, 7 jal x2,l7 lui x1,0x99 lui x1,0x8 srli x1,x1,12 l8: addi x1, x1, 8 jal x2,l8 lui x1,0x99
На рис.8.5 - она же в симуляторе RARS.
(рис 8.5)
В программе заданы несколько коротких бесконечных цикла, работающих с регистрами х1, х2. Регистр х1 инициируется определенным значением, регистр х2 будет использован, как регистр связи. При простом линейном исполнении программа зациклится на метке l1.
Дамп программы:
Address Code Basic Line Source 0x00400000 0x000010b7 lui x1,1 1 lui x1,0x1 0x00400004 0x00c0d093 srli x1,x1,12 2 srli x1,x1,12 0x00400008 0x00108093 addi x1,x1,1 4 addi x1, x1, 1 0x0040000c 0xffdff16f jal x2,0xfffffffc 5 jal x2,l1 0x00400010 0x000990b7 lui x1,0x00000099 6 lui x1,0x99 0x00400014 0x000020b7 lui x1,2 8 lui x1,0x2 0x00400018 0x00c0d093 srli x1,x1,12 9 srli x1,x1,12 0x0040001c 0x00208093 addi x1,x1,2 11 addi x1, x1, 2 0x00400020 0xffdff16f jal x2,0xfffffffc 12 jal x2,l2 0x00400024 0x000990b7 lui x1,0x00000099 13 lui x1,0x99 0x00400028 0x000030b7 lui x1,3 15 lui x1,0x3 0x0040002c 0x00c0d093 srli x1,x1,12 16 srli x1,x1,12 0x00400030 0x00308093 addi x1,x1,3 18 addi x1, x1, 3 0x00400034 0xffdff16f jal x2,0xfffffffc 19 jal x2,l3 0x00400038 0x000990b7 lui x1,0x00000099 20 lui x1,0x99 0x0040003c 0x000040b7 lui x1,4 22 lui x1,0x4 0x00400040 0x00c0d093 srli x1,x1,12 23 srli x1,x1,12 0x00400044 0x00408093 addi x1,x1,4 25 addi x1, x1, 4 0x00400048 0xffdff16f jal x2,0xfffffffc 26 jal x2,l4 0x0040004c 0x000990b7 lui x1,0x00000099 27 lui x1,0x99 0x00400050 0x000050b7 lui x1,5 29 lui x1,0x5 0x00400054 0x00c0d093 srli x1,x1,12 30 srli x1,x1,12 0x00400058 0x00508093 addi x1,x1,5 32 addi x1, x1, 5 0x0040005c 0xffdff16f jal x2,0xfffffffc 33 jal x2,l5 0x00400060 0x000990b7 lui x1,0x00000099 34 lui x1,0x99 0x00400064 0x000060b7 lui x1,6 36 lui x1,0x6 0x00400068 0x00c0d093 srli x1,x1,12 37 srli x1,x1,12 0x0040006c 0x00608093 addi x1,x1,6 39 addi x1, x1, 6 0x00400070 0xffdff16f jal x2,0xfffffffc 40 jal x2,l6 0x00400074 0x000990b7 lui x1,0x00000099 41 lui x1,0x99 0x00400078 0x000070b7 lui x1,7 43 lui x1,0x7 0x0040007c 0x00c0d093 srli x1,x1,12 44 srli x1,x1,12 0x00400080 0x00708093 addi x1,x1,7 46 addi x1, x1, 7 0x00400084 0xffdff16f jal x2,0xfffffffc 47 jal x2,l7 0x00400088 0x000990b7 lui x1,0x00000099 48 lui x1,0x99 0x0040008c 0x000080b7 lui x1,8 50 lui x1,0x8 0x00400090 0x00c0d093 srli x1,x1,12 51 srli x1,x1,12 0x00400094 0x00808093 addi x1,x1,8 53 addi x1, x1, 8 0x00400098 0xffdff16f jal x2,0xfffffffc 54 jal x2,l8 0x0040009c 0x000990b7 lui x1,0x00000099 55 lui x1,0x99
Для исполнения на многопоточном процессоре массив программных счетчиков инициируется значениями адресов начала каждого из циклов - в данном примере - адреса:
0x0, 0x14, 0x28, 0x3С, 0x50, 0x64, 0x78, 0x8C.
Таблица хартов - перечисление номеров хартов с 0 по 7 в 4-битном представлении. Например, если активен должен быть харт с номером 0, то в таблице он будет представлен цифрой 8 (4`b1000), если с номером 3, то 0xB (4`b1011) и так далее.
(рис 8.5) Тестовая программа в симуляторе RARS
Тест 1:
| PC.txt | Hart_table.txt |
|---|---|
@00 0 14 28 3c 50 64 78 8c |
@00 8 1 2 3 4 5 6 7 |
Рис. 8.6-8.7 - временные диаграммы исполнения кода.
На ней представлены значения:
Для регистра Х1 0-го активного харта видна сначала его загрузка значением 0х1000, потом сдвиг его до 0х1.
(рис 8.6) Временные диаграммы исполнения программы в многопоточном ядре - активен харт0
(рис 8.7) Временные диаграммы исполнения программы в многопоточном ядре - активен харт0
Тест 3 - распределение адресов тоже самое, активируем 2й харт (номер 1):
| PC.txt | Hart_table.txt |
|---|---|
@00 0 14 28 3c 50 64 78 8c |
@00 8 9 2 3 4 5 6 7 |
(рис 8.8) Временные диаграммы исполнения программы в многопоточном ядре - активены харт0 и харт1
Итоговые микроархитектурные блоки многопоточного варианта процессора:
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.