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

Конвейеризированное процессорное ядро

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

Несложно заметить, что предложенную микроархитектуру многотактного процессора (рис.7.1) можно относительно просто превратить в конвейеризированную. Для этого достаточно (вот сейчас будет страшно - а точно ли?) разрешить работу всех промежуточных регистров Rx, а также блоков программного счетчика, файл-регистра, регистров специальных функций, и памяти на запись.

(рис 7.1) Структурная схема многотактного процессора

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

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

(рис 7.2) Временные диаграммы конвейеризованного варианта

Смотря на рис.7.2 можно с одной стороны порадоваться - выборка инструкций действительно конвейеризовалась, но вот выполнение последовательности команд наталкивается на неопределенность при выборке операндов из файл-регистра.

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

(рис 7.3) "Разбор" причин нарушения корректности работы

Следовательно, необходимо немного пересмотреть вариант разбиения конвейера по этапам для корректной работы.

Микроархитектурные блоки процессора оставляем пока как и были:

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

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

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

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

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

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

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

rv_alu_v - АЛУ;

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

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

Новый вариант разбиения по этапам:

L0 - выборка текущего значения программного счетчика (точнее пары значений - PC и РС+4).

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

L2 - дешифрация инструкции - выделение адресов операндов, типа инструкции, формирование управляющих сигналов.

L3 - вычисления - выборка операндов из файл-регистров, работа АЛУ, чтение/запись данных из оперативной памяти, запись данных в файл-регистры.

L4 - запись нового значения в программный счётчик.

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

Структурная схема конвейеризированного варианта процессора представлена на рис.7.4.

(рис 7.4) Структурная схема конвейеризированного варианта процессора

Этапы L0-L2 остаются пока без изменений.

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

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

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

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

(рис 7.5) Выборка текущего значения программного счетчика
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),
  .pc_load(r6l7_PC_load),
  .pc_next(l6r7_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;
  assign r1_out = {r1l1_PC, r1l1_PCplus, 192'h0}; 

rv_r_reg R1(
  .clk(clk),
  .rst_n(rst),
  .en(en_level[0]),
  .r_in(r1_in),
  .r_out(r1_out)
);
(рис 7.6) Выборка слова-инструкции из памяти
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),
  .code_out(l1r2_iw),
  .d_addr(r5l5_Rez),
  .d_out(l5r6_Mem_data),
  .d_in(r5l5_Rs2_reg),
  .we(r5l5_mem_wr)
);
// 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_out = {r2l2_iw, r2l2_PC, r2l2_PCplus, 160'h0};

rv_r_reg R2(
  .clk(clk),
  .rst_n(rst),
  .en(en_level[1]),
  .r_in(r2_in),
  .r_out(r2_out)
);

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

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

(рис 7.7) Дешифровка инструкции
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};
assign r3_out = {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, 125'h0};

rv_r_reg R3(
  .clk(clk),
  .rst_n(rst),
  .en(en_level[2]),
  .r_in(r3_in),
  .r_out(r3_out)
);

L3 - вычисления

Адреса регистров-операндов подаются на адресные входы файл-регистров (основного и регистров специальных функций).

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

Также выходы файл-регистра Rs1, Rs2 совместно с константой и значением регистра CSR и программного счетчика подается на выход мультиплексора операндов, выходы его - на входы операндов АЛУ. Выходные сигналы АЛУ поступают на вход блока CSR, на вход мультиплексора операндов для записи в файл-регистр (по адресу rd), адресуют память (ОЗУ).

В регистр R4 записывается новое значение программного счетчика и сигнал разрешения записи в него.

(рис 7.8) Структура логики этапа L3 - вычисления

Описание логики этапа "вычисления":

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)
);
wire [31:0] Rs1_reg;
wire [31:0] Rs2_reg;
wire [31:0] CSR_mux;

