09:04
Глава 6. Инструкции C0-FF, дополненный набор — Лисья нора
Оглавление
- Дополнение пинов
- Инструкции RET ccc, RET
- Инструкция POP
- Инструкции PCHL, SPHL, DI и EI
- Инструкция JMP ccc
- Инструкции OUT, IN: работа с портами
- Инструкция XTHL
- Инструкция XCHG
- Инструкция CALL ccc
- Инструкция PUSH
- Инструкции АЛУ с непосредственным операндом
- Инструкция RST
§ Дополнение пинов
В этой главе потребуется писать и читать в порты, поэтому мне потребуется добавить некоторые новые пины.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.Алгоритм.
- Такт 0. В d записываем новое значение SP+2, сдвиг стека (n=3 это SP), выбираем память cp, указатель на вершину стека и если условие выполняется, то тогда в регистр SP будет записано SP+2, но в противном случае, ничего не будет сделано
- Такт 1. Читается младший байт из стека (адрес возврата)
- Такт 2. Читается старший байт из стека
- Такт 4. Если условие выполнено, то на 10-м такте в регистр PC будет записано прочтенное значение из стека, а иначе просто перейдет к следующей инструкции.
§ Инструкция 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Алгоритм.
- Такт 0. Запись в SP нового значения SP+2, сигналы аналогичны тем же, что и в RET
- Такт 1. Чтение младшего байта
- Такт 2. Чтение старшего байта. Отключаем указатель памяти cp (sw=0), и пишется результат в 16-битный регистр (n) только в том случае, если это не AF
- Такт 3. Если регистровая пара является AF, то в этом случае из старшего байта d переносится в младший для того, чтобы была возможность оттуда записать в регистр A (b=1, n=7), а в PSW записывается то, что было в младшем байте d.
§ Инструкции 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Алгоритм.
- Такт 0. Ставится указатель в памяти на вершину стека
- Такт 1. Читается младший байт из стека
- Такт 2. Читается старший байт из стека и пишется результат в регистр HL, и старый HL временно сохраняется в CP
- Такт 3. Пишется в вершину стека младший байт бывшего ранее HL, а старший байт сохраняется временно в регистр d, восстанавливается указатель вершины стека в CP
- Такт 4. Пишется старший байт в вершину стека
§ Инструкция 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Алгоритм.
- Такт 1. Считывание младшего байта адреса перехода
- Такт 2. Считывание старшего байта адреса перехода
- Такт 3. Запись в память младшей части PC (или PCL) в стек, но только если условие выполнилось, иначе ничего не пишется
- Такт 4. Аналогично, запись PCH (старшей части PC) в стек, при выполнении условия
- Такт 5. Если условие выполнилось, то тогда занести в PC прочитанное значение адреса
- Такт 6. Запись в регистр SP (n=3) нового значения SP-2, но только если условие выполнено
- Такт 10. Если переход в процедуру был, то продолжить отсчитывать такты, иначе перейти к следующей инструкции (это вариант выполнения инструкции за 11Т)
§ Инструкция 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Алгоритм.
- Такт 0. В регистр SP (n=3) пишется (w=1) новое значение SP-2, и также устанавливается адрес памяти тоже CP = SP-2. Туда будут писаться данные.
- Такт 1. Выбор тех данных которые будут писаться в стек. Если в opc[5:4] указана 3-я регистровая пара, то это AF, иначе регистры BC, DE или HL
- Такт 2. Запись в память младшего байта регистровой пары
- Такт 3. Запись в память старшего байта регистровой пары
- Такт 10. Отключение от указателя памяти CP и переход к исполнению следующей инструкции
(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Алгоритм.
- Такт 0. Уменьшение SP=SP-2, выбор CP=SP-2 для записи туда адреса возврата и тут же, сохранение туда младшего байта PCL
- Такт 1. Сохранение в стек старшего байта PCH и запись в PC вычисленного адреса из номера опкода
Материалы к статье. Код процессора