Построение однотактного простейшего ядра RISC-V
Введение
Приступая к реализации собственно процессорного узла для определенного набора команд, мы ступаем в область создания т.н. микроархитектуры процессора - его уникальной для каждой реализации внутренней структуры. По факту уже, разбирая вариант построения декодера команд в предыдущей части, мы уже начали работу над микроархитектурой.
Вернемся к основным архитектурным блокам процессора:
- регистровый файл/файл-регистр (31 активный регистр, плюс "регистр ноля", два выходных порта, один порт на запись);
- программный счётчик (PC);
- блок регистров специального назначения (CSR);
- арифметико-логическое устройство (АЛУ);
- блок дешифратора команд.
Дополнительными блоками не входящими в архитектурные блоки процессорного ядра, но необходимые для работы:
- память программ;
- память данных (оперативная память).
Согласно теории - однотактная микроархитектура подразумевает выполнение команды процессора за один такт. Все операции выполняются за один такт, эта микроархитектура не требует никакого неархитектурного состояния. Все исполнительные узлы процессорного ядра (за исключением, конечно же его регистров, определенных или других элементов с памятью состояния) архитектурой представлены комбинационно-логическими схемами. Минимальная длительность такта при этом определяется временем выполнения самой медленной команды (например, временем распространения сигнала переноса в сумматоре).
Файл-регистр
Файл-регистр данных архитектур устроен довольно просто - особенно для базовых конфигураций архитектур.
Тридцать два - 32-разрядных регистра, два порта на чтение данных (для упрощения делаем их асинхронными), один синхронный порт на запись данных. Регистр файла с адресом 0 - всегда при чтении выдает ноль, на запись не работает.
Файл-регистр принимает адреса регистров-операндов (rs1, rs2), асинхронно выдает содержимое соответствующих регистров на выходные порты. Параллельно при наличии разрешающего сигнала записывает в регистр с адресом rd значение, поданное на вход.
При такой реализации выдача значений регистров производится всегда, безотносительно к операции, соответственно, дальнейшая "судьба" данных регистров, поданных на выходы зависит от последующих узлов и требует аккуратного обращения.
Код файл-регистра:
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 ); // 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 (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
Тест-бенч:
`include "rv_reg_file.v" `timescale 10ns/1ns /* 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 */ module testbench; // input and output test signals reg clk; reg [4:0] rs1; reg [4:0] rs2; reg [4:0] rd; wire [31:0] Rs1_out; wire [31:0] Rs2_out; reg [31:0] Rd_input; reg we; rv_reg_file dut ( clk, rs1, rs2, rd, Rs1_out, Rs2_out, Rd_input, we); integer i; initial begin clk = 1'b0; rs1 = 5'h00; rs2 = 5'h01; rd = 5'h02; we=1'b0; #10; i = 0; rs1 <= #i 5'h00; rs2 <= #i 5'h01; rd <= #i 5'h02; Rd_input <= #i 32'h01; we <= #i 1'b0; i = i + 20; rs1 <= #i 5'h01; rs2 <= #i 5'h01; rd <= #i 5'h01; Rd_input <= #i 32'h77; we <= #i 1'b0; i = i + 20; //we = 1'b1; rs1 <= #i 5'h02; rs2 <= #i 5'h01; rd <= #i 5'h02; Rd_input <= #i 32'h01; we <= #i 1'b0; i = i + 20; //we = 1'b1; rs1 <= #i 5'h03; rs2 <= #i 5'h02; rd <= #i 5'h03; Rd_input <= #i 32'h01; we <= #i 1'b0; i = i + 20; rs1 <= #i 5'h04; rs2 <= #i 5'h04; rd <= #i 5'h04; Rd_input <= #i 32'h01; we <= #i 1'b1; i = i + 20; rs1 <= #i 5'h05; rs2 <= #i 5'h04; rd <= #i 5'h05; Rd_input <= #i 32'h01; we <= #i 1'b0; i = i + 20; //we = 1'b0; rs1 <= #i 5'h00; rs2 <= #i 5'h01; rd <= #i 5'h06; Rd_input <= #i 32'h01; we <= #i 1'b0; i = i + 20; rs1 <= #i 5'h00; rs2 <= #i 5'h01; rd <= #i 5'h07; Rd_input <= #i 32'h01; we <= #i 1'b0; i = i + 20; for(i=0; i < 80; i = i+1) #10 clk = ~clk; end initial $monitor("Rs1=%h Rs2=%h ", Rs2_out, Rs2_out); initial $dumpvars; //iverilog dump init endmodule
АЛУ
Арифметико-логическое устройство является одним из центральных узлов любого процессора. Структура АЛУ и его функциональность определяется ISA, поэтому вернемся к еще раз к базовым операциям RISC-V.
Операции с регистрами и константами (immediate/непосредственными значениями) - тип-I - оперируют с регистрами и непосредственными операндами - арифметико-логические операции, сравнения с константами, загрузкой данных в регистры
funct3 | opcode | I-type | |
---|---|---|---|
Rd = Rs1+imm (NOP encoded as ADDI x0, x0, 0.) | 000 | 0010011 | ADDI |
Rd= 1 if Rs< imm else 0 | 010 | 0010011 | SLTI |
Rd= 1 if Rs< uimm else 0 | 011 | 0010011 | SLTIU |
Rd = rs1 XOR imm | 100 | 0010011 | XORI |
Rd = rs1 OR imm | 110 | 0010011 | ORI |
Rd = rs1 AND imm | 111 | 0010011 | ANDI |
funct7 | funct3 | opcode | R-type | |
---|---|---|---|---|
Rd = Rs1 + Rs2 | 0000000 | 000 | 0110011 | ADD |
Rd = Rs1 - Rs2 | 0100000 | 000 | 0110011 | SUB |
Rd = Rs1 << Rs2 | 0000000 | 001 | 0110011 | SLL |
Rd= 1 if Rs1 < Rs2 else 0 | 0000000 | 010 | 0110011 | SLT |
Rd= 1 if Rs1 < Rs2 else 0(unsign) SLTU rd, x0, rs2 sets rd to 1 if rs2 is not equal to zero |
0000000 | 011 | 0110011 | SLTU |
Rd = Rs1 XOR Rs2 | 0000000 | 100 | 0110011 | XOR |
Rd = Rs1 >> Rs2 | 0000000 | 101 | 0110011 | SRL |
Rd = Rs1 >> Rs2 (sign ext) | 0100000 | 101 | 0110011 | SRA |
Rd = Rs1 OR Rs2 | 0000000 | 110 | 0110011 | OR |
Rd = Rs1 AND Rs2 | 0000000 | 111 | 0110011 | AND |
Rd = Rs1 << imm | 0000000 | 001 | 0010011 | SLLI |
Rd = Rs1 >> imm (0 ext) | 0000000 | 101 | 0010011 | SRLI |
Rd = Rs1 >> imm (sign ext) | 0100000 | 101 | 0010011 | SRAI |