Там схема в LogiSim нарисована, это вообще жесть, представляю сколько оно рисовалось :)
Вид для печати
Там схема в LogiSim нарисована, это вообще жесть, представляю сколько оно рисовалось :)
Именно. Я помню в детстве у меня был вот такой ЧБ телевизор "Кварц":
http://s1921687209.narod.ru/00/tw/kwarc40tb306_4.jpg
Он максимально тупой и когда его кинескоп был новым показывал просто отменную по четкости картинку. И действительно, те Денди, что у меня были, а так же Спектрум показывал на нем без черезстрочности. Т.е., как сейчас модно говорить: 240р. Это когда оба поля одинаковые. Чтобы поля отличались, нужно чтобы количество строк в каждом полуполе отличалось. У нас есть сейчас только NTSC версия и там есть вот такой узел:
http://savepic.net/10172508.png
Я не знаю, зачем сделано именно так, но если запретить перекидываться триггеру по V[8] то NTSC тоже перестает crawl! :) Вот чему равен LINE5:
http://savepic.net/10167388.png
Основной: 1 0101 0100, корректирующий: 1 0101 0011, т.е. разница буквально на 1 пиксель.
- - - Добавлено - - -
Опять же, там стоит AND, и сократиться на 1 пиксель только строка пререндера, на которую указывает сигнал RESCL... Причем, в каждом втором кадре. Но эффект есть.
- - - Добавлено - - -
Vslav, это рандомная логика ядра 6502.
Vslav, это что, они вон первозыч туда пихают... Но это уже оффтопик, хоть и близко к реверсу.
Сегодня поговорим о первом блоке PPU: выбор регистра для чтения или записи. Почему он? Он единственный который в качестве входа использует внешние пины (со стороны процессора) и формирует внутренние сигналы. Вот так он выглядит в оригинальном схемном вводе:
http://savepic.net/10218591.png
Здесь: N_DBE это строб обращения, R_W это направление обращения (лог.0 - запись в PPU), A[2..0] - жгут адресных линий. Сюда же следует добавить вот эту защелку, которая сохраняет записываемые данные для внутренних нужд:
http://savepic.net/10162268.png
Собственно, здесь все просто. Адреса поданы на дешифратор, который преобразует 3х битный код в 8 сигналов. Эти 8 сигналов смешиваются с сигналами управления (N_DBE и R_W), от чего получается набор сигналов Rx и Wx. Первый означает чтение из регистра а второй запись в него. Не все регистры полноценны: некоторые доступны только на чтение а некоторые только на запись. Узел вверху на мультиплексорах это обычная перекидушка на эквивалентах защелок (помните, мультиплексор на 4х транзисторах?). Он устанавливается в конкретное состояние при чтении из регистра #2 - по сигналу R2, а перекидывается при обращении к регистрам #5 и #6, по сигналам R5_x и R6_x. Это двойные регистры, первый (#5) регистр прокрутки а второй (#6) регистр адреса VRAM для обращения процессора. Особенность PPU в том, что для рендера и для обращения процессором к VRAM используется один и тот же счетчик, так что обращаться к VRAM следует только во время кадрового гашения либо при отключенном рендере, иначе результат не предсказуем.
После чистки схема этого узла получается следующей:
http://savepic.net/10165340.png
В принципе, она практически не отличается от исходной. Однако она будет немного отличаться в Верилоге. Я объясню. Дело в том, что входные сигналы являются полностью асинхронными к внутренним сигналам и поэтому их надо синхронизировать. Как говорят в книжках: перевести в локальный (рабочий) тактовый домен. Так как сигналы полностью асинхроны к частоте ядра, то привязывать их можно только к главному мастерклоку - самой быстрой доступной частоте. Это гарантирует, что ядро не пропустит входящий запрос. В Верилоге это будет выглядеть так:
Для начала мы наберем наш PPU в виде отдельных блоков-модулей, а потом объеденим их под одной крышей, чтобы не плодить сущностей. Я думаю тут комментировать не надо, это действительно самый простой из модулей.Код:// Управление регистрами PPU со стороны CPU
module PPU_REG_SEL(
// Входные цепи
input Clk, // Тактовая последовательность
input [2:0]Adr, // Адрес регистра
input RnW, // Направление обращения R/W
input nSTB, // Строб обращения к PPU
input [7:0]DIn, // Входные данные
// Выходные цепи
output reg W0, // Запись в регистр #0
output reg W1, // Запись в регистр #1
output reg R2, // Чтение из регистра #2
output reg W3, // Запись в регистр #3
output reg R4, // Чтение из регистра #4
output reg W4, // Запись в регистр #4
output reg W5_1, // Запись в регистр #5/1
output reg W5_2, // Запись в регистр #5/2
output reg W6_1, // Запись в регистр #6/1
output reg W6_2, // Запись в регистр #6/2
output reg R7, // Чтение из регистра #7
output reg W7, // Запись в регистр #7
output reg [7:0]DOut // Выходные данные
);
// Внутренние регистры
reg [2:0]AdrR;
reg RnWR;
reg nSTBR;
reg Sel;
reg FlipR;
wire Flip;
assign Flip = W5_1 | W5_2 | W6_1 | W6_2;
// Логика работы проста
always @(posedge Clk) begin
// Синхронизируем сигналы
AdrR[2:0] <= Adr[2:0]; RnWR <= RnW; nSTBR <= nSTB;
// Декодируем
W0 <= ~AdrR[2] & ~AdrR[1] & ~AdrR[0] & ~RnWR & ~nSTBR;
W1 <= ~AdrR[2] & ~AdrR[1] & AdrR[0] & ~RnWR & ~nSTBR;
R2 <= ~AdrR[2] & AdrR[1] & ~AdrR[0] & RnWR & ~nSTBR;
W3 <= ~AdrR[2] & AdrR[1] & AdrR[0] & ~RnWR & ~nSTBR;
R4 <= AdrR[2] & ~AdrR[1] & ~AdrR[0] & RnWR & ~nSTBR;
W4 <= AdrR[2] & ~AdrR[1] & ~AdrR[0] & ~RnWR & ~nSTBR;
W5_1 <= AdrR[2] & ~AdrR[1] & AdrR[0] & ~RnWR & ~nSTBR & ~Sel;
W5_2 <= AdrR[2] & ~AdrR[1] & AdrR[0] & ~RnWR & ~nSTBR & Sel;
W6_1 <= AdrR[2] & AdrR[1] & ~AdrR[0] & ~RnWR & ~nSTBR & ~Sel;
W6_2 <= AdrR[2] & AdrR[1] & ~AdrR[0] & ~RnWR & ~nSTBR & Sel;
R7 <= AdrR[2] & AdrR[1] & AdrR[0] & RnWR & ~nSTBR;
W7 <= AdrR[2] & AdrR[1] & AdrR[0] & ~RnWR & ~nSTBR;
// Выбор регистра из пары
if (R2) Sel <= 1'b0;
else if (FlipR & ~Flip) Sel <= ~Sel;
FlipR <= Flip;
// Запоминаем данные
if (~nSTB & ~RnW) DOut[7:0] <= DIn[7:0];
end
// Конец модуля
endmodule
Рендер начинает брать данные с другого места, а в моменты перезагрузок счетчиков восстанавливать некоторые адреса, мешая собственно самому CPU.
Titus, ессно, мы же повторим внутреннюю логическую структуру.