Сегодня поговорим о первом блоке PPU: выбор регистра для чтения или записи. Почему он? Он единственный который в качестве входа использует внешние пины (со стороны процессора) и формирует внутренние сигналы. Вот так он выглядит в оригинальном схемном вводе:
Здесь: N_DBE это строб обращения, R_W это направление обращения (лог.0 - запись в PPU), A[2..0] - жгут адресных линий. Сюда же следует добавить вот эту защелку, которая сохраняет записываемые данные для внутренних нужд:
Собственно, здесь все просто. Адреса поданы на дешифратор, который преобразует 3х битный код в 8 сигналов. Эти 8 сигналов смешиваются с сигналами управления (N_DBE и R_W), от чего получается набор сигналов Rx и Wx. Первый означает чтение из регистра а второй запись в него. Не все регистры полноценны: некоторые доступны только на чтение а некоторые только на запись. Узел вверху на мультиплексорах это обычная перекидушка на эквивалентах защелок (помните, мультиплексор на 4х транзисторах?). Он устанавливается в конкретное состояние при чтении из регистра #2 - по сигналу R2, а перекидывается при обращении к регистрам #5 и #6, по сигналам R5_x и R6_x. Это двойные регистры, первый (#5) регистр прокрутки а второй (#6) регистр адреса VRAM для обращения процессора. Особенность PPU в том, что для рендера и для обращения процессором к VRAM используется один и тот же счетчик, так что обращаться к VRAM следует только во время кадрового гашения либо при отключенном рендере, иначе результат не предсказуем.
После чистки схема этого узла получается следующей:
В принципе, она практически не отличается от исходной. Однако она будет немного отличаться в Верилоге. Я объясню. Дело в том, что входные сигналы являются полностью асинхронными к внутренним сигналам и поэтому их надо синхронизировать. Как говорят в книжках: перевести в локальный (рабочий) тактовый домен. Так как сигналы полностью асинхроны к частоте ядра, то привязывать их можно только к главному мастерклоку - самой быстрой доступной частоте. Это гарантирует, что ядро не пропустит входящий запрос. В Верилоге это будет выглядеть так:
Для начала мы наберем наш 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







Ответить с цитированием
Размещение рекламы на форуме способствует его дальнейшему развитию 

