Лисья Нора

Оглавление


§ Дополнение пинов

В этой главе потребуется писать и читать в порты, поэтому мне потребуется добавить некоторые новые пины.
module KR580VM80ALite
(
input clock,
input reset_n,
input ce,
output m0,
output [15:0] address,
output reg [ 7:0] port, // Добавлено: Номер порта от 0 до 255
input [ 7:0] in,
input [ 7:0] port_in, // Добавлено: Входящие данные от порта
output reg [ 7:0] out, // Используется также для записи в порт
output reg we,
output reg port_we, // Добавлено: Разрешение записи в порт
output reg iff1 // Добавлено: Разрешение или отключение прерываний
);
В Радио86 не реализованы прерывания как таковые, потому DI/EI инструкции выведены на пищалку и управляют звуком. Ввиду невозможности точно рассчитать такты из-за постоянной остановки процессора видеоконтроллером, звук получается "не очень", и постоянно хрипит.

§ Инструкции RET ccc, RET

Возврат из подпрограммы. С вершины стека берется значение и записывается в PC в том случае, если выполнено условие, выполняется за 11Т. В другом случае, пропуск инструкции, выполняется за 5Т. Это выход из подпрограммы по условию. Всего условий в процессоре предусмотрено восемь: NZ, Z, NC, C, PO, PE, P, M. В самом деле, условий всего лишь 4, просто на каждое дается либо положительный, либо отрицательный ответ.
Например, условие JNZ означает, что выполняться будет тогда, когда ZF=0, условие JZ значит, что выполняться будет только если ZF=1. Всего проверяются флаги: ZF, CF, PF и SF. К примеру, условие "JP" значит что если SF=0, то выполнить переход к метке. Для "JM" означает что если SF=1, то выполнить.
Создам провод на 4 бита, где в каждом бите будет проверяемый флаг.
wire [3:0] cond = {psw[SF], psw[PF], psw[CF], psw[ZF]};
Здесь бит 0 проверяет флаг ZF, бит 3 – флаг SF. Рассмотрим подробнее код микрооперации.
8'b11xx_x000,
8'b1100_1001: case (t)
 
0: begin d <= sp + 2; w <= ccc; n <= 3; sw <= 1; cp <= sp; end
1: begin d[ 7:0] <= in; cp <= cpn; end
2: begin d[15:8] <= in; sw <= 0; end
4: begin t <= ccc ? 5 : 0; end
10: begin t <= 0; pc <= d; end
 
endcase
Здесь объединены два вида RET, условный (11xx_x000) и безусловный опкод (1100_1001), то есть просто RET. Видно, что появился некий провод ccc:
wire ccc = (cond[ opc[5:4] ] == opc[3]) || (opc == 8'hC9) || (opc == 8'hCD);
В зависимости от кода условий, которое хранится в op[5:3], будет выбрано решение. Сначала выбираем проверяемый флаг из opc[5:4], через мультиплексор cond[opc[5:4]] смотрим его значение и сравниваем с условием opc[3]. Если совпадает, то ccc=1. Помимо условного перехода, здесь ccc=1 может быть на некоторых инструкциях, это C9=RET и CD=CALL.
Алгоритм.

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

Чтение слова из вершины стека. Время исполнения 11Т.
Из стека можно прочесть только в 4 регистра, а именно BC, DE, HL, AF. Последний обрабатывается особым образом. Дело в том, что регистровая пара AF состоит из двух регистров: в старшей части записан аккумулятор A, в младшей регистр PSW, или регистр флагов.
8'b11xx_0001: case (t)
 
0: begin d <= sp + 2; w <= 1; n <= 3; sw <= 1; cp <= sp; end
1: begin d[ 7:0] <= in; cp <= cpn; end
2: begin d[15:8] <= in; sw <= 0; n <= opc[5:4]; w <= opc[5:4] != 3; end
3: begin if (opc[5:4] == 3) begin d[7:0] <= d[15:8]; psw <= d[7:0]; b <= 1; n <= 7; end end
10: begin t <= 0; end
 
