§ Изменение концепции

Ранее я говорил о том, что делаю процессор 8088, основываясь на 8-битной шине данных. Так вот, посмотрел на то, сколько тратится тактов на каждую инструкцию и сильно, очень сильно разочаровался в таком подходе. Мне бы хотелось сделать процессор следующим образом:
  • Шина 32-х битная
  • Инструкции могут быть не только 8086, но и поддерживать 80186
  • Инструкция будет загружаться в "очередь инструкции" каждый раз, считывая до 6 байт
  • Исполнение инструкции не обязательно будет находится в posedge clock, но часть его вынесена в always @*-секцию
  • Некоторые простые инструкции теперь могут выполняться за 1Т
  • Байт modrm будет декодироваться полностью за 1Т
Но я не буду делать это, просто хочу немного отойти от темы и посмотреть, как можно декодировать инструкции еще.

§ Определение длины инструкции

У каждой инструкции есть своя длина. Чтобы определить длину инструкции, достаточно лишь знать ее опкод и, если есть, байт modrm. В таблице ниже я приведу карту базового набора инструкции.
000102030405060708090A0B0C0D0E0F
001212
101212
201212
301212
40
50
602211
701111111111111111
801211
904
A0222212
B01111111122222222
C011212321
D011
E0111111112241
F012

Там, где опкод помечен зеленым, это значит, что за ним идет байт modrm, а значит, что инструкция как минимум содержит в себе два байта, но может и больше, потому что все определяется байтом modrm - там может быть считывание 1-байтного или 2-байтного смещения. В ячейках есть цифры 1 или 2, которые означают размер непосредственного значения (immediate), идущего за опкодом и байтом modrm, если есть. В случае когда нет ни байта modrm, ни immediate, то такой опкод является одиночным и занимает 1 байт.
Для этой цели я создам модуль в файле cpu_isize.v:
1// verilator lint_off WIDTH
2// verilator lint_off CASEX
3// verilator lint_on  CASEINCOMPLETE
4
5module cpu_isize
6(
7    input       [15:0]  op_queue,
8    output reg          op_modrm_byte,
9    output reg          op_modrm_mem,
10    output reg  [1:0]   op_modrm_disp,
11    output reg  [2:0]   op_imm_size
12);
13
14endmodule
Сверху закомментированы указания верилятору о том, что не надо предлагать casez и не проверять несоответствия ширины разрядности.
В качестве входного "аргумента" предлагается op_queue (48 бит или 6 байт), состоящего из опкода и возможного байта modrm, то есть, два байта. Опкод декодируется однозначно, а байт modrm просматривается только тогда, когда он в опкоде есть.
На выходе бит op_modrm_byte сигнализирует о том, что опкод имеет байт modrm, и потому надо будет проверять то, что на выходе op_modrm_disp, там может быть значение от 0 до 2. Независимо от modrm-байта, для каждого опкода также генерируется значение op_imm_size от 0 до 4 — количество байт непосредственного значения. Непосредственным значением также может быть 4-байтный переход по длинному адресу инструкции jmp far segment:offset или call far segment:offset.
Выход op_modrm_mem если 1, то значит, что в качестве операнда используется чтение из памяти.
Код, который расшифровывает наличие байта modrm, достаточно простой:
1
2// Определить наличие байта modrm
3always @* begin
4
5    // Наличие байта ModRM
6    casex (op_queue[7:0])
7    8'b00xx_x0xx, // ALU mrm
8    8'b0110_001x, // BOUND, ARPL
9    8'b0110_10x1, // IMUL
10    8'b1000_xxxx, // GrpArith,TEST,MOV,LEA,POP
11    8'b1100_000x, // GrpShift
12    8'b1100_01xx, // GrpShift
13    8'b1101_00xx, // LES,LDS,MOV
14    8'b1101_1xxx, // ESC, GrpMisc
15    8'b1111_x11x: op_modrm_byte = 1'b1;
16    default:      op_modrm_byte = 1'b0;
17    endcase
18
19end
В самом деле, это весь набор кодов для того, чтобы расшифровать, есть ли байт modrm или нет у опкода, но, опкод должен быть только из базового набора, без 0F-префикса.
Как ранее и говорилось, если у опкода есть байт modrm, то надо проверить, есть ли у него смещения при получении адреса памяти, где будет располагаться операнд.
1op_modrm_disp   = 2'b00;
2op_modrm_mem    = op_modrm_byte && op_queue[15:8] != 3'b11;
3
4// Размер disp [0..2]
5if (op_modrm_byte) begin
6
7    casex (op_queue[15:8])
8    8'b00_xxx_110,
9    8'b10_xxx_xxx: op_modrm_disp = 2'h2;
10    8'b01_xxx_xxx: op_modrm_disp = 2'h1;
11    default:       op_modrm_disp = 2'b0;
12    endcase
13
14end
Сканирование дополнительных 2-х байт может быть только в случае если 1) используется offset16, 2) есть 16-битное слагаемое в вычислении эффективного адреса. Для считывания однобайтного знакового смещения есть mod=01, ну и для всех остальных будет 0, нет дополнительного смещения.
А теперь будет приведен код для самой значительной части, расчет количества байт, необходимых для непосредственного операнда:
1casex (op_queue[7:0])
2    // Однобайтные
3    8'b00xx_x100, 8'b0110_101x, 8'b0111_xxxx, 8'b1000_00x0,
4    8'b1000_0011, 8'b1010_1000, 8'b1011_0xxx, 8'b1100_000x,
5    8'b1100_0110, 8'b1100_1101, 8'b1101_010x, 8'b1110_0xxx,
6    8'b1110_1011:
7        op_imm_size = 2'h1;
8    // Двухбайтные
9    8'b00xx_x101, 8'b0110_100x, 8'b1000_0001, 8'b1010_00xx,
10    8'b1010_1001, 8'b1011_1xxx, 8'b1100_0010, 8'b1100_0111,
11    8'b1100_1010, 8'b1110_100x:
12        op_imm_size = 2'h2;
13    // ENTER i16,i8
14    8'b1100_1000:
15        op_imm_size = 2'h3;
16    // JMP|CALL far
17    8'b1001_1010, 8'b1110_1010:
18        op_imm_size = 3'h4;
19    // TEST rm, i8/i16
20    8'b1111_011x:
21        if (op_queue[13:12] == 2'b00)
22            op_imm_size = op_queue[0] + 1'b1;
23    default:
24        op_imm_size = 1'b0;
25endcase
Вначале здесь выбираются такие маски опкодов, которые отвечают за 1,2,3,4-байтные случаи, но в конце видно, что есть специальные опкоды F6,F7, у которых имеется номер функции в байте modrm. Если этот номер функции 000 или 001, то это — инструкция TEST с непосредственным операндом. Для F6 это будет один байт, для F7 - два байта. Во всех остальных случаях, кроме описанных здесь, непосредственное значение не используется.
В данный момент материал находится в стадии разработки, поэтому пока что на этом все.
Я заканчиваю эту часть и начинаю следующую. У меня ранее был разработан процессор, который работает, используя 32-х битные операнды, так что теперь я просто буду объяснять то, как он работает.