rv_reg_file reg_file(   // register's file
  .clk(~clk),
  .rs1(r3l3_rs1),
  .rs2(r3l3_rs2),
  .rd(r3l3_rd),
  .Rs1_out(Rs1_reg),
  .Rs2_out(Rs2_reg),
  .Rd_input(Rd_reg),
  .we(r3l3_rd_wr),
  .en(1'b1)
);
rv_csr csr(
  .clk(~clk),
  .csr_addr(r3l3_imm[11:0]),
  .csr_in(Rez),
  .csr_out(CSR_mux),
  .csr_wr(r3l3_csr_wr),
  .en(1'b1)
);
wire [31:0] Op1;
wire [31:0] Op2;
rv_ops_mux ops_mux(
  .opcode(r3l3_op),
  .funct3(r3l3_fn3),
  .funct7(r3l3_fn7),
  .Rs1(Rs1_reg),
  .Rs2(Rs2_reg),
  .imm(r3l3_imm),
  .PC(r3l3_PC),
  .CSR(CSR_mux),
  .Op1(Op1),
  .Op2(Op2)
);
rv_cmp cmp(
  .opcode(r3l3_op),
  .funct3(r3l3_fn3),
  .Rs1(Rs1_reg),
  .Rs2(Rs2_reg),
  .pc_new(l3r4_PC_load)
);
wire [31:0] Rez;
wire [31:0] Rd_reg;
wire [31:0] Mem_data;
rv_alu_v alu_v(
  .opcode(r3l3_op),
  .funct3(r3l3_fn3),
  .funct7(r3l3_fn7),
  .Op1(Op1),
  .Op2(Op2),
  .Rez(Rez)
);

rv_rez_mux rez_mux(
  .opcode(r3l3_op),
  .funct3(r3l3_fn3),
  .funct7(r3l3_fn7),
  .Rez(Rez),
  .Pc_plus(r3l3_PCplus),
  .Mem_data(Mem_data),
  .Imm(r3l3_imm),
  .Rd(Rd_reg)
);

wire r4l4_PC_load;
wire [31:0] r4l4_Rez;

wire [255:0] r4_in;
wire [255:0] r4_out;
assign r4_in = {Rez, l3r4_PC_load, 223'h0};
wire [222:0] r4_null;
assign {r4l4_Rez, r4l4_PC_load, r4_null} = r4_out;

L4 - запись нового значения в программный счётчик.

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

(рис 7.9) Запись нового значения в программный счётчик
wire r4l4_PC_load;
wire [31:0] r4l4_Rez;

wire [255:0] r4_in;
wire [255:0] r4_out;
assign r7_in = {Rez, l3r4_PC_load, 223'h0};
wire [222:0] r4_null;
assign {r4l4_Rez, r4l4_PC_load, r4_null} = r4_out;

rv_r_reg R4(
  .clk(clk),
  .rst_n(rst),
  //.en(en_level[6]),
  .en(1'b1),
  .r_in(r4_in),
  .r_out(r4_out)
);

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

`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]),
  .en(1'b1),
  .pc_load(r4l4_PC_load),
  .pc_next(r4l4_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]),
  .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(32'h0), //(r1l1_PC),
  .i_addr(r1l1_PC>>2),
  .code_out(l1r2_iw),
  .d_addr(Rez>>2),
  .d_out(Mem_data),
  .d_in(Rs2_reg),
  .we(r3l3_mem_wr),
  .en(1'b1)
);
// 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]),
  .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()
);
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]),
  .en(1'b1),
  .r_in(r3_in),
  .r_out(r3_out)
);
//L3_R4
wire [31:0] Rs1_reg;
wire [31:0] Rs2_reg;
wire [31:0] CSR_mux;

rv_reg_file reg_file(   // register's file
  .clk(clk),
  .rs1(r3l3_rs1),
  .rs2(r3l3_rs2),
  .rd(r3l3_rd),
  .Rs1_out(Rs1_reg),
  .Rs2_out(Rs2_reg),
  .Rd_input(Rd_reg),
  .we(r3l3_rd_wr),
  .en(1'b1)
);
rv_csr csr(
  .clk(clk),
  .csr_addr(r3l3_imm[11:0]),
  .csr_in(Rez),
  .csr_out(CSR_mux),
  .csr_wr(r3l3_csr_wr),
  .en(1'b1)
);
wire [31:0] Op1;
wire [31:0] Op2;
rv_ops_mux ops_mux(
  .opcode(r3l3_op),
  .funct3(r3l3_fn3),
  .funct7(r3l3_fn7),
  .Rs1(Rs1_reg),
  .Rs2(Rs2_reg),
  .imm(r3l3_imm),
  .PC(r3l3_PC),
  .CSR(CSR_mux),
  .Op1(Op1),
  .Op2(Op2)
);
rv_cmp cmp(
  .opcode(r3l3_op),
  .funct3(r3l3_fn3),
  .Rs1(Rs1_reg),
  .Rs2(Rs2_reg),
  .pc_new(l3r4_PC_load)
);
wire [31:0] Rez;
wire [31:0] Rd_reg;
wire [31:0] Mem_data;
rv_alu_v alu_v(
  .opcode(r3l3_op),
  .funct3(r3l3_fn3),
  .funct7(r3l3_fn7),
  .Op1(Op1),
  .Op2(Op2),
  .Rez(Rez)
);

rv_rez_mux rez_mux(
  .opcode(r3l3_op),
  .funct3(r3l3_fn3),
  .funct7(r3l3_fn7),
  .Rez(Rez),
  .Pc_plus(r3l3_PCplus),
  .Mem_data(Mem_data),
  .Imm(r3l3_imm),
  .Rd(Rd_reg)
);

wire r4l4_PC_load;
wire [31:0] r4l4_Rez;

wire [255:0] r4_in;
wire [255:0] r4_out;
assign r4_in = {Rez, l3r4_PC_load, 223'h0};
wire [222:0] r4_null;
assign {r4l4_Rez, r4l4_PC_load, r4_null} = r4_out;

rv_r_reg R4(
  .clk(clk),
  .rst_n(rst),
  .en(1'b1),
  .r_in(r4_in),
  .r_out(r4_out)
);

Endmodule

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

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

(рис 7.10) Тестовая программа в симуляторе RARS (рис 7.11) Временные диаграммы (рис 7.12) Тестовая программа в симуляторе RARS (рис 7.13) Временные диаграммы тестирования работы варианта конвейера

Из всего этого следует, что необходимо как-то разрешать данную ситуацию. Варианты могут быть следующие.

  • Немного модифицируем логику выборки команд так, чтобы при обнаружении команд перехода (любого) запрещается дальнейшая загрузка команд в конвейер (это можно сделать даже на первом этапе. Но это приведет к тому, что данные команды в любом случае будут выполняться несколько тактов, вне зависимости от того, выполняется условие перехода или нет.
  • Можно добавить опцию сброса конвейера при обнаружении перехода - например все этапы заменяем на выполнение операции "nop" - ее роль в архитектуре RISC-V исполняет команда " addi x0,x0,0" (как один из вариантов).
  • Попытаемся пойти вторым путём, так как первый действительно порождает много циклов простоя.

    Немного модифицируется дешифратор команд - вводим дополнительный входной сигнал "сброса конвейера", по которому дешифратор при любом входном слове выдает комбинацию сигналов, соответствующих "nop" (в наборе команд RV32I nop соответствует, например, команда сложения add x0,x0,x0):

    module rv_desh
    ( input [31:0] inst,
      output reg [6:0] opcode,
      output reg [4:0] rs1,
      output reg rs1_en,
      output reg [4:0] rs2,
      output reg rs2_en,
      output reg [4:0] rd,
      output reg rd_wr,
      output reg [2:0] funct3,
      output reg f3_en,
      output reg [6:0] funct7,
      output reg f7_en,
      output reg mem_en,
      output reg mem_wr,
      output reg csr_en,
      output reg csr_wr,
      input pipe_rst,
      output reg pc_load
    );
    
    always @ *
    begin
        if (pipe_rst) begin
            opcode[6:0] <= 7'b0010011;
            rs1 <= 5'h0;
            rs2 <= 5'h0;
            rd <= 5'h0;
            funct3 <= 3'b0;
            funct7 <= 3'b0;
            end
        else begin
            opcode[6:0] <= inst[6:0];
            rs1 <= inst[19:15];
            rs2 <= inst[24:20];
            rd <= inst[11:7];
            funct3 <= inst[14:12];
            funct7 <= inst[31:25];
        end
    end
    
    always @ *
    begin
      case (opcode)
        //I-type
        7'b00000_11 : begin // load data from mem
            $display("LOAD");
            rs1_en <= 1'b1;
            rs2_en <= 1'b1;
            rd_wr <= 1'b1;
            f3_en <= 1'b1;
            f7_en <= 1'b0;
            mem_en <= 1'b1;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b0;
            end
        7'b00011_11 : begin // fence
            $display("fence");
            rs1_en <= 1'b1;
            rs2_en <= 1'b1;
            rd_wr <= 1'b1;
            f3_en <= 1'b1;
            f7_en <= 1'b0;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b0;
            end
        7'b00100_11 : begin // reg with immediate operations
            $display("OP-IMM");
            rs1_en <= 1'b1;
            rs2_en <= 1'b1;
            rd_wr <= 1'b1;
            f3_en <= 1'b1;
            f7_en <= 1'b1;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b0;
            end
        7'b00100_11 : begin // reg with reg operations
            $display("OP-reg");
            rs1_en <= 1'b1;
            rs2_en <= 1'b1;
            rd_wr <= 1'b1;
            f3_en <= 1'b1;
            f7_en <= 1'b1;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b0;
            end
        7'b11100_11 : begin // mret - system return
            $display("RET_SYST");
            rs1_en <= 1'b0;
            rs2_en <= 1'b0;
            rd_wr <= 1'b0;
            f3_en <= 1'b1;
            f7_en <= 1'b0;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            if (funct3 == 3'b000) begin
                csr_en <= 1'b0;
                csr_wr <= 1'b0; 
            end 
            else begin  // operations with CSR 
                csr_en <= 1'b1;
                csr_wr <= 1'b1; 
            end 
            pc_load <= 1'b0;        
            end
        7'b11001_11 : begin // Relative (rs1) jump and link in register
            $display("JALR");
            rs1_en <= 1'b1;
            rs2_en <= 1'b0;
            rd_wr <= 1'b1;
            f3_en <= 1'b0;
            f7_en <= 1'b0;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b1;
            end
        //J-type
        7'b11011_11 : begin // pc relative jump and link in register
            $display("JAL");
            rs1_en <= 1'b0;
            rs2_en <= 1'b0;
            rd_wr <= 1'b1;
            f3_en <= 1'b0;
            f7_en <= 1'b0;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b1;
            end
        //S-type
        7'b01000_11 : begin // store register value in memory
            $display("Store");
            rs1_en <= 1'b1;
            rs2_en <= 1'b1;
            rd_wr <= 1'b0;
            f3_en <= 1'b1;
            f7_en <= 1'b0;
            mem_en <= 1'b1;
            mem_wr <= 1'b1;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b0;
            end
        //U-type
        7'b01101_11 : begin // load upper immediate
            $display("LUI");
            rs1_en <= 1'b0;
            rs2_en <= 1'b0;
            rd_wr <= 1'b1;
            f3_en <= 1'b0;
            f7_en <= 1'b0;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b0;
            end
        7'b00101_11 : begin // add upper immediate to PC
            $display("AUIPC");
            rs1_en <= 1'b0;
            rs2_en <= 1'b0;
            rd_wr <= 1'b1;
            f3_en <= 1'b0;
            f7_en <= 1'b0;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b0;
            end
        //B-type
        7'b11000_11 : begin // conditional PC relative branch - PC+imm
            $display("BRANCH");
            rs1_en <= 1'b1;
            rs2_en <= 1'b1;
            rd_wr <= 1'b0;
            f3_en <= 1'b1;
            f7_en <= 1'b0;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b1; // must be 'AND' with compatator output signal
            end
        default: begin
            $display ("default");
            rs1_en <= 1'b0;
            rs2_en <= 1'b0;
            rd_wr <= 1'b0;
            f3_en <= 1'b0;
            f7_en <= 1'b0;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b0;
            end
      endcase
    end
    endmodule
    

    Вводим биты сброса конвейера в сам конвейер.

    Этап L0:

    wire [255:0] r1_in;
    assign r1_in = {l3r4_PC_load, l0r1_PC, l0r1_PCplus, 191'h0}; 
    wire [255:0] r1_out;
    wire [190:0] r1_null;
    assign {r1l1_pipe_rst, 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)
    );
    

    Этап L1:

    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 = {r1l1_pipe_rst, l1r2_iw, r1l1_PC, r1l1_PCplus, 159'h0};
    wire [158:0] r2_null;
    assign {r2l2_pipe_rst, 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)
    );
    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;
    

    В случае опознавания перехода необходимо, чтобы команды, уже попавшие в конвейер, игнорировались. Суммирующий сигнал pipe_rst для дешифратора команд объединятся по "or" из несколки сигналов, чтобы охватить "заранее" эффекты от перехода.

    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),
      .pipe_rst(r2l2_pipe_rst | l3r4_PC_load | r1l1_pipe_rst),
      .pc_load()
    );
    

    Финальная структура процессора представлена на рис.7.14

    (рис 7.14) Итоговый вариант конвейеризированного процессора

    Временные диаграммы приведены на рис.7.15 - видно, что блокирование выполнения операций предыдущих этапов конвейера, равно как и переход - работают штатно.

    (рис 7.15) Временные диаграммы тестирования работы конвейера

    Финальный код процессорного ядра:

    `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(1'b1),
      .pc_load(l3r4_PC_load),
      .pc_next(Rez),
      .pc(l0r1_PC),
      .pc_plus(l0r1_PCplus)
    );
    wire [255:0] r1_in;
    assign r1_in = {l3r4_PC_load, l0r1_PC, l0r1_PCplus, 191'h0}; 
    wire [255:0] r1_out;
    wire [190:0] r1_null;
    assign {r1l1_pipe_rst, 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(32'h0), //(r1l1_PC),
      .i_addr(r1l1_PC>>2),
      .code_out(l1r2_iw),
      .d_addr(Rez>>2),
      .d_out(Mem_data),
      .d_in(Rs2_reg),
      .we(r3l3_mem_wr),
      .en(1'b1)
    );
    // 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;
    
    //assign r2_in = {l3r4_PC_load, l1r2_iw, r1l1_PC, r1l1_PCplus, 159'h0};
    assign r2_in = {r1l1_pipe_rst, l1r2_iw, r1l1_PC, r1l1_PCplus, 159'h0};
    wire [158:0] r2_null;
    assign {r2l2_pipe_rst, 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),
      .pipe_rst(r2l2_pipe_rst | l3r4_PC_load | r1l1_pipe_rst),
      .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(1'b1),
      .r_in(r3_in),
      .r_out(r3_out)
    );
    
    wire [31:0] Rs1_reg;
    wire [31:0] Rs2_reg;
    wire [31:0] CSR_mux;
    
    rv_reg_file reg_file(   // register's file
      .clk(~clk),
      .rs1(r3l3_rs1),
      .rs2(r3l3_rs2),
      .rd(r3l3_rd),
      .Rs1_out(Rs1_reg),
      .Rs2_out(Rs2_reg),
      .Rd_input(Rd_reg),
      .we(r3l3_rd_wr),
      .en(1'b1)
    );
    rv_csr csr(
      .clk(~clk),
      .csr_addr(r3l3_imm[11:0]),
      .csr_in(Rez),
      .csr_out(CSR_mux),
      .csr_wr(r3l3_csr_wr),
      .en(1'b1)
    );
    wire [31:0] Op1;
    wire [31:0] Op2;
    rv_ops_mux ops_mux(
      .opcode(r3l3_op),
      .funct3(r3l3_fn3),
      .funct7(r3l3_fn7),
      .Rs1(Rs1_reg),
      .Rs2(Rs2_reg),
      .imm(r3l3_imm),
      .PC(r3l3_PC),
      .CSR(CSR_mux),
      .Op1(Op1),
      .Op2(Op2)
    );
    rv_cmp cmp(
      .opcode(r3l3_op),
      .funct3(r3l3_fn3),
      .Rs1(Rs1_reg),
      .Rs2(Rs2_reg),
      .pc_new(l3r4_PC_load)
    );
    wire [31:0] Rez;
    wire [31:0] Rd_reg;
    wire [31:0] Mem_data;
    rv_alu_v alu_v(
      .opcode(r3l3_op),
      .funct3(r3l3_fn3),
      .funct7(r3l3_fn7),
      .Op1(Op1),
      .Op2(Op2),
      .Rez(Rez)
    );
    
    rv_rez_mux rez_mux(
      .opcode(r3l3_op),
      .funct3(r3l3_fn3),
      .funct7(r3l3_fn7),
      .Rez(Rez),
      .Pc_plus(r3l3_PCplus),
      .Mem_data(Mem_data),
      .Imm(r3l3_imm),
      .Rd(Rd_reg)
    );
    
    wire r4l4_PC_load;
    wire [31:0] r4l4_Rez;
    
    wire [255:0] r4_in;
    wire [255:0] r4_out;
    assign r4_in = {Rez, l3r4_PC_load, 223'h0};
    wire [222:0] r4_null;
    assign {r4l4_Rez, r4l4_PC_load, r4_null} = r4_out;
    
    rv_r_reg R4(
      .clk(clk),
      .rst_n(rst),
      .en(1'b1),
      .r_in(r4_in),
      .r_out(r4_out)
    );
    
    endmodule
    
    Страницы:

    Несложно заметить, что предложенную микроархитектуру многотактного процессора (рис.7.1) можно относительно просто превратить в конвейеризированную. Для этого достаточно (вот сейчас будет страшно - а точно ли?) разрешить работу всех промежуточных регистров Rx, а также блоков программного счетчика, файл-регистра, регистров специальных функций, и памяти на запись.

    (рис 7.1) Структурная схема многотактного процессора

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

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

    (рис 7.2) Временные диаграммы конвейеризованного варианта

    Смотря на рис.7.2 можно с одной стороны порадоваться - выборка инструкций действительно конвейеризовалась, но вот выполнение последовательности команд наталкивается на неопределенность при выборке операндов из файл-регистра.

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

    (рис 7.3) "Разбор" причин нарушения корректности работы

    Следовательно, необходимо немного пересмотреть вариант разбиения конвейера по этапам для корректной работы.

    Микроархитектурные блоки процессора оставляем пока как и были:

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

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

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

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

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

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

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

    rv_alu_v - АЛУ;

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

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

    Новый вариант разбиения по этапам:

    L0 - выборка текущего значения программного счетчика (точнее пары значений - PC и РС+4).

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

    L2 - дешифрация инструкции - выделение адресов операндов, типа инструкции, формирование управляющих сигналов.

    L3 - вычисления - выборка операндов из файл-регистров, работа АЛУ, чтение/запись данных из оперативной памяти, запись данных в файл-регистры.

    L4 - запись нового значения в программный счётчик.

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

    Структурная схема конвейеризированного варианта процессора представлена на рис.7.4.

    (рис 7.4) Структурная схема конвейеризированного варианта процессора

    Этапы L0-L2 остаются пока без изменений.

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

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

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

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

    (рис 7.5) Выборка текущего значения программного счетчика
    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),
      .pc_load(r6l7_PC_load),
      .pc_next(l6r7_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;
      assign r1_out = {r1l1_PC, r1l1_PCplus, 192'h0}; 
    
    rv_r_reg R1(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[0]),
      .r_in(r1_in),
      .r_out(r1_out)
    );
    
    (рис 7.6) Выборка слова-инструкции из памяти
    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),
      .code_out(l1r2_iw),
      .d_addr(r5l5_Rez),
      .d_out(l5r6_Mem_data),
      .d_in(r5l5_Rs2_reg),
      .we(r5l5_mem_wr)
    );
    // 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_out = {r2l2_iw, r2l2_PC, r2l2_PCplus, 160'h0};
    
    rv_r_reg R2(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[1]),
      .r_in(r2_in),
      .r_out(r2_out)
    );
    

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

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

    (рис 7.7) Дешифровка инструкции
    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};
    assign r3_out = {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, 125'h0};
    
    rv_r_reg R3(
      .clk(clk),
      .rst_n(rst),
      .en(en_level[2]),
      .r_in(r3_in),
      .r_out(r3_out)
    );
    

    L3 - вычисления

    Адреса регистров-операндов подаются на адресные входы файл-регистров (основного и регистров специальных функций).

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

    Также выходы файл-регистра Rs1, Rs2 совместно с константой и значением регистра CSR и программного счетчика подается на выход мультиплексора операндов, выходы его - на входы операндов АЛУ. Выходные сигналы АЛУ поступают на вход блока CSR, на вход мультиплексора операндов для записи в файл-регистр (по адресу rd), адресуют память (ОЗУ).

    В регистр R4 записывается новое значение программного счетчика и сигнал разрешения записи в него.

    (рис 7.8) Структура логики этапа L3 - вычисления

    Описание логики этапа "вычисления":

    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)
    );
    wire [31:0] Rs1_reg;
    wire [31:0] Rs2_reg;
    wire [31:0] CSR_mux;
    
    rv_reg_file reg_file(   // register's file
      .clk(~clk),
      .rs1(r3l3_rs1),
      .rs2(r3l3_rs2),
      .rd(r3l3_rd),
      .Rs1_out(Rs1_reg),
      .Rs2_out(Rs2_reg),
      .Rd_input(Rd_reg),
      .we(r3l3_rd_wr),
      .en(1'b1)
    );
    rv_csr csr(
      .clk(~clk),
      .csr_addr(r3l3_imm[11:0]),
      .csr_in(Rez),
      .csr_out(CSR_mux),
      .csr_wr(r3l3_csr_wr),
      .en(1'b1)
    );
    wire [31:0] Op1;
    wire [31:0] Op2;
    rv_ops_mux ops_mux(
      .opcode(r3l3_op),
      .funct3(r3l3_fn3),
      .funct7(r3l3_fn7),
      .Rs1(Rs1_reg),
      .Rs2(Rs2_reg),
      .imm(r3l3_imm),
      .PC(r3l3_PC),
      .CSR(CSR_mux),
      .Op1(Op1),
      .Op2(Op2)
    );
    rv_cmp cmp(
      .opcode(r3l3_op),
      .funct3(r3l3_fn3),
      .Rs1(Rs1_reg),
      .Rs2(Rs2_reg),
      .pc_new(l3r4_PC_load)
    );
    wire [31:0] Rez;
    wire [31:0] Rd_reg;
    wire [31:0] Mem_data;
    rv_alu_v alu_v(
      .opcode(r3l3_op),
      .funct3(r3l3_fn3),
      .funct7(r3l3_fn7),
      .Op1(Op1),
      .Op2(Op2),
      .Rez(Rez)
    );
    
    rv_rez_mux rez_mux(
      .opcode(r3l3_op),
      .funct3(r3l3_fn3),
      .funct7(r3l3_fn7),
      .Rez(Rez),
      .Pc_plus(r3l3_PCplus),
      .Mem_data(Mem_data),
      .Imm(r3l3_imm),
      .Rd(Rd_reg)
    );
    
    wire r4l4_PC_load;
    wire [31:0] r4l4_Rez;
    
    wire [255:0] r4_in;
    wire [255:0] r4_out;
    assign r4_in = {Rez, l3r4_PC_load, 223'h0};
    wire [222:0] r4_null;
    assign {r4l4_Rez, r4l4_PC_load, r4_null} = r4_out;
    

    L4 - запись нового значения в программный счётчик.

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

    (рис 7.9) Запись нового значения в программный счётчик
    wire r4l4_PC_load;
    wire [31:0] r4l4_Rez;
    
    wire [255:0] r4_in;
    wire [255:0] r4_out;
    assign r7_in = {Rez, l3r4_PC_load, 223'h0};
    wire [222:0] r4_null;
    assign {r4l4_Rez, r4l4_PC_load, r4_null} = r4_out;
    
    rv_r_reg R4(
      .clk(clk),
      .rst_n(rst),
      //.en(en_level[6]),
      .en(1'b1),
      .r_in(r4_in),
      .r_out(r4_out)
    );
    

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

    `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]),
      .en(1'b1),
      .pc_load(r4l4_PC_load),
      .pc_next(r4l4_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]),
      .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(32'h0), //(r1l1_PC),
      .i_addr(r1l1_PC>>2),
      .code_out(l1r2_iw),
      .d_addr(Rez>>2),
      .d_out(Mem_data),
      .d_in(Rs2_reg),
      .we(r3l3_mem_wr),
      .en(1'b1)
    );
    // 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]),
      .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()
    );
    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]),
      .en(1'b1),
      .r_in(r3_in),
      .r_out(r3_out)
    );
    //L3_R4
    wire [31:0] Rs1_reg;
    wire [31:0] Rs2_reg;
    wire [31:0] CSR_mux;
    
    rv_reg_file reg_file(   // register's file
      .clk(clk),
      .rs1(r3l3_rs1),
      .rs2(r3l3_rs2),
      .rd(r3l3_rd),
      .Rs1_out(Rs1_reg),
      .Rs2_out(Rs2_reg),
      .Rd_input(Rd_reg),
      .we(r3l3_rd_wr),
      .en(1'b1)
    );
    rv_csr csr(
      .clk(clk),
      .csr_addr(r3l3_imm[11:0]),
      .csr_in(Rez),
      .csr_out(CSR_mux),
      .csr_wr(r3l3_csr_wr),
      .en(1'b1)
    );
    wire [31:0] Op1;
    wire [31:0] Op2;
    rv_ops_mux ops_mux(
      .opcode(r3l3_op),
      .funct3(r3l3_fn3),
      .funct7(r3l3_fn7),
      .Rs1(Rs1_reg),
      .Rs2(Rs2_reg),
      .imm(r3l3_imm),
      .PC(r3l3_PC),
      .CSR(CSR_mux),
      .Op1(Op1),
      .Op2(Op2)
    );
    rv_cmp cmp(
      .opcode(r3l3_op),
      .funct3(r3l3_fn3),
      .Rs1(Rs1_reg),
      .Rs2(Rs2_reg),
      .pc_new(l3r4_PC_load)
    );
    wire [31:0] Rez;
    wire [31:0] Rd_reg;
    wire [31:0] Mem_data;
    rv_alu_v alu_v(
      .opcode(r3l3_op),
      .funct3(r3l3_fn3),
      .funct7(r3l3_fn7),
      .Op1(Op1),
      .Op2(Op2),
      .Rez(Rez)
    );
    
    rv_rez_mux rez_mux(
      .opcode(r3l3_op),
      .funct3(r3l3_fn3),
      .funct7(r3l3_fn7),
      .Rez(Rez),
      .Pc_plus(r3l3_PCplus),
      .Mem_data(Mem_data),
      .Imm(r3l3_imm),
      .Rd(Rd_reg)
    );
    
    wire r4l4_PC_load;
    wire [31:0] r4l4_Rez;
    
    wire [255:0] r4_in;
    wire [255:0] r4_out;
    assign r4_in = {Rez, l3r4_PC_load, 223'h0};
    wire [222:0] r4_null;
    assign {r4l4_Rez, r4l4_PC_load, r4_null} = r4_out;
    
    rv_r_reg R4(
      .clk(clk),
      .rst_n(rst),
      .en(1'b1),
      .r_in(r4_in),
      .r_out(r4_out)
    );
    
    Endmodule
    

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

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

    (рис 7.10) Тестовая программа в симуляторе RARS (рис 7.11) Временные диаграммы (рис 7.12) Тестовая программа в симуляторе RARS (рис 7.13) Временные диаграммы тестирования работы варианта конвейера

    Из всего этого следует, что необходимо как-то разрешать данную ситуацию. Варианты могут быть следующие.

  • Немного модифицируем логику выборки команд так, чтобы при обнаружении команд перехода (любого) запрещается дальнейшая загрузка команд в конвейер (это можно сделать даже на первом этапе. Но это приведет к тому, что данные команды в любом случае будут выполняться несколько тактов, вне зависимости от того, выполняется условие перехода или нет.
  • Можно добавить опцию сброса конвейера при обнаружении перехода - например все этапы заменяем на выполнение операции "nop" - ее роль в архитектуре RISC-V исполняет команда " addi x0,x0,0" (как один из вариантов).
  • Попытаемся пойти вторым путём, так как первый действительно порождает много циклов простоя.

    Немного модифицируется дешифратор команд - вводим дополнительный входной сигнал "сброса конвейера", по которому дешифратор при любом входном слове выдает комбинацию сигналов, соответствующих "nop" (в наборе команд RV32I nop соответствует, например, команда сложения add x0,x0,x0):

    module rv_desh
    ( input [31:0] inst,
      output reg [6:0] opcode,
      output reg [4:0] rs1,
      output reg rs1_en,
      output reg [4:0] rs2,
      output reg rs2_en,
      output reg [4:0] rd,
      output reg rd_wr,
      output reg [2:0] funct3,
      output reg f3_en,
      output reg [6:0] funct7,
      output reg f7_en,
      output reg mem_en,
      output reg mem_wr,
      output reg csr_en,
      output reg csr_wr,
      input pipe_rst,
      output reg pc_load
    );
    
    always @ *
    begin
        if (pipe_rst) begin
            opcode[6:0] <= 7'b0010011;
            rs1 <= 5'h0;
            rs2 <= 5'h0;
            rd <= 5'h0;
            funct3 <= 3'b0;
            funct7 <= 3'b0;
            end
        else begin
            opcode[6:0] <= inst[6:0];
            rs1 <= inst[19:15];
            rs2 <= inst[24:20];
            rd <= inst[11:7];
            funct3 <= inst[14:12];
            funct7 <= inst[31:25];
        end
    end
    
    always @ *
    begin
      case (opcode)
        //I-type
        7'b00000_11 : begin // load data from mem
            $display("LOAD");
            rs1_en <= 1'b1;
            rs2_en <= 1'b1;
            rd_wr <= 1'b1;
            f3_en <= 1'b1;
            f7_en <= 1'b0;
            mem_en <= 1'b1;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b0;
            end
        7'b00011_11 : begin // fence
            $display("fence");
            rs1_en <= 1'b1;
            rs2_en <= 1'b1;
            rd_wr <= 1'b1;
            f3_en <= 1'b1;
            f7_en <= 1'b0;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b0;
            end
        7'b00100_11 : begin // reg with immediate operations
            $display("OP-IMM");
            rs1_en <= 1'b1;
            rs2_en <= 1'b1;
            rd_wr <= 1'b1;
            f3_en <= 1'b1;
            f7_en <= 1'b1;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b0;
            end
        7'b00100_11 : begin // reg with reg operations
            $display("OP-reg");
            rs1_en <= 1'b1;
            rs2_en <= 1'b1;
            rd_wr <= 1'b1;
            f3_en <= 1'b1;
            f7_en <= 1'b1;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b0;
            end
        7'b11100_11 : begin // mret - system return
            $display("RET_SYST");
            rs1_en <= 1'b0;
            rs2_en <= 1'b0;
            rd_wr <= 1'b0;
            f3_en <= 1'b1;
            f7_en <= 1'b0;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            if (funct3 == 3'b000) begin
                csr_en <= 1'b0;
                csr_wr <= 1'b0; 
            end 
            else begin  // operations with CSR 
                csr_en <= 1'b1;
                csr_wr <= 1'b1; 
            end 
            pc_load <= 1'b0;        
            end
        7'b11001_11 : begin // Relative (rs1) jump and link in register
            $display("JALR");
            rs1_en <= 1'b1;
            rs2_en <= 1'b0;
            rd_wr <= 1'b1;
            f3_en <= 1'b0;
            f7_en <= 1'b0;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b1;
            end
        //J-type
        7'b11011_11 : begin // pc relative jump and link in register
            $display("JAL");
            rs1_en <= 1'b0;
            rs2_en <= 1'b0;
            rd_wr <= 1'b1;
            f3_en <= 1'b0;
            f7_en <= 1'b0;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b1;
            end
        //S-type
        7'b01000_11 : begin // store register value in memory
            $display("Store");
            rs1_en <= 1'b1;
            rs2_en <= 1'b1;
            rd_wr <= 1'b0;
            f3_en <= 1'b1;
            f7_en <= 1'b0;
            mem_en <= 1'b1;
            mem_wr <= 1'b1;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b0;
            end
        //U-type
        7'b01101_11 : begin // load upper immediate
            $display("LUI");
            rs1_en <= 1'b0;
            rs2_en <= 1'b0;
            rd_wr <= 1'b1;
            f3_en <= 1'b0;
            f7_en <= 1'b0;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b0;
            end
        7'b00101_11 : begin // add upper immediate to PC
            $display("AUIPC");
            rs1_en <= 1'b0;
            rs2_en <= 1'b0;
            rd_wr <= 1'b1;
            f3_en <= 1'b0;
            f7_en <= 1'b0;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b0;
            end
        //B-type
        7'b11000_11 : begin // conditional PC relative branch - PC+imm
            $display("BRANCH");
            rs1_en <= 1'b1;
            rs2_en <= 1'b1;
            rd_wr <= 1'b0;
            f3_en <= 1'b1;
            f7_en <= 1'b0;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b1; // must be 'AND' with compatator output signal
            end
        default: begin
            $display ("default");
            rs1_en <= 1'b0;
            rs2_en <= 1'b0;
            rd_wr <= 1'b0;
            f3_en <= 1'b0;
            f7_en <= 1'b0;
            mem_en <= 1'b0;
            mem_wr <= 1'b0;
            csr_en <= 1'b0;
            csr_wr <= 1'b0;
            pc_load <= 1'b0;
            end
      endcase
    end
    endmodule
    

    Вводим биты сброса конвейера в сам конвейер.

    Этап L0:

    wire [255:0] r1_in;
    assign r1_in = {l3r4_PC_load, l0r1_PC, l0r1_PCplus, 191'h0}; 
    wire [255:0] r1_out;
    wire [190:0] r1_null;
    assign {r1l1_pipe_rst, 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)
    );
    

    Этап L1:

    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 = {r1l1_pipe_rst, l1r2_iw, r1l1_PC, r1l1_PCplus, 159'h0};
    wire [158:0] r2_null;
    assign {r2l2_pipe_rst, 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)
    );
    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;
    

    В случае опознавания перехода необходимо, чтобы команды, уже попавшие в конвейер, игнорировались. Суммирующий сигнал pipe_rst для дешифратора команд объединятся по "or" из несколки сигналов, чтобы охватить "заранее" эффекты от перехода.

    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),
      .pipe_rst(r2l2_pipe_rst | l3r4_PC_load | r1l1_pipe_rst),
      .pc_load()
    );
    

    Финальная структура процессора представлена на рис.7.14

    (рис 7.14) Итоговый вариант конвейеризированного процессора

    Временные диаграммы приведены на рис.7.15 - видно, что блокирование выполнения операций предыдущих этапов конвейера, равно как и переход - работают штатно.

    (рис 7.15) Временные диаграммы тестирования работы конвейера

    Финальный код процессорного ядра:

    `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(1'b1),
      .pc_load(l3r4_PC_load),
      .pc_next(Rez),
      .pc(l0r1_PC),
      .pc_plus(l0r1_PCplus)
    );
    wire [255:0] r1_in;
    assign r1_in = {l3r4_PC_load, l0r1_PC, l0r1_PCplus, 191'h0}; 
    wire [255:0] r1_out;
    wire [190:0] r1_null;
    assign {r1l1_pipe_rst, 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(32'h0), //(r1l1_PC),
      .i_addr(r1l1_PC>>2),
      .code_out(l1r2_iw),
      .d_addr(Rez>>2),
      .d_out(Mem_data),
      .d_in(Rs2_reg),
      .we(r3l3_mem_wr),
      .en(1'b1)
    );
    // 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;
    
    //assign r2_in = {l3r4_PC_load, l1r2_iw, r1l1_PC, r1l1_PCplus, 159'h0};
    assign r2_in = {r1l1_pipe_rst, l1r2_iw, r1l1_PC, r1l1_PCplus, 159'h0};
    wire [158:0] r2_null;
    assign {r2l2_pipe_rst, 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),
      .pipe_rst(r2l2_pipe_rst | l3r4_PC_load | r1l1_pipe_rst),
      .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(1'b1),
      .r_in(r3_in),
      .r_out(r3_out)
    );
    
    wire [31:0] Rs1_reg;
    wire [31:0] Rs2_reg;
    wire [31:0] CSR_mux;
    
    rv_reg_file reg_file(   // register's file
      .clk(~clk),
      .rs1(r3l3_rs1),
      .rs2(r3l3_rs2),
      .rd(r3l3_rd),
      .Rs1_out(Rs1_reg),
      .Rs2_out(Rs2_reg),
      .Rd_input(Rd_reg),
      .we(r3l3_rd_wr),
      .en(1'b1)
    );
    rv_csr csr(
      .clk(~clk),
      .csr_addr(r3l3_imm[11:0]),
      .csr_in(Rez),
      .csr_out(CSR_mux),
      .csr_wr(r3l3_csr_wr),
      .en(1'b1)
    );
    wire [31:0] Op1;
    wire [31:0] Op2;
    rv_ops_mux ops_mux(
      .opcode(r3l3_op),
      .funct3(r3l3_fn3),
      .funct7(r3l3_fn7),
      .Rs1(Rs1_reg),
      .Rs2(Rs2_reg),
      .imm(r3l3_imm),
      .PC(r3l3_PC),
      .CSR(CSR_mux),
      .Op1(Op1),
      .Op2(Op2)
    );
    rv_cmp cmp(
      .opcode(r3l3_op),
      .funct3(r3l3_fn3),
      .Rs1(Rs1_reg),
      .Rs2(Rs2_reg),
      .pc_new(l3r4_PC_load)
    );
    wire [31:0] Rez;
    wire [31:0] Rd_reg;
    wire [31:0] Mem_data;
    rv_alu_v alu_v(
      .opcode(r3l3_op),
      .funct3(r3l3_fn3),
      .funct7(r3l3_fn7),
      .Op1(Op1),
      .Op2(Op2),
      .Rez(Rez)
    );
    
    rv_rez_mux rez_mux(
      .opcode(r3l3_op),
      .funct3(r3l3_fn3),
      .funct7(r3l3_fn7),
      .Rez(Rez),
      .Pc_plus(r3l3_PCplus),
      .Mem_data(Mem_data),
      .Imm(r3l3_imm),
      .Rd(Rd_reg)
    );
    
    wire r4l4_PC_load;
    wire [31:0] r4l4_Rez;
    
    wire [255:0] r4_in;
    wire [255:0] r4_out;
    assign r4_in = {Rez, l3r4_PC_load, 223'h0};
    wire [222:0] r4_null;
    assign {r4l4_Rez, r4l4_PC_load, r4_null} = r4_out;
    
    rv_r_reg R4(
      .clk(clk),
      .rst_n(rst),
      .en(1'b1),
      .r_in(r4_in),
      .r_out(r4_out)
    );
    
    endmodule
    
    Вернуться к учебному плану