endcase
Алгоритм.
Получается достаточно странно, но этот код работает.

§ Инструкции PCHL, SPHL, DI и EI

Это довольно простые инструкции, поэтому я объединил их в одном параграфе. PCHL записывает HL в программный счетчик PC (PC = HL), SPHL записывает в SP (SP = HL), DI и EI отключают и включают прерывания. Ранее я упоминал, что прерывания в процессоре не реализованы, потому они тут используются для "бипера", генератора звуковых сигналов.
Реализация инструкции PCHL (5 тактов):
8'b1110_1001: case (t)
4: begin pc <= hl; t <= 0; end
endcase
Реализация инструкции SPHL (5 тактов):
8'b1111_1001: case (t)
0: begin d <= hl; w <= 1; n <= 3; end
4: begin t <= 0; end
endcase
И реализация инструкции DI и EI (4 такта):
8'b1111_x011: case (t)
3: begin t <= 0; iff1 <= opc[3]; end
endcase
Думаю, что код достаточно очевиден и в данном случае комментарии излишни.

§ Инструкция JMP ccc

Выполняется условный или безусловный переход по абсолютному адресу (16 бит). Вне зависимости, выполнилось ли условие или нет, эта инструкция всегда выполняется за 10 тактов.
8'b11xx_x010,
8'b1100_0011: case (t)
 
1: begin cp[ 7:0] <= in; pc <= pcn; end
2: begin cp[15:8] <= in; pc <= pcn; end
9: begin t <= 0; if (ccc || opc[0]) pc <= cp; end
 
endcase
Вначале в регистр cp считывается 16-битный адрес, в любом случае он считывается, и на такте #9 выполняется загрузка cp в pc либо по условию, либо безусловно (опкод C3).

§ Инструкции OUT, IN: работа с портами

На самом деле, в Радио86 портов тоже нет, но процессор все-таки умеет с ними работать, так что можно и добавить такую возможность. OUT отправляет в порт значение из аккумулятора, IN же наоборот, читает из порта в аккумулятор. При этом, флаги никак не меняются. Инструкции выполняются за 10Т.
Реализация OUT.
8'b1101_0011: case (t)
 
1: begin port <= in; port_we <= 1; out <= a; pc <= pcn; end
9: begin t <= 0; end
 
endcase
Пишется номер порта из непосредственного операнда, а также пишутся данные (out) и устанавливается сигнал port_we=1. Следует обязательно добавить сброс port_we на каждом такте.
port_we <= 0;
Я этот код занес вне области исполнения микрокодов. С реализацией IN все так же несложно.
8'b1101_1011: case (t)
 
1: begin port <= in; pc <= pcn; end
2: begin b <= 1; n <= 7; d <= port_in; end
9: begin t <= 0; end
 
endcase
Точно так же устанавливается номер порта, после чего считывается оттуда значение и записывается в регистр A (n=7). Определенного стробирующего такта не отсылается, то есть, предполагаем что при чтении из порта, ничего не защелкивается.

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

Обменивает вершину стека с регистровой парой HL. Выполняется за 18Т. По праву, можно назвать эту инструкцию самой медленно выполняющейся из всех. Она и достаточно сложна и требует 2 чтения и 2 записи в память.
8'b1110_0011: case (t)
 
0: begin sw <= 1; cp <= sp; end
1: begin d[ 7:0] <= in; cp <= cpn; end
2: begin d[15:8] <= in; w <= 1; n <= 2; cp <= hl; end
3: begin we <= 1; out <= cp[7:0]; d[7:0] <= cp[15:8]; cp <= sp; end
4: begin we <= 1; out <= d[7:0]; cp <= cpn; end
17: begin sw <= 0; t <= 0; end
 
endcase
Алгоритм.
Тут я использовал временно регистр CP для того, чтобы хранить HL, поскольку я не хотел делать новые регистры для этой цели.

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

Обмен регистровых пар DE и HL. Время выполнения – 4Т.
8'b1110_1011: case (t)
 
0: begin d <= de; n <= 2; w <= 1; cp <= hl; end
1: begin d <= cp; n <= 1; w <= 1; end
3: begin t <= 0; end
 
endcase
Как и в случае с XTHL, я использую для временного хранения регистр CP. На такте #0 в регистр HL пишется значение DE, на такте #1 в регистр DE пишется то, что ранее было в HL.

§ Инструкция CALL ccc

Условный вызов процедуры. Время выполнения либо 11Т, если вызов не произошел, или 17Т, если переход был выполнен. У инструкции есть два варианта исполнения – условный и безусловный. Безусловный вариант имеет опкод CDh и выполняется всегда, то есть в ccc=1, если opc=CDh.
8'b11xx_x100,
8'b1100_1101: case (t)
 
1: begin d[ 7:0] <= in; pc <= pcn; end
2: begin d[15:8] <= in; pc <= pcn; end
3: begin we <= ccc; out <= pc[ 7:0]; sw <= 1; cp <= sp - 2; end
4: begin we <= ccc; out <= pc[15:8]; cp <= cpn; end
5: begin if (ccc) pc <= d[15:0]; end
6: begin w <= ccc; d <= sp - 2; n <= 3; end
10: begin t <= ccc ? 11 : 0; end
16: begin t <= 0; end
 
endcase
Алгоритм.
В этом микрокоде я пошел на хитрость, записывая или не записывая в регистры и в память новые значения в зависимости от выполнения условия.

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

Запись в стек регистровой пары BC, DE, HL или AF. Выполнение за 11Т.
8'b11xx_0101: case (t)
 
0: begin d <= sp - 2; w <= 1; n <= 3; sw <= 1; cp <= sp - 2; end
1: begin d <= opc[5:4] == 2'b11 ? {a, (psw & 8'b11010101) | 2'b10} : r16; end
2: begin we <= 1; out <= d[ 7:0]; end
3: begin we <= 1; out <= d[15:8]; cp <= cpn; end
10: begin sw <= 0; t <= 0; end
 
endcase
Алгоритм.
Как можно отметить, вместо PSW части я пишу (psw & 8'b11010101) | 2'b10, что позволяет удалить из PSW биты номер 3,5 и установить бит 1 в единицу, поскольку что бы ни было там записано, но в память в этих битах должны писаться именно эти значения.

§ Инструкции АЛУ с непосредственным операндом

Аналогично предыдущим АЛУ инструкциям, они работают с двумя операндами, с операндом А (слева) и непосредственным операндом (справа), который идет сразу за опкодом. Расположение этих инструкции не случайно так сделано, что младшие три бита опкода равны 110b, поскольку позволяет сделать чтение из памяти без всяких дополнительных мультиплексоров.
8'b11xx_x110: case (t)
 
1: begin d <= alur; pc <= pcn; b <= (opc[5:3] != CMP); n <= 7; psw <= aluf; end
6: begin t <= 0; end
 
endcase
Вместо выбора из памяти по адресу HL, из памяти теперь выбирается операнд по адресу PC. Соответственно, добавляется PC+1, и результат пишется в регистр A (кроме CMP инструкции), и в PSW регистр.

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

Вызов прерывания, который работает как вызов процедуры по заранее определенному адресу. Например RST 7 вызывает процедуру по адресу 7*8 = 56 в памяти. RST 1 вызывает по адресу 8 и так далее. Всего таких адресов восемь. Выполняется за 11Т.
8'b11xx_x111: case (t)
 
1: begin we <= 1; d <= sp - 2; n <= 3; w <= 1; cp <= sp - 2; sw <= 1; out <= pc[7:0]; end
2: begin we <= 1; out <= pc[15:8]; cp <= cpn; pc <= {opc[5:3], 3'b000}; end
10: begin sw <= 0; t <= 0; end
 
endcase
Алгоритм.
И вот на этом весь процессор готов. Сделаны все его инструкции и теперь можно начинать пользоваться. Но это лишь только половина дел, поскольку необходимо сделать к нему видеоадаптер, встроить ОЗУ и ПЗУ, создать симулятор в Verilator. Впереди еще много работы, прежде чем мы увидим какой-то результат.