Попробуем немного улучшить предыдущий вариант процессорного ядра, проведя повторную ревизию модулей ядра, проанализировав систему команд и возможные преобразования сигналов и данных, которые необходимо будет сделать.
Подход к построению многотактного процессорного ядра выбран не совсем классический, введем изначально некоторое количество т.н. "неархитектурных регистров" которые будут хранить промежуточные состояния.
И так, еще раз наши микроархитектурные блоки однотактного варианта:
Пробуем рассмотреть возможные сигналы и действия на каждом из этапов. Промежуточные результаты каждого из этапов фиксируются в неархитектурных регистрах. Да, таким образом количество задействованных ресурсов возрастает, но в качестве упражнения такой подход может иметь место.
Предлагается разбиение функциональности на несколько уровней:
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 и в соответствии с программной переходит на нулевой адрес.
Очевидные недостатки представленной микроархитектуры:
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
Попробуем немного улучшить предыдущий вариант процессорного ядра, проведя повторную ревизию модулей ядра, проанализировав систему команд и возможные преобразования сигналов и данных, которые необходимо будет сделать.
Подход к построению многотактного процессорного ядра выбран не совсем классический, введем изначально некоторое количество т.н. "неархитектурных регистров" которые будут хранить промежуточные состояния.
И так, еще раз наши микроархитектурные блоки однотактного варианта:
Пробуем рассмотреть возможные сигналы и действия на каждом из этапов. Промежуточные результаты каждого из этапов фиксируются в неархитектурных регистрах. Да, таким образом количество задействованных ресурсов возрастает, но в качестве упражнения такой подход может иметь место.
Предлагается разбиение функциональности на несколько уровней:
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 и в соответствии с программной переходит на нулевой адрес.
Очевидные недостатки представленной микроархитектуры:
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
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.