Обратная разработка GID на Opel Astra H — ПИПМАЙ: Лучшее со всей сети
Акцентный цвет
Фон
Игровой блок на главной
Праздничное оформление
Для всех устройств

Обратная разработка GID на Opel Astra H

Захотел собрать CarPC в Opel Astra H. Родной монохромный экран убрать нельзя, так как он является шлюзом между разными CAN-шинами и на него выводится много полезной информации. Оставлять второй экран в авто тоже не хочу. Потому буду парсить данные с шины экрана и выводить через композитный видеовыход, используя FPGA.

Предыстория

Более 10 лет назад, когда было много свободного времени и чесались руки, я, вдохновившись чужими CarPC проектами, тоже собрал себе компьютер в машину (Москвич 214145). В то время подрабатывал системным администратором и мне стало очень удобно не таскать с собой каждый день ноутбук.

Размещение экрана было очень неудобным, но тогда не было такой доступной 3D печати и выкручивались как могли. На фото CarPC установлен в торпедо от Opel Vectra A.

Настоящее время

Шло время, все менялось и теперь у меня есть автомобиль Opel Astra H, больше знаний и опыта, но «дурная голова рукам покоя не дает» и я решил снова собрать CarPC.

Цель всего проекта не CarPC, как результат, а самообразование. На рынке много готовых решений, которые будут дешевле. Я лишь делюсь описанием небольшого этапа.

Консоль

Следующим образом выглядит торпедо в автомобиле Opel Astra H

Штатный монохромный графический экран имеет удобное местоположение на торпедо, можно его убрать, распечатать переходную рамку под новую матрицу, но есть большое НО: штатный экран (у меня это трехстрочный монохромный графический экран, именуемый как GID) является шлюзом между разными CAN-шинами. Если его отключить, то не будет работать климатическая система.

Умельцы переносят этот экран в потолок. Печатают рамку на центральную часть магнитолы и переносят туда, но все это требует доработки штатной проводки, либо создание удлинителей. Мне кажется, что два экрана в авто – это перебор.

Можно спрятать GID за торпедо, но на него выводится много полезной информации и не хочется ее терять.

В 2020 году купил подходящую матрицу экрана и плату конвертера на контроллере RTD2662. Напечатал рамку, примерил и на этом мои силы кончились, так как ушли в домашний ремонт.

Прошло 5 лет, я снова вернулся к этому увлекательному проекту. Но пора заканчивать эту небольшую лирику и переходить к сути

Ломаем GID

Я решил и GID спрятать и информацию с него получать.

Можно ловить CAN-пакеты. Благодаря замечательному сообществу, именуемому как «Astra H CAN хакеры», декодировано и разобрано на биты огромное количество пакетов и параметров, которые в них содержатся.

Но я решил пойти другим путем: буду ловить данные, которые идут непосредственно на матрицу экрана. Таким образом плата от GID останется на родном месте.

Чтобы случайно не сломать мой экран, приобрел такой-же на разборке. Чтобы он включился на столе – необходима магнитола и соединить их по CAN шине. В таком случае экран включается при включении магнитолы.

Матрица экрана с кодовым названием 80509CN выглядит следующим образом:

Что примечательно – у нее два шлейфа. Ранее мне такие не попадались. По всевозможным номерам, которые есть на матрице – не ищется никакой документации. Значит буду проводить обратную разработку.

Матрица подключается к основной плате GID-а.

Осмотрев плату, стало понятно, что слоев в ней больше, чем два, так как некоторые переходные отверстия от сигнальных дорожек с одной стороны платы, никуда не подключены на обратной стороне.

Установлен микроконтроллер NEC v850eca2. Пролистав документацию, не увидел специализированного контроллера для экрана. Скорее всего экран сидит или на внешней шине данных, или на GPIO.

Не стал реверсить схему подключения экрана к микроконтроллеру, а решил пробежать по выводам. Внешне похоже, что оба разъема экрана имеют одинаковую распиновку. Прозвонил выводы двух разъемов на взаимное соединение, отметил КРАСНЫМ – линии, которые попарно соединены, а ЖЕЛТЫМ – которые у каждого разъема индивидуальны.

И действительно, большее число выводов подключено параллельно.

Случайно наткнулся в интернете на информацию, что если перевернуть экран на 180 градусов и подключить, то он тоже будет работать (внутри корпуса есть выступы, препятствующие этому, но один человек отколол часть матрицы и у него перестала работать нижняя половина, на которой наибольшее количество важной информации. Он удалил ограничители и перевернул экран)

Получается, что внутри экрана ДВА одинаковых контроллера. Что сильно облегчает дальнейшую работу.

Далее я взял осциллограф и начал смотреть, как себя ведут разные линии.

На фотографии ниже сделал отметки: если напряжение стабильное, то указал уровень. Сигнал положительной полярности (преобладает низкий уровень), то отметил как «p», сигнал отрицательной полярности (преобладает высокий уровень), то отметил как «n», если идет постоянный меандр, то указал частоту и заполнение. Цифровые сигналы имеют лог. Уровень 5в.

Из этой картины явно выделяется шина данных на 8 бит и сигналы выбора верхнего/нижнего контроллера (единственный сигнальный вывод на разъеме, который не объединен).

Подключил логический анализатор DSLogic, начал смотреть обмен на шине.

Сняв лог с выборкой 10ns в течение 1с, начал его просматривать и обнаружил интересные данные

Битовые данные на 8-битной шине повторяют надпись, которая отображается на экране.

Также увидел сигналы выбора верхнего/нижнего контроллера (на картинке - зеленым)

Сигнал отрицательной полярности, скорее всего – nWE, по его спаду (заднему фронту) контроллер защелкивает данные (на картинке - красным)

Далее по диаграмме нашел место, где начинается выборка нижнего контроллера.

После перехода должны идти команды. И действительно, последняя неизвестная линия – является сигналом выбора данные/команда. На картинке – оранжевым.

Подсчитал разрешение матрицы

Получилось, что GID у Astra H имеет разрешение 218х138 точек.

Анализируем полученные данные

Небольшая часть работы с паяльником уже проделана.

Предполагаю, что матрица экрана изготовлена под заказ GM, но стоит ли внутри известный контроллер или какой-то свой? Что ж, в этот момент я надеялся, что если контроллер и проприетарный, то писался с взглядом на существующие.

За полный цикл выдачи данных на одну верхнюю половину экрана, происходят 8 посылок следующего содержания: 3 байта команд и 208 байт данных.

Рассмотрим эти 3 байта команд. На картинке начало 4х посылок

В них последовательно инкрементируется единичка в первом командном слове. Таким образом становятся понятны MSB и LSB шины данных.

Полученная распиновка экрана на картинке:

Но вернемся к нашим четырем посылкам. Как же удобны цифровые анализаторы с программными декодерами.

Но посмотрим не только их, а все команды

8 команд перед посылками для верхней половины экрана:

[0xB0, 0x10, 0x05]

[0xB1, 0x10, 0x05]

[0xB2, 0x10, 0x05]

[0xB3, 0x10, 0x05]

[0xB4, 0x10, 0x05]

[0xB5, 0x10, 0x05]

[0xB6, 0x10, 0x05]

[0xB7, 0x10, 0x05]

8 команд перед посылками для нижней половины экрана:

[0xB0, 0x12, 0x05]

[0xB1, 0x12, 0x05]

[0xB2, 0x12, 0x05]

[0xB3, 0x12, 0x05]

[0xB4, 0x12, 0x05]

[0xB5, 0x12, 0x05]

[0xB6, 0x12, 0x05]

[0xB7, 0x12, 0x05]

Ширина экрана 218, но данных приходит только на 208. Но и на экране слева и справа отступы по 5 пикселей. Скорее всего 3-й байт данных – номер столбца, а младшие 4 бита в первом байте команды – номер «строки». Почему в кавычках – данные выдаются сразу на 8 строк. Получается, что это больше указатель на сектор строк.

После вышеуказанных 16 больших команд с данными приходит «дозаполнение» пустых ячеек.

Тут видно, что половины экрана «независимы». Приходят команды для каждой половинки, а затем данные идут сразу на две половинки.

Предположу, что внутри основного микроконтроллера NEC v850eca2 буфер видеокадра рассчитан на 208x128 точек, так как некоторые строки и столбцы прячутся за рамку экрана.

После полного заполнения «видимой части экрана», «дозаполнение» приходит на все оставшиеся пиксели (в том числе обрезки строк, шириной по 5 пикселей).

Экран обновляется раз в секунду.

Помимо буфера обновления экрана – иногда по шине приходят команды конфигурации экрана.

Из всех вышеперечисленных данных я нашел наиболее подходящий, по командам и описанию, контроллер - ST7565, только в моем случае – разрешение экрана побольше.

Но не все команды удалось найти в описании. Вот пример конфигурации экрана:

В машине, в настройках экрана можно включать инверсию. Теперь я понял, что передача буфера данных на экран не меняется – меняется только конфигурация для включения инверсии.

Пишем программный декодер

В целом, собранных данных достаточно, чтобы написать примитивный декодер. Его я буду писать на C# и выводить графику.

Для декодирования буду использовать собранный логическим анализатором дамп.

Выгружу его в формат csv и буду читать из файла.

Алгоритм работы программы, следующий:

С делал PictureBox размером 872x512 пикселей – в 4 раза больше, чем реальное разрешение экрана, буду выводить точки размером 4х4 (чтобы было не мелко). Сам видеобуфер размером 218x16 байт.

В обработчике PictureBox_Paint() буду отрисовывать данные из видеобуфера.

В основном цикле программы я пробегаю по всем строкам входного файла, разбираю значения, храню прошлое состояние nWE. Ищу спад (задний фронт), т.е. если старое значение 1, а новое 0, то выполняю действие.

Завел две глобальные переменные на номер строки и столбца.

Дополнительно у меня есть флаг валидности данных.

Фильтрую первую пришедшую команду по маске 0xF8. Если команда по маске 0xB0, беру из нее номер строки по маске 0x07. Если при этом выбрана верхняя часть экрана, то в номер строки пишу этот номер, если выбрана нижняя, то этот номер + 8. Следующие две команды – вспомогательные. Вторую не обрабатываю, из третьей беру номер столбца. Если эти три команды прошли, то поднимаю флаг валидности данных, что следующие данные можно писать в буфер. Каждая запись инкрементирует номер столбца.

Если в первую пришедшую команду по маске 0xF8 пришла команда отличная от 0xB0, то снимаю флаг валидности данных.

На картинке результат работы программного декодера.

Пишем аппаратный декодер

Для аппаратного декодера я использовал ПЛИС altera cyclone iv ep4ce10. У меня как раз лежит одна с выгоревшим jtag портом, но без проблем считывает конфигурацию с SPI флеш-памяти. У нее на борту есть аппаратные блоки двухпортовой SRAM-памяти. Это очень полезно, так как чтение и запись будут производиться разными модулями.

Так как уровни логических сигналов GID 5в, а у моей ПЛИС 3.3в, были использованы микросхемы для согласования уровней. У них есть сигнал OE. Его подключил к выходу ПЛИС, где генерирую постоянную лог «1». ПЛИС запускается не сразу, а сначала считывает конфигурацию. Чтобы в этот момент не было конфликтов (согласователи с автоматическим определением направления) – решил сделать так.

Для аппаратного декодера перенес с небольшими изменениями мою программную реализацию. Никаких оптимизаций, просто чтобы работало.

 

module decoder(
    input         clk,
    
    input  [7:0]  data,
    input         nWE,
    input         DC,
    input         CS_up,
    input         CS_down,
    
    output [12:0] mem_address,
    output [7:0]  mem_data,
    output        mem_we,
    output        mem_clk
);

reg [1:0]  cmd_pos = 2'd0;
reg        decoder_mem_we = 1'b0;
reg [7:0]  col_adr = 8'h00;
reg [3:0]  row_adr = 4'h0;
reg        reg_mem_clk = 0;
reg        mem_clk_cnt = 0;


always @(posedge nWE) begin
    if(DC == 1'b0) begin
        if(cmd_pos == 2'd0) begin
            if((data & 8'hF8) == 8'hB0) begin
                if(CS_up) begin
                    row_adr <= data & 8'h0F;
                    col_adr <= 0;
                end else if(CS_down) begin
                    row_adr <= (data & 8'h0F) + 8'd8;
                    col_adr <= 0;
                end
                cmd_pos <= 1;
            end 
            decoder_mem_we <= 1'b0;
        end else if(cmd_pos == 2'd1) begin 
            col_adr <= col_adr | (data & 8'h01)<<4;
            cmd_pos <= 2'd2;
        end else if(cmd_pos == 2'd2) begin
            col_adr <= col_adr | (data & 8'h0F);
            cmd_pos <= 2'd0;
            decoder_mem_we <= 1'b1;
        end
    end
    
    if(decoder_mem_we & DC) begin
        col_adr <= col_adr + 1;
    end
end

assign mem_we = decoder_mem_we & DC & ~nWE;

assign mem_address = {row_adr[3:0], col_adr[7:0]};
assign mem_data = data;

always @(posedge clk)begin
    if(~nWE) begin
        if(~mem_clk_cnt) begin
            if(~reg_mem_clk)begin
                reg_mem_clk <= 1'b1;
            end else begin
                reg_mem_clk <= 1'b0;
                mem_clk_cnt <= 1'b1;
            end
        end
    end else begin
        reg_mem_clk <= 1'b0;
        mem_clk_cnt <= 1'b0;
    end
end

assign mem_clk = reg_mem_clk;

endmodule

 

 

Код синтезировался и даже работает

Для отладки выводил данные, которые пишутся в SRAM на внешние выводы, но потом понял, что сразу в железе отладить не получится и придется писать тестбенч.

Взял добрый уютный iverilog. Очень мало времени работаю с ПЛИС, не знаю многих подходов, потому вместо чтения csv файла средствами iverilog – в моем программном декодере написал конвертер входных данных в строки для тестбенча (да закидают меня тапками, сейчас я уже знаю как правильно, но на тот момент это было быстрое рабочее решение)

Получил строки вида:

И с головой ушел в моделирование

В результате – модуль декодера стучится в SRAM, генерирует необходимые сигналы: адрес, данные, разрешение записи и clk для защелкивания.

Пишем аппаратный вывод данных

Вывод данных должен быть универсальным. В том числе, чтобы в машину (кто еще не потерял нить рассуждений и дочитал до этого момента, я же делаю все это для машины) можно было поставить китайскую магнитолу, а ее экран перенести наверх (если я не доберусь до постройки CarPC). Обычно у магнитол из доступных разъемов – только CVBS, а у используемого мной контроллера экрана RTD2662 доступно до 4х аналоговых видеовходов, то его и будем использовать.

Для аналогового вывода использовал 8-битный DAC R2R (но на каждый выход использовал 2 порта ввода-вывода, чтобы повысить ток. В общей сложности использовал 16 портов), а сигнал синхронизации - суммировал к текущему уровню.

Одна статья с хабра очень помогла быстро запустить CVBS и написать его заново под свои нужды.

Начал разбираться и заметил особенность. Что в моей реализации, что в реализации из статьи: если попытаться передать чередующиеся строки белая-черная-белая, то экран вместо строк начинает мерцать. Если 2 строки белые – 2 черные – 2 белые, то все ОК, но разрешение падает в 2 раза (мне не критично, т.к. мне нужно вообще 128 строк).

Под рукой всегда держал шпаргалку:

Затем сделал некоторые свои тесты с нужным разрешением экрана

Таким получился мой модуль вывода Monochrome Composite Video

 

module PAL(
    input  clk,
    output [15:0]mem_address,
    input  mem_data,
    output mem_clk,
    
    output [7:0]video_out
);

wire sync;
reg frame = 0;

wire [7:0]video_in;


assign video_in = (mem_data) ? BRIGHT : 8'd0; //test

wire [7:0]video;
assign video = (frame) ? video_in : 8'd0;
assign video_out = (sync) ? (video + 8'd25) : 8'd0;

localparam BRIGHT             = 8'h80;

localparam BROAD_SYNC_SECTION1 = 5'd0; //5
localparam SHORT_SYNC_SECTION1 = 5'd1; //5
localparam EMPTY_LINE1         = 5'd3; //18
localparam FULL_LINE1          = 5'd4; //287
localparam SHORT_SYNC_SECTION2 = 5'd5; //5
localparam BROAD_SYNC_SECTION2 = 5'd6; //5
localparam SHORT_SYNC_SECTION3 = 5'd7; //5
localparam EMPTY_SYNC_SECTION  = 5'd8; //1
localparam EMPTY_LINE2         = 5'd10; //17
localparam FULL_LINE2          = 5'd11; //287
localparam SHORT_LINE          = 5'd12; //1
localparam SHORT_SYNC_SECTION4 = 5'd13; //5

localparam X_END_HALF_LINE     = 12'd1600;
localparam X_END_FULL_LINE     = 12'd3200;

localparam IMAGE_START_CLK     = 12'd573;//12'd520;
localparam IMAGE_STOP_CLK      = 12'd2971;//12'd2923;  2644-900 = 8clk/1px

reg [12:0]x_end_counter        = 12'd0;
reg [12:0]y_end_counter        = 12'd0;
reg [12:0]sync_start_counter   = 12'd0;

reg [12:0]x_clk_count          = 12'd0;
reg [12:0]y_clk_count          = 12'd0;

reg [5:0] current_state        = 6'h00;
reg [5:0] next_state           = 6'h00;
reg [8:0] count_states_max     = 9'h000;

reg [12:0]image_clk_count      = 12'd0;

reg [9:0]x_pos = 10'h000;
reg [3:0]x_tmp_pos = 4'd0;

//assign x_pos[9:0] = image_clk_count[12:3];

wire [12:0]y_tmp_clk_count;
assign y_tmp_clk_count = y_clk_count - 16;
wire [9:0]y_pos;
assign y_pos[9:0] = y_tmp_clk_count[10:1];

always @(posedge clk) begin
    if(x_clk_count >= x_end_counter - 1) begin
        frame <= 0;
        x_clk_count     <= 12'd0;
        image_clk_count <= 12'd0;
        x_pos <= 10'd0;
        if(y_clk_count >= y_end_counter - 1) begin
            current_state <= next_state;
            y_clk_count <= 12'd0;
        end else begin
            y_clk_count <= y_clk_count + 1;
        end
        
    end else begin 
        x_clk_count <= x_clk_count + 1;
        if(x_clk_count >= IMAGE_START_CLK && x_clk_count < IMAGE_STOP_CLK && (current_state == FULL_LINE1 || current_state == FULL_LINE2))begin
            if(y_clk_count >= 16 && y_clk_count < 272) //16-271 ; 256/64 4line/1px
            begin
                frame <= 1;
            end else begin
                frame <= 0;
            end
            //if(image_clk_count )
            
            if(x_tmp_pos == 4'd10)begin
                x_tmp_pos <= 0;
                x_pos <= x_pos + 1;
            end else begin
                x_tmp_pos <= x_tmp_pos + 1;
            end
            
            image_clk_count <= image_clk_count + 1;
        end else begin
            frame <= 0;
        end
        
    end
end

assign sync = (x_clk_count >= sync_start_counter) ? 1'b1 : 1'b0;

always @(*) begin
    case (current_state)
        BROAD_SYNC_SECTION1: begin //0 - 2.5
            next_state         = SHORT_SYNC_SECTION1;
            x_end_counter      = X_END_HALF_LINE;
            y_end_counter      = 12'd5;
            sync_start_counter = 12'd1365;
        end
        SHORT_SYNC_SECTION1: begin //2.5 - 5
            next_state         = EMPTY_LINE1;
            x_end_counter      = X_END_HALF_LINE;
            y_end_counter      = 12'd5;
            sync_start_counter = 12'd120;
        end
        EMPTY_LINE1: begin //7-23
            next_state         = FULL_LINE1;
            x_end_counter      = X_END_FULL_LINE;
            y_end_counter      = 12'd18;
            sync_start_counter = 12'd235;
        end
        FULL_LINE1: begin //24-310
            next_state         = SHORT_SYNC_SECTION2;
            x_end_counter      = X_END_FULL_LINE;
            y_end_counter      = 12'd287;
            sync_start_counter = 12'd235;

        end
        SHORT_SYNC_SECTION2: begin //311-312.5
            next_state         = BROAD_SYNC_SECTION2;
            x_end_counter      = X_END_HALF_LINE;
            y_end_counter      = 12'd5;
            sync_start_counter = 12'd120;
        end
        BROAD_SYNC_SECTION2: begin //312.5-315
            next_state         = SHORT_SYNC_SECTION3;
            x_end_counter      = X_END_HALF_LINE;
            y_end_counter      = 12'd5;
            sync_start_counter = 12'd1365;
        end
        SHORT_SYNC_SECTION3: begin //316-317.5 
            next_state         = EMPTY_SYNC_SECTION;
            x_end_counter      = X_END_HALF_LINE;
            y_end_counter      = 12'd5;
            sync_start_counter = 12'd120;
        end
        EMPTY_SYNC_SECTION: begin //317.5
            next_state         = EMPTY_LINE2;
            x_end_counter      = X_END_HALF_LINE;
            y_end_counter      = 12'd1;
            sync_start_counter = 12'd0;
        end
        EMPTY_LINE2: begin
            next_state         = FULL_LINE2;
            x_end_counter      = X_END_FULL_LINE;
            y_end_counter      = 12'd17;
            sync_start_counter = 12'd235;
        end
        FULL_LINE2: begin
            next_state         = SHORT_LINE;
            x_end_counter      = X_END_FULL_LINE;
            y_end_counter      = 12'd287;
            sync_start_counter = 12'd235;
        end
        SHORT_LINE: begin
            next_state         = SHORT_SYNC_SECTION4;
            x_end_counter      = X_END_HALF_LINE;
            y_end_counter      = 12'd1;
            sync_start_counter = 12'd235;
        end
        SHORT_SYNC_SECTION4: begin
            next_state         = BROAD_SYNC_SECTION1;
            x_end_counter      = X_END_HALF_LINE;
            y_end_counter      = 12'd5;
            sync_start_counter = 12'd120;
        end
    endcase
end

assign mem_address[15:0] = {y_pos[6:3], x_pos[7:0], y_pos[2:0]};

assign mem_clk = clk & frame;

endmodule

 

 

Много лишнего, не оптимально, так что прошу строго не судить.

Модуль верхнего уровня и память

Память двухпортовая. Причем на вход она 8-битная, а на выход – 1-битная.

Отлаживать и моделировать пришлось долго, так как было много глупых ошибок, вроде такой:

 

Так как моделирую в iverilog, то накидал простую модель

 

module SRAM(
    input    [12:0]  address_a,
    input    [7:0]   data_a,
    input            clock_a,
    input            wren_a,
    output    [7:0]   q_a,
    
    input    [15:0]  address_b,
    input    [7:0]   data_b,
    input            clock_b,
    input            wren_b,
    output            q_b
    
);

reg [7:0]mem[8191:0];

reg [7:0]r_q_a = 8'h00;
assign q_a = r_q_a;
reg      r_q_b = 1'b0;
assign q_b = r_q_b;

always @(posedge clock_a) begin
    r_q_a <= mem[address_a];

    if(wren_a == 1'b1)begin
        mem[address_a] <= data_a;
    end
end

always @(posedge clock_b) begin
    
    r_q_b <= mem[address_b[15:3]][address_b[2:0]];
    
    if(wren_b == 1'b1)begin
        mem[address_b] <= data_b;
    end
end

endmodule

 

 

Вот как выглядит мой TOP-модуль

module GID(
    input clk,
    input [7:0]lcd_data,
    input lcd_nWE,
    input lcd_DC,
    input lcd_CS_up,
    input lcd_CS_down,
    output [7:0]LED,
    output driver_oe,
    output [7:0]video_out,
    output [7:0]video_out2
);

assign driver_oe = 1'b1;

wire [7:0]video;
assign video_out = video;
assign video_out2 = video;

wire [7:0] test_ledout;
assign test_ledout[7:0] = address_a[7:0];
assign LED = ~test_ledout;

wire [12:0] address_a;
wire [7:0]  data_a;
wire        wren_a;
wire        mem_clk_a;

wire [15:0] address_b;
wire        data_b;
wire        mem_clk_b;

decoder decoder(
    .clk         ( clk         ),
    
    .data        ( lcd_data    ),
    .nWE         ( lcd_nWE     ),
    .DC          ( lcd_DC      ),
    .CS_up       ( lcd_CS_up   ),
    .CS_down     ( lcd_CS_down ),
    
    .mem_address ( address_a   ),
    .mem_data    ( data_a      ),
    .mem_we      ( wren_a      ),
    .mem_clk     ( mem_clk_a   )
);

SRAM SRAM(
    .address_a   ( address_a   ),
    .data_a      ( data_a      ),
    .clock_a     ( mem_clk_a   ),
    .wren_a      ( wren_a      ),
    .q_a         (  ),
    
    .address_b   ( address_b   ),
    .data_b      ( ),
    .clock_b     ( mem_clk_b   ),
    .wren_b      ( 1'b0        ),
    .q_b         ( data_b      )
);

PAL PAL(
    .clk         ( clk         ),
    
    .mem_address ( address_b   ),
    .mem_data    ( data_b      ),
    .mem_clk     ( mem_clk_b   ),
    
    .video_out   ( video       )
);

endmodule

Схема в блоках выглядит даже не плохо, если не залезать внутрь

Результат

Ну и вот что мы имеем на выходе:

Понимаю, что все не очень оптимально, сделано на коленке за 3-4 вечера, но я очень доволен результатом и много чего полезного узнал.

Спасибо за внимание.

 


Раскрыть

Занудства чуток — сектор строк называется спрайт

+1

Новые комментарии

Синдром «винтика»: почему тихое решение отойти в сторону — это тоже поступок?

RuCyberPioneer
1 день назад

Цитата Нимёллера сильная, но вы путаете две абсолютно разные вещи: равнодушное соучастие и осознанное несотрудничество.
Пастор Нимёллер писал о людях, которые продолжали работать на систему, платить ей налоги, выполнять её приказы и молча отворачиваться, пока она уничтожала их соседей. Это как раз позиция трусливого «винтика», о котором я писал в посте. Эти люди не сделали «тихий выбор» — они выбрали быть частью машины, надеясь, что их не тронут.
А концепция Ганди, Толстого или того же Бориса Пастернака — это совершенно другое. Это активный отказ быть шестеренкой. Это когда ты говоришь: «Я не буду писать на соседа донос, я не буду подписывать этот подлый протокол, я не пойду ловить людей, я уволюсь с этой работы и пойду сажать картошку». Если бы в Германии 1930-х годов машинисты поездов, клерки в министерствах и обычные рабочие массово сделали такой «тихий выбор» и просто остались дома со своими семьями — нацисты физически не смогли бы построить свои лагеря. Им бы просто некем было выполнять приказы.
 Истинный выбор — это не поставить галочку на чужом бланке раз в несколько лет. Это каждый день выбирать оставаться Человеком на своем рабочем и бытовом месте.
На этом дискуссию со своей стороны закрываю. Каждый выбирает сам. Удачи на выборах!

Синдром «винтика»: почему тихое решение отойти в сторону — это тоже поступок?

BuhBuhov
1 день назад

Когда нацисты хватали коммунистов, я молчал: я же не был коммунистом.
Когда они сажали социал-демократов, я молчал: я же не был социал-демократом.
Когда они хватали членов профсоюза, я молчал: я же не был членом профсоюза.
Когда они пришли за мной — заступиться за меня было уже некому.

© Нимёллер Мартин

Да, я не пойду с вилами на сельсовет. Но на выборы схожу обязательно.

+1
Синдром «винтика»: почему тихое решение отойти в сторону — это тоже поступок?

RuCyberPioneer
1 день назад

Большинство людей добровольно сдают свои принципы в краткосрочную аренду ради иллюзии безопасности. Кажется: ну, я сейчас молча подпишу эту сомнительную бумагу по указке сверху, промолчу на собрании, где откровенно травят человека, или поддакну агрессивной толпе — и меня пронесет, я в домике. Не пронесет. Как только человек соглашается стать послушным слепым винтиком, он перестает принадлежать себе и система перемалывает его личность первой.Истинная зрелость и здравый смысл начинаются там, где ты осознаешь: у тебя всегда есть выбор не приумножать бред. Не нужно лезть на баррикады — достаточно просто вернуть себе управление своей жизнью. Перестать кормить чужие маятники своими эмоциями, заняться делом, созидать на своем месте и не соучаствовать в том, что разрушает твою личность. Это позиция хозяина своей жизни. Тот, кто этого до сих пор не понял, так и проживет жизнь чужим расходным материалом

У вас только помирать!

BuhBuhov
3 дня назад

Ну не знаю, не знаю. Чтобы записаться к гастроэнтерологу, сначала пришлось записаться к терапевту. Благо к нему за день-два можно записаться. Терапевт выдал направление, я пошел на сестринский пост и меня записали через две недели. Не два месяца конечно, но и не пара часов. Если человеку хреново уже сейчас, а к врачу можно попасть только через две недели, это как-бы намекает на безысходность. Даже к платному гастроэнтерологу ближе чем на 2-3 дня не записаться. Но цены там сейчас такие, что не каждый может себе позволить… По факту, если что хреновое приключится, то только скорая может помочь, а так можно ложиться и помирать.

+1
Cетевой игрулькин, нейрослоп.

WseGa
4 дня назад

Иногда бывает, да. Надо будет подумать, может что то типа виньетки сделать как в вр — там помогает тем кто не справляется с вестибулярной.

+2
Cетевой игрулькин, нейрослоп.

WseGa
4 дня назад

Автоген иногда сбоит:-( 

+2
Cетевой игрулькин, нейрослоп.

BuhBuhov
4 дня назад

Я побегал, прикольно. Но у меня быстро начало рябить в глазах и порой кружилась голова. Привык давить кнопки быстро, отчего экран перед глазами вращается как бешенный.

+2
Cетевой игрулькин, нейрослоп.

Zakradnopodkradnoy
4 дня назад

Можно позалипать, но нашёл небольшой баг — противник не может из помещения выйти, так как нет дверей :)

+1
6 роз

BuhBuhov
4 дня назад

Bahama! Bahama Mama!
На бутылку не дает родная мама!

+1
Нейрослоповая залипуха (Игра)

WseGa
4 дня назад

Надо в англ раскладке чтобы было

+1
Проклятый старый дом

Admin
7 дней назад

видел кино наше, где чёрныые риэлторы так и работли — пенсам давали норм вариант в обмен на их старый, но там была «закладка»  в стене с радиационной хтонью

+2
Проклятый старый дом

Surinamski
8 дней назад

В стенке был замурован источник гамма-излучения

+2
Проклятый старый дом

DoctorrHamster
8 дней назад

И чё там было? 

+2
Проклятый старый дом

Surinamski
9 дней назад

В одной квартире а Краматорске тоже умирали люди один за одним....

+2
Новый страшный супь

DoctorrHamster
12 дней назад

Короче, у желчного может быть тупо спазм. Отсюда и боли 

+2