Содержание
6502
Процессор 6502 был разработан компанией MOS в 197-бородатом году. За основу была положена архитектура процессора Motorolla 6800:
(видно что в обоих случаях верхнюю часть занимает декодер и рандомная логика, а всю нижнюю часть процессора занимает контекст)
Описание внутренней архитектуры процессора 6502
Процессор делится на 2 части: верхняя и нижняя.
В верхней части находится управляющая часть, которая выдает ряд контрольных линий ("команд") для управления нижней частью.
В нижней части находится контекст процессора: внутренние шины и регистры, с одним исключением - регистр флагов (P) находится в верхней части в "размазанном" виде.
Работа процессора тактируется тактовым импульсом PHI0, при этом используются оба полутакта.
Во время первого полутакта (PHI1) процессор находится в режиме "говорю". В это время процессор выдает наружу данные.
Во время второго полутакта (PHI2) процессор находится в режиме "слушаю", во время этого полутакта внешние устройства могут помещать данные на шину данных, чтобы процессор их "обработал".
Регистры
- PD: текущее значения кода операции для предекодирования
- IR: регистр инструкций (хранит код текущей операции)
- X, Y: индексные регистры
- S: указатель стека
- AI, BI: входные значения для АЛУ
- ADD: промежуточный результат операции на АЛУ
- AC: аккумулятор
- PCH/PCL: program counter, состоящий из 2-х половинок
- PCHS/PCLS: вспомогательные регистры program counter (S означает "set" (?))
- ABH/ABL: регистры для вывода на внешнюю шину адреса
- DL: data latch, хранит последнее прочитанное значение внешней шины данных
- DOR: data output register, содержит значение которое будет записано в шину данных
- P: регистр флагов, на самом деле состоит из множества разбросанных по схеме защелок
Непосредственно программисту доступны следующие регистры: A (аккумулятор), X, Y, S, P, PC.
Внешние шины
Внешних шин всего две: 16-разрядная адресная (ADDR) и 8-разрядная шина данных (DATA). Адресная шина односторонняя - писать в неё может только процессор. Шина данных двунаправленная.
Внутренние шины
- ADH/ADL: шина адреса
- SB: special bus, шина для обмена регистрами
- DB: внутренняя шина данных
Во время второго полутакта (PHI2) все внутренние шины подзаряжаются и имеют значение 0xff. Сделано это по причине того, что "разрядить" транзистор в нужный момент получается быстрее, чем "зарядить" (смена значения 1⇒0 происходит быстрее, чем смена 0⇒1).
Соединения регистров с шинами
Последовательно соединяя шины и регистры процессор выполняет разнообразные инструкции. Многообразие соединений обеспечивает разнообразие команд процессора, а разделение команд на такты позволяет выполнять сложные действия. Дополнительно производится управление АЛУ (сложение, логическое-И и пр.)
Режимы адресации 6502
Адресация - это способ доставить операнд в нужное место памяти (или загрузить его оттуда). Разработчики 6502 были очень щедрыми и добавили в контекст ажно два индексных регистра X и Y.
"Индексный" - это означает что к адресу памяти определенным образом добавляется смещение, чтобы получить новый адрес. Обычно это нужно для доступу к массивам. В этом случае начало массива будет являться фиксированным адресом, а значение в индексном регистре - индексом массива (смещением).
Список режимов адресации :
- Immediate (непосредственный операнд). В этом случае операнд хранится в самой инструкции (обычно вторым байтом, после кода операции). Пример LDA #$1C : A = 0x1C
- Absolute (абсолютная адресация). В инструкции указывается полный 16-разрядный адрес, откуда следует получить операнд. Например LDA $1234 : A = [$1234]
- Zero page absolute (абсолютная адресация на нулевой странице) : Разработчики сделали оптимизированную версию абсолютной адресации, добавив возможность адресоваться только к нулевой странице (страницы 6502 имеют размер 256 байт). Пример LDA $56 : в этом случае процессор автоматом делает старшие 8 разрядов адреса равными 0x00, а младшие 8 разрядов берутся из инструкции. Итоговый адрес получается 0x0056. A = [0x0056]. Сделано это для экономии размера инструкции (экономится 1 байт).
- Indexed (индексная) : в этом режиме адресации к постоянному значению адреса добавляется смещение из регистра X или Y. Например LDA $1234, X : A = [$1234 + X]
- Zero page Indexed (индексная адресация на нулевой странице) : аналогично индексной, но использовать можно только регистр X. Пример LDA $33, X : A = [$0033 + X]
Ну а дальше начинается особенная магия:
- Pre-indexed indirect (косвенная с пре-индексацией) : Значение операнда, который является адресом в нулевой странице складывается со значением регистра X и получается косвенный адрес. Затем по адресу, на который ссылается косвенный адрес, получается значение операнда. Пример LDA ($34, X) : A = [[$0034 + X]]. Важно : при сложении адреса и значения в регистре X происходит "заворачивание" вокруг 256 байт. То есть перенос в старшую половину адреса не происходит. ( 0xFF + 0x02 будет равно 0x0001, а не 0x0101). Косвенная означает "взять адрес по адресу".
- Post-indexed indirect (косвенная с пост-индексацией) : Отличается от предыдущей тем, что вначале выбирается косвенный адрес из нулевой страницы, а затем к нему добавляется значение индексного регистра Y. Пример LDA ($2A), Y : A = [[$002A] + Y].
Набор инструкций
6502 обладает всеми необходимыми инструкциями, а также включает в себя такие достаточно удобные инструкции как ротация бит (ROL/ROR) и тестирование разряда (BIT). Не все процессоры того времени содержали такие операции.
Поведение инструкций, а также адресные режимы полностью задаются кодом операции, для упрощения декодирования, однако ширина шины (8 разрядов) не позволяет выполнять все инструкции за 1 такт. Также декодер несколько не оптимизирован, поэтому минимальное время выполнения инструкций - 2 такта, при этом первый такт всегда занимает выборка кода операции (1й байт инструкции).
Краткое содержание инструкций :
Инструкция | Действие |
---|---|
ADC | Add Memory to Accumulator with Carry |
AND | "AND" Memory with Accumulator |
ASL | Shift Left One Bit (Memory or Accumulator) |
BCC | Branch on Carry Clear |
BCS | Branch on Carry Set |
BEQ | Branch on Result Zero |
BIT | Test Bits in Memory with Accumulator |
BMI | Branch on Result Minus |
BNE | Branch on Result not Zero |
BPL | Branch on Result Plus |
BRK | Force Break |
BVC | Branch on Overflow Clear |
BVS | Branch on Overflow Set |
CLC | Clear Carry Flag |
CLD | Clear Decimal Mode |
CLI | Clear interrupt Disable Bit |
CLV | Clear Overflow Flag |
CMP | Compare Memory and Accumulator |
CPX | Compare Memory and Index X |
CPY | Compare Memory and Index Y |
DEC | Decrement Memory by One |
DEX | Decrement Index X by One |
DEY | Decrement Index Y by One |
EOR | "Exclusive-Or" Memory with Accumulator |
INC | Increment Memory by One |
INX | Increment Index X by One |
INY | Increment Index Y by One |
JMP | Jump to New Location |
JSR | Jump to New Location Saving Return Address |
LDA | Load Accumulator with Memory |
LDX | Load Index X with Memory |
LDY | Load Index Y with Memory |
LSR | Shift Right One Bit (Memory or Accumulator) |
NOP | No Operation |
ORA | "OR" Memory with Accumulator |
PHA | Push Accumulator on Stack |
PHP | Push Processor Status on Stack |
PLA | Pull Accumulator from Stack |
PLP | Pull Processor Status from Stack |
ROL | Rotate One Bit Left (Memory or Accumulator) |
ROR | Rotate One Bit Right (Memory or Accumulator) |
RTI | Return from Interrupt |
RTS | Return from Subroutine |
SBC | Subtract Memory from Accumulator with Borrow |
SEC | Set Carry Flag |
SED | Set Decimal Mode |
SEI | Set Interrupt Disable Status |
STA | Store Accumulator in Memory |
STX | Store Index X in Memory |
STY | Store Index Y in Memory |
TAX | Transfer Accumulator to Index X |
TAY | Transfer Accumulator to Index Y |
TSX | Transfer Stack Pointer to Index X |
TXA | Transfer Index X to Accumulator |
TXS | Transfer Index X to Stack Pointer |
TYA | Transfer Index Y to Accumulator |
Разработчики подбирали кодировку таким образом, чтобы её было удобней обрабатывать декодером и рандомной логикой. Более подробное описание каждой инструкции вы можете узнать перейдя по ссылке соотв. кода операции.
HI | LO-BYTE | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 0A | 0B | 0C | 0D | 0E | 0F | |
00 | BRK impl | ORA X,ind | Not impl.02 | Not impl.03 | Not impl.04 | ORA zpg | ASL zpg | Not impl.07 | PHP impl | ORA # | ASL A | Not impl.0B | Not impl.0C | ORA abs | ASL abs | Not impl.0F |
Прерывания
Всего у 6502 существует четыре типа прерываний:
- IRQ: обычное аппаратное прерывание. Можно запретить флагом I (interrupt disable), если флаг I=1, это значит что прерывание "отключено" и не проходит в процессор.
- NMI: немаскируемое прерывание. Имеет более высокий приоритет над IRQ, срабатывает по перепаду уровня, конкретно - по спаду (falling edge).
- RES: аппаратный сброс. После включения 6502 нужно устанавливать контакт /RES в 0 в течении нескольких тактов, чтобы процессор "пришёл в себя".
- BRK: программное прерывание. Инициируется инструкцией BRK.
Verilog
// ------------------ // Dispatcher module Dispatcher ( // Outputs _ready, _IPC, _T0X, _T1X, T0, T1, _T2, _T3, _T4, _T5, T5, T6, RD, Z_IR, FETCH, RW, SYNC, ACRL2, // Inputs PHI0, RDY, DORES, RESP, B_OUT, BRK6E, BRFW, _BRTAKEN, ACR, _ADL_PCL, PC_DB, _IMPLIED, _TWOCYCLE, decoder ); input PHI0, RDY; input DORES, RESP, B_OUT, BRK6E, BRFW, _BRTAKEN, ACR, _ADL_PCL, PC_DB, _IMPLIED, _TWOCYCLE; input [129:0] decoder; output _ready, _IPC, _T0X, _T1X, T0, T1, _T2, _T3, _T4, _T5, T5, T6, RD, Z_IR, FETCH, RW, SYNC, ACRL2; wire _ready, _IPC, _T0X, _T1X, T0, T1, _T2, _T3, _T4, _T5, T5, T6, RD, Z_IR, FETCH, RW, SYNC; // Clocks wire PHI1, PHI2; assign PHI1 = ~PHI0; assign PHI2 = PHI0; // Misc wire BR2, BR3, _MemOP, STOR, _SHIFT, _STORE; assign BR2 = decoder[80]; assign BR3 = decoder[93]; assign _MemOP = ~( decoder[111] | decoder[122] | decoder[123] | decoder[124] | decoder[125] ); assign STOR = ~( ~decoder[97] | _MemOP ); assign _SHIFT = ~( decoder[106] | decoder[107] ); assign _STORE = ~decoder[97]; // Ready Control wire Ready1_Out, Ready2_Out; mylatch Ready1 ( Ready1_Out, ~(RDY | Ready2_Out), PHI2 ); mylatch Ready2 ( Ready2_Out, WR, PHI1 ); assign _ready = Ready1_Out; // R/W Control wire WRLatch_Out, RWLatch_Out; wire WR; mylatch WRLatch ( WRLatch_Out, ~( decoder[98] | decoder[100] | T5 | STOR | T6 | PC_DB), PHI2 ); assign WR = ~ ( _ready | REST | WRLatch_Out ); mylatch RWLatch ( RWLatch_Out, WR, PHI1 ); assign RW = ~RWLatch_Out; assign RD = (PHI1 | ~RWLatch_Out); // Short Cycle Counter (T0-T1) wire TRESXLatch_Out, TWOCYCLELatch_Out, TRES1Latch_Out, T0Latch_Out, T1Latch_Out; mylatch TRESXLatch ( TRESXLatch_Out, TRESX, PHI1 ); mylatch TWOCYCLELatch ( TWOCYCLELatch_Out, _TWOCYCLE, PHI1 ); mylatch TRES1Latch ( TRES1Latch_Out, TRES1, PHI1 ); assign _T0X = ~( (~(TRESXLatch_Out&TWOCYCLELatch_Out) & ~TRES1Latch_Out) | ~(T0Latch_Out | T1Latch_Out) ); assign T0 = ~_T0X; mylatch T0Latch ( T0Latch_Out, _T0X, PHI2 ); mylatch T1Latch ( T1Latch_Out, ~(T0Latch_Out | _ready), PHI1 ); assign _T1X = ~T1Latch_Out; // Long Cycle Counter (T2-T5) (Shift Register) wire T1InputLatch_Out; mylatch T1InputLatch ( T1InputLatch_Out, T1, PHI2 ); wire LatchIn_T2_Out, LatchOut_T2_Out; mylatch LatchIn_T2 ( LatchIn_T2_Out, _ready ? LatchOut_T2_Out : ~T1InputLatch_Out, PHI1 ); mylatch LatchOut_T2 ( LatchOut_T2_Out, ~(LatchIn_T2_Out | TRES2), PHI2 ); assign _T2 = (LatchIn_T2_Out | TRES2 ); wire LatchIn_T3_Out, LatchOut_T3_Out; mylatch LatchIn_T3 ( LatchIn_T3_Out, _ready ? LatchOut_T3_Out : ~LatchOut_T2_Out, PHI1 ); mylatch LatchOut_T3 ( LatchOut_T3_Out, ~(LatchIn_T3_Out | TRES2), PHI2 ); assign _T3 = (LatchIn_T3_Out | TRES2 ); wire LatchIn_T4_Out, LatchOut_T4_Out; mylatch LatchIn_T4 ( LatchIn_T4_Out, _ready ? LatchOut_T4_Out : ~LatchOut_T3_Out, PHI1 ); mylatch LatchOut_T4 ( LatchOut_T4_Out, ~(LatchIn_T4_Out | TRES2), PHI2 ); assign _T4 = (LatchIn_T4_Out | TRES2 ); wire LatchIn_T5_Out, LatchOut_T5_Out; mylatch LatchIn_T5 ( LatchIn_T5_Out, _ready ? LatchOut_T5_Out : ~LatchOut_T4_Out, PHI1 ); mylatch LatchOut_T5 ( LatchOut_T5_Out, ~(LatchIn_T5_Out | TRES2), PHI2 ); assign _T5 = (LatchIn_T5_Out | TRES2 ); // Extra Cycle Counter (T5-T6) wire T56Latch_Out, T5Latch1_Out, T2Latch2_Out, T6Latch1_Out, T6Latch2_Out; mylatch T56Latch ( T56Latch_Out, ~(_SHIFT | _MemOP | _ready), PHI2 ); mylatch T5Latch1 ( T5Latch1_Out, ~(T5Latch2_Out & _ready) & ~T56Latch_Out, PHI1 ); mylatch T5Latch2 ( T5Latch2_Out, ~T5Latch1_Out, PHI2 ); mylatch T6Latch1 ( T6Latch1_Out, ~(~T5Latch1_Out & ~_ready), PHI2 ); mylatch T6Latch2 ( T6Latch2_Out, ~T5Latch1_Out, PHI1 ); assign T5 = ~T5Latch1_Out; assign T6 = T6Latch2_Out; // Instruction Termination (reset cycle counters) wire REST, ENDS, ENDX, TRES2; wire ENDS1_Out, ENDS2_Out; assign REST = ~(_STORE & _SHIFT) & DORES; mylatch ENDS1 ( ENDS1_Out, _ready ? ~T1 : (~(_BRTAKEN & BR2) & ~T0), PHI2 ); mylatch ENDS2 ( ENDS2_Out, RESP, PHI2 ); assign ENDS = ~( ENDS1_Out | ENDS2_Out ); wire temp; assign temp = ~( decoder[100] | decoder[101] | decoder[102] | decoder[103] | decoder[104] | decoder[105]); assign ENDX = ~( ~temp | T6 | BR3 | ~(_MemOP | decoder[96] | ~_SHIFT) ); wire ReadyPhi1_Out, RESP1_Out, RESP2_Out, T1L_Out; mylatch ReadyPhi1 ( ReadyPhi1_Out, ~_ready, PHI1 ); mylatch RESP1 ( RESP1_Out, ~(RESP | ReadyPhi1_Out | RESP2_Out), PHI2 ); mylatch RESP2 ( RESP2_Out, ~(RESP1_Out | Brfw), PHI1 ); mylatch T1L ( T1L_Out, ~TRES1, PHI1 ); assign T1 = ~T1L_Out; assign TRES1 = (ENDS | ~(_ready | ~(RESP1_Out | Brfw) ) ); assign SYNC = T1; wire TRESX1_Out, TRESX2_Out; mylatch TRESX1 ( TRESX1_Out, ~(decoder[91] | decoder[92]), PHI2); mylatch TRESX2 ( TRESX2_Out, ~( RESP | ENDS | ~(_ready | ENDX) ), PHI2 ); assign TRESX = ~(BRK6E | ~(_ready | ACRL1 | REST | TRESX1_Out) | ~TRESX2_Out); wire TRES2Latch_Out; mylatch TRES2Latch ( TRES2Latch_Out, TRESX, PHI1 ); assign TRES2 = ~TRES2Latch_Out; // ACR Latch wire ACRL1, ACRL2; wire ACRL1Latch_Out, ACRL2Latch_Out; assign ACRL2 = ~(~ACR & ~ReadyDelay) & (~ReadyDelay | ~ACRL1Latch_Out); mylatch ACRL1Latch ( ACRL1Latch_Out, ~ACRL2Latch_Out, PHI2 ); mylatch ACRL2Latch ( ACRL2Latch_Out, ACRL2, PHI1 ); assign ACRL1 = ~ACRL1Latch_Out; // Program Counter Increment Control wire ReadyDelay, Brfw; wire DelayLatch1_Out, DelayLatch2_Out; mylatch DelayLatch1 ( DelayLatch1_Out, _ready, PHI1 ); mylatch DelayLatch2 ( DelayLatch2_Out, ~DelayLatch1_Out, PHI2 ); assign ReadyDelay = ~DelayLatch2_Out; wire BRFWLatch_Out; mylatch BRFWLatch ( BRFWLatch_Out, ~(~BR3 | ReadyDelay), PHI2 ); assign Brfw = ~(BRFW ^ ACR) & BRFWLatch_Out; wire RouteCLatch_Out, a_out, b_out, c_out; mylatch RouteCLatch ( RouteCLatch_Out, ~(BR2 & _BRTAKEN) & (_ADL_PCL | BR2 | BR3), PHI2 ); mylatch c_latch ( c_out, ~(RouteCLatch_Out | _ready | ~_IMPLIED), PHI1 ); mylatch a_latch ( a_out, B_OUT, PHI1 ); mylatch b_latch ( b_out, Brfw, PHI1 ); assign _IPC = ~(a_out & (b_out | c_out)); // Fetch Control wire FetchLatch_Out; mylatch FetchLatch ( FetchLatch_Out, T1, PHI2 ); assign FETCH = ~( _ready | ~FetchLatch_Out ); assign Z_IR = ~( B_OUT & FETCH ); endmodule // Dispatcher