Оглавление


§ Модификация кода

В предыдущей главе я более-менее подробно рассказал о том, как подключить, запустить процессор так, чтобы он сохранял опкод, в этой же главе я расскажу и напишу код выполнения некоторых простых инструкции из первого, базового набора команд. На рассмотрении сегодня будут инструкции LXI, STAX, LDAX, INX и DCX.
В первую очередь, в целях больше отладки, чем какого-то реального значения, я выведу особый сигнал m0 в пины модуля:
1module KR580VM80ALite
2(
3    input               clock,
4    input               reset_n,
5    input               ce,
6    output              m0,          // Новый пин -- начало инструкции
7    output      [15:0]  address,
8    input       [ 7:0]  in,
9    output reg  [ 7:0]  out,
10    output reg          we
11);
12
13assign m0 = (t == 0);
Он будет равен 1, если это — нулевой, стартовый такт инструкции. Соответственно, можно будет поменять запись опкода, просто поставив m0 в уже существующем ранее коде:
1if (m0) begin opcode <= in; pc <= pcn; end
Разницы никакой, но выглядит чуть более прилично. Для функционирования записи данных в регистры, также будут добавлены несколько регистров:
1reg         b;  // =1 Запись d в 8-битный регистр n
2reg         w;  // =1 Запись d в 16-битный регистр n
3reg [15:0]  d;  // 16-битное значение
4reg [ 2:0]  n;  // Номер регистра
Соответственно, в блоке always тоже будет некоторая модификация:
1...
2else if (ce) begin
3
4    t  <= t + 1;      // Счетчик микрооперации
5    b  <= 0;          // Выключить запись в регистр (по умолчанию)
6    w  <= 0;
7    we <= 0;          // Аналогично, выключить запись в память (по умолчанию)
Запись в регистр происходит так. Сначала выставляется номер регистра n, где 0-B, 1-C, 2-D, 3-E, 4-H, 5-L и 7-A.
  • При наличии сигнала b=1, будет записана младшая часть регистра d в 8-битный регистр
  • При наличии сигнала w=1, будет записано значение d в 16-битный регистр
Подготовительный этап закончен, можно приниматься за разработку.

§ Запись регистров

Каждый раз, когда я делаю 80-совместимые процессоры, я делаю немного по-разному каждый из них. Не отходя от своей традиции, сделал видоизменение тоже в том, что вместо того, чтобы формировать данные на позитивном фронте, а записывать на негативном, я формирую также на позитивном, но в то же время запись регистров тоже проходит на позитивном, но на следующем такте.
Это значит, что если на такте 0 будет установлен сигнал b=1 (8 бит) или w=1 (16 бит) , то на такте 1 будет произведена запись в регистр номер n данных d.
1always @(posedge clock)
2if (reset_n && ce) begin
3
4    // 8-bit
5    if (b)
6    case (n)
7    0: bc[15:8] <= d[7:0];  // B
8    1: bc[ 7:0] <= d[7:0];  // C
9    2: de[15:8] <= d[7:0];  // D
10    3: de[ 7:0] <= d[7:0];  // E
11    4: hl[15:8] <= d[7:0];  // H
12    5: hl[ 7:0] <= d[7:0];  // L
13    7:  a       <= d[7:0];  // A
14    endcase
15
16    // 16-bit
17    if (w)
18    case (n)
19    2'b00: bc <= d;
20    2'b01: de <= d;
21    2'b10: hl <= d;
22    2'b11: sp <= d;
23    endcase
24
25end
Также немаловажной деталью этого действия является тот факт, что запись в регистр будет произведена только тогда, когда процессор не находится в состоянии сброса, а также активирован. Как я и говорил ранее, активация с помощью сигнала CE позволит на время останавливать исполнение процессором микрокода без каких-либо негативных последствий. Для Радио86 это довольно важная деталь, поскольку видеоконтроллер постоянно останавливает процессор, пока читает из видеопамяти.

§ Инструкция LXI

Эта инструкция записывает 16-битное непосредственное значение, которое идет сразу за опкодом, в регистр BC, DE, HL или SP и выполняется за 10 тактов. На самом деле, эта инструкция выполняется за 3 реальных такта, но остальные 7 это чтение из памяти, отправка сигнала регенерации. Поскольку я не реализовываю до самых мелочей, то сигнал регенерации формироваться не будет, как и не производиться счетчик регистра R. Зачем его считать, если к нему никакого доступа нет.
Ниже приведен код выполнения инструкции. В зависимости от того, какой t, будет взят либо сохраненный ранее opcode, либо же данные из памяти in.
1casex (t ? opcode : in)
28'b00xx_0001: case (t)
3
4    1: begin pc <= pcn; d[ 7:0] <= in; n <= opcode[5:4]; end
5    2: begin pc <= pcn; d[15:8] <= in; w <= 1; end
6    9: begin t <= 0; end
7
8endcase
Алгоритм работы:
  • На 0-м такте происходит только защелкивание опкода, поэтому каких-то реальных действий и не выполняется
  • На 1-й такте на входе in находится младший байт непосредственного значения, выбирает номер регистра (BC, DE, HL или SP)
  • На 2-м такте ставится на запись уже старший байт и записывает в регистр на следующем такте
  • Такты с 3 по 8 пропускаются (то есть, ничего не происходит)
  • И на 9-м такте переходит к следующей инструкции
По сути, это одна из самых простых инструкции.

§ Инструкция STAX

Значение из регистра A записывается либо в память по адресу BC, либо по адресу DE. Эта инструкция занимает 7Т у процессора, но на деле она у меня выполняется всего лишь за один такт, за первый.
18'b000x_0010: case (t)
2
3    0: begin we <= 1; out <= a; cp <= in[4] ? de : bc; sw <= 1; end
4    6: begin sw <= 0; t <= 0; end
5
6endcase
Устанавливается sw=1 и we=1, чтобы на следующем такте записалось значение из out=a в память по адресу bc, если это STAX B, либо же de, если это STAX D. И всё. На следующих тактах от 1 до 5 ничего не происходит, кроме как на последнем, который обозначен как 6-й, но на самом деле он 7-й, ибо от 0 до 6 включительно — это 7 тактов. Там возвращается sw=0, что устанавливает указать адреса опять на PC и переходит к новой инструкции.

§ Инструкция LDAX

Загрузка из памяти по адресу BC или DE в регистр A. Выполняется за 7Т.
18'b000x_1010: case (t)
2
3    0: begin cp <= in[4] ? de : bc; sw <= 1; end
4    1: begin b  <= 1; n <= 7; d <= in; end
5    6: begin sw <= 0; t <= 0; end
6
7endcase
  • На 0-м такте выбирается источник, откуда будут читаться данные
  • На 1-м такте из памяти читается в промежуточный регистр d для последующей записи в A
  • Соответственно, на 2-м такте происходит запись и на 6-м — переключение обратно на PC и переход к исполнению следующей инструкции

§ Инструкция INX и DCX

Прибавляется +1 если это INX или убавляется -1, если DCX к 16-битной регистровой паре.
18'b00xx_x011: case (t)
2
3    0: begin
4
5        case (in[5:4])
6        2'b00: begin d <= in[3] ? bc - 1 : bc + 1; end
7        2'b01: begin d <= in[3] ? de - 1 : de + 1; end
8        2'b10: begin d <= in[3] ? hl - 1 : hl + 1; end
9        2'b11: begin d <= in[3] ? sp - 1 : sp + 1; end
10        endcase
11
12        n <= in[5:4];
13        w <= 1;
14
15    end
16    4: begin t <= 0; end
17
18endcase
  • На 0-м такте в промежуточный регистр d записывается инкремент или декремент 16-битного регистра. Честно сказать, использование такой конструкции тратит много логических элементов, но ПЛИС у меня большая, так что заботиться не стоит о таких мелочах.
  • Выбирается номер регистра для записи и записывается на следующему такте полученное значение обратно.
  • На 4-м такте — окончание работы этой инструкции и переход к следующей
Далее я уже не буду подробно так комментировать насчет последнего такта, это уже вполне очевидно.
Приложение. Исходный код по материалам текущей главы