К ленте · Россия

РоссияНовостьМодели

Doom, System Shock и Ultima VII на ПЛИС за $15: компьютер из 8640 логических ячеек

Процессор RISC-V на 36 МГц с кешами, 8 МБ памяти, HDMI 720×480 при 60 Гц со стереозвуком 48 кГц, два порта USB и SD-карта. Всё это собрано из 8640 таблиц поиска (LUT) отладочной платы с ПЛИС за 15 долларов. Чтобы погонять систему как следует, я портировал на неё Doom, Descent, Tyrian, System Shock и Ultima VII, и все они идут вполне прилично.

От переводчика: это объединение первой и шестой частей серии m9b. В первой автор описывает устройство компьютера, в шестой рассказывает, как на него переносили игры. Части о процессоре, контроллере памяти, HDMI и USB-хосте остались в оригинале, ссылки на них сохранены.

Видео с платы: Doom, timedemo и игра · Descent, первый уровень · Tyrian на полной скорости · System Shock

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

Семейство Tang Nano

Платы Tang Nano от Sipeed — это ПЛИС Gowin на плате размером с пластинку жвачки, с портом USB-C для прошивки и набором разъёмов, который зависит от модели. По объёму логики четыре платы различаются почти в двадцать раз:

Площадь каждого квадрата равна числу 4-входовых таблиц поиска (LUT4) на плате. Жёлтые прямоугольники — кристаллы памяти в корпусе. Они нарисованы одного размера, потому что на каждой плате по 8 МБ; у 9K эта память разделена на два кристалла. Cortex-M3 у 4K — аппаратное ядро: оно расположено рядом с логикой, а не собрано из неё.

Плата

ПЛИС

LUT4

Блочная память

Память в корпусе

Аппаратный CPU

Tang Nano 1K

GW1NZ-1

1152

8 КБ

нет

нет

Tang Nano 4K

GW1NSR-4C

4608

20 КБ

8 МБ HyperRAM

Cortex-M3

Tang Nano 9K

GW1NR-9

8640

52 КБ

8 МБ PSRAM (2 кристалла)

нет

Tang Nano 20K

GW2AR-18

20 736

92 КБ

8 МБ SDRAM

нет

Блочная память считается по 2 КБ на блок, без битов чётности.

Основная часть ретрокомпьютерных проектов на Tang Nano живёт на 20K: ядер под неё довольно много. 1K хватает на маленький процессор, но не на компьютер вокруг него, к тому же у неё, в отличие от остальных плат серии, нет разъёма HDMI. У 4K прямо в кремнии есть Cortex-M3, и это была бы отличная основа для небольшого компьютера, но из-за того, как там устроена шина адреса, с ним сложно работать. Про эту плату я, скорее всего, ещё напишу.

У 9K аппаратного процессора нет. Если процессор нужен, его придётся собирать из тех же 8640 ячеек, что и всё остальное. Эту задачу я и выбрал, поставив себе несколько непреложных условий:

  • Никаких дополнительных вычислителей. Соблазнительно перепрошить и задействовать BL702, но этого не будет. Работает только ПЛИС.
  • Компьютер общего назначения. Игры служат тестами, и специальных аппаратных ускорителей под какую-то одну из них я делать не хочу.
  • Настоящий ввод и вывод. HDMI со звуком, USB-клавиатуры и мыши, SD-карта с настоящей файловой системой.
  • Портировать готовое. Взять существующий исходный код на C/C++ и перенести его на плату. Немного оптимизации допустимо, но игра должна выглядеть, звучать и вести себя в точности как оригинал.

Вот и всё. Отправной точкой могла бы стать готовая система, сгенерированная LiteX: он эту плату поддерживает. Но производительность результата, увы, довольно ужасная (по крайней мере, на момент написания).

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

Микросхема на плате, GW1NR-LV9QN88P, на самом деле состоит из трёх кристаллов в одном корпусе: самой ПЛИС и двух PSRAM по 32 Мбит (4 МБ каждая). У каждой PSRAM своя 8-битная шина с удвоенной скоростью передачи данных (DDR) до ПЛИС, рассчитанная на частоту до 166 МГц. Ни один из этих проводников не выходит за пределы корпуса: на плате микросхема выглядит как обычная ПЛИС с 88 выводами.

Два кристалла PSRAM сидят на разных шинах, поэтому могут передавать данные одновременно. В m9b оба работают на 144 МГц синхронно, в одном такте.

  • 8640 4-входовых таблиц поиска;
  • 52 КБ блочной памяти: 26 блоков по 2 КБ;
  • 8 МБ PSRAM на двух кристаллах;
  • 664 МБ/с — пиковая пропускная способность PSRAM на бумаге: 166 МГц × 2 фронта × 2 кристалла.

Эти цифры задают рамки для всего остального. 52 КБ блочной памяти — меньше, чем было у типичного домашнего компьютера 1980-х. Один кадр VGA, какой используют наши тестовые игры, занимает 320 × 200 байт и не поместился бы даже во все 26 блоков вместе. Поэтому программы и их данные живут в PSRAM, а блочная память уходит на кеши и буферы. 664 МБ/с у PSRAM — число на бумаге: в третьей части я прослеживаю его вплоть до того, что реально достаётся процессору, а это лишь малая доля.

Никакой выделенной аппаратуры для остального нет. Сериализаторы в микросхеме достаточно быстрые для HDMI и памяти, но кодировщик собран из логики. Звук передаётся внутри сигнала HDMI, так что это тоже логика. USB-хоста на плате нет: разъёмы USB подключены прямо к выводам ввода-вывода, и протокол тоже обрабатывает логика.

Система

Вот весь компьютер, разбитый по тактовым доменам.

Пунктиром обведены тактовые домены, жёлтым отмечена блочная память или PSRAM, красным — то, что находится на плате, вне микросхемы. Мост и буферная память передачи связывают домены: мост — процессор и контроллер, у которых общее дерево тактирования, а буферная память — контроллер и видеотракт, у которых оно разное.

Верхний уровень устроен довольно просто. Я взял высокопроизводительное ядро RISC-V (VexiiRiscv) и отдал заметную часть блочной памяти под щедрый кеш (24 КБ из наших 52), потому что задержка DDR-памяти довольно пугающая, и обращаться к ней слишком часто совсем не хочется. Во второй части возможности процессора и устройство подсистемы разобраны гораздо подробнее.

Контроллер PSRAM работает с обоими кристаллами синхронно: он формирует один поток команд и дублирует его на оба кристалла. Физический уровень (PHY) — просто набор сериализаторов и десериализаторов. Внутреннее устройство контроллера описано в третьей части. Кроме того, контроллер PSRAM связан с графическим и звуковым блоком через буферную память передачи, в которой помещаются две строки видеоданных и два звуковых буфера. У видеоблока есть и собственная память для палитры и таблицы строк; процессор пишет в неё напрямую, чтобы сократить задержку. Кодировщик HDMI — простой TMDS-кодировщик, который превращает пиксельные данные в сигнал HDMI. Эту подсистему разбирает четвёртая часть. USB-хост — ядро на микрокоде, которое обрабатывает протокол USB и даёт процессору простой регистровый интерфейс; ему посвящена пятая часть. Набор встроенной периферии завершают UART для отладки и SPI для работы с флеш-памятью и SD-картой.

Тактирование

Все тактовые частоты получаются из единственного кварца платы на 27 МГц через две ФАПЧ (PLL) микросхемы и три её делителя. Некоторые частоты нам неподвластны, и их нужно соблюдать, иначе система не заработает. Одна из них — пиксельная частота стандартного режима VESA (для выбранного мной режима 720×480 это 27 МГц). Значит, для сериализаторов нужна частота 5 × 27 = 135 МГц. Другая — 12 МГц для USB, стандартная для full-speed USB. Все остальные частоты приходится получать делением или умножением этих, причём обе ФАПЧ уже заняты: 12 и 27 не кратны друг другу. В итоге всё оборудование я тактировал кратными частоты USB: 36, 72 и 144 МГц. Хорошо согласованные частоты очень важны, потому что каждое пересечение тактовых доменов стоит задержки.

Сигнал

МГц

Откуда

Почему такое значение

Битовая частота TMDS

135

pll_hdmi: 27 × 5

HDMI передаёт 10 бит на пиксель по каждому каналу: 270 Мбит/с, по обоим фронтам 135 МГц

Пиксельная частота

27

135 ÷ 5

720 × 480 при 59,94 Гц — это режим на 27 МГц. Получена делением битовой частоты, поэтому два такта сериализаторов синфазны по построению

Ввод-вывод PSRAM

144

pll_main: 27 × 16 ÷ 3

Номинал 166 МГц; 144 — самая высокая частота, которая ещё делится до 48 для USB

Контроллер PSRAM

72

pll_main ÷ 2

Сериализаторы ввода-вывода принимают по 4 бита на вывод за такт контроллера

Процессор

36

72 ÷ 2

Столько, сколько процессор выдерживает по таймингам (37,8 МГц в последней сборке), и синхронно с контроллером

USB

12

pll_main ÷ 3, затем ÷ 4

Low-speed USB — это 1,5 Мбит/с с выборкой 8 раз на бит: ровно 12 МГц

Куда уходят ячейки

После размещения и трассировки проект занимает 7560 из 8640 логических ячеек (88%) и 25 из 26 блоков памяти. На деле ограничение жёстче: ячейки упаковываются парами в слайсы, и заняты 97% этих пар. При таком заполнении итоговая частота сборки сильно зависит от того, куда инструменты случайно поставят логику, поэтому каждая сборка заканчивается изучением отчёта о таймингах.

Блок

Логических ячеек

Блоков памяти

Что хранится в блочной памяти

Процессор (с кешами)

≈ 4340

18

L1 данных: 8; теги L1 данных: 2; L1 выборки команд: 4; теги L1 выборки: 1; предсказатель переходов: 1; регистры: 2

Кодировщик HDMI

≈ 630

–

Видеоблок, видеопамять

≈ 490

2 + 4

палитра и таблица строк: 2; буферная память передачи (строки, звук): 4

Контроллер PSRAM, мост

≈ 560

–

строчные буферы моста — в LUT-памяти

USB-хост, два порта

≈ 490

1

ПЗУ микрокода

UART, SPI, чтение флеш-памяти

≈ 270

–

Связующая логика: декодирование адресов ввода-вывода, пересечения доменов, сбросы

≈ 690

–

Цифры по блокам взяты из синтеза и приблизительны: размещение и трассировка оптимизируют проект поверх границ блоков, итог по ним — те самые 7560.

Больше половины микросхемы занимает процессор. Всё остальное я сделал настолько компактным, насколько смог, и по дороге пожертвовал возможностями, которые не были нужны прямо сейчас. Например, в первых версиях был аппаратный текстовый режим, но его пришлось вырезать (заодно на стартовом экране появился более симпатичный пропорциональный шрифт). Ещё я убрал любой синтез звука, оставив только простой вывод волновых данных.

Игры: ретро наоборот

Все пять игр написаны для DOS-компьютеров начала 1990-х. Их современные порты рассчитаны на современную машину. Чтобы запустить их на m9b, часть этой модернизации пришлось откатить и посадить игры на диету по памяти, тактам и звуковому оборудованию, которая местами строже, чем на тех ПК, где они вышли впервые.

Игра

Порт

Скорость на m9b

Что оказалось важнее всего

Doom (1993, shareware)

doomgeneric

≈ 22 fps (timedemo)

процедуры отрисовки столбцов и полос; кеш данных

Descent (1995, shareware)

D1X Rebirth 0.40

≈ 10 fps, было 5

деления в наложении текстур; кабина

Tyrian 2.1 (1995)

OpenTyrian

35 fps, полная скорость, было 17

одна проверка границ на каждый пиксель

System Shock (1994)

Shockolate

≈ 7,4 fps, было 5,8

память: ролики на 17 МБ; невыровненные чтения; деления

Ultima VII (1992), обе игры

Exult 1.12.1

7–13 fps

память: куча 6,2 МБ в объёме меньше 6 МБ

Ultima VII, оригинальный движок

Ultima7_Decomp

8–10 fps (предел самого движка — 10)

блоки кучи, выровненные по слову; спрайты и тайлы по слову за раз

m9b выполняет команды строго по порядку, не имеет блока плавающей точки, работает на 36 МГц, и 8 МБ его памяти скрыты за кешами, промах в которых стоит около 25 тактов (третья часть). Звукового чипа и MIDI-синтезатора у него нет. Зато есть 32-битные аппаратные умножитель и делитель, достаточно быстрая SD-карта и видеоблок, который бесплатно переключает страницы и масштабирует изображение.

Программы именно портированы. Каждая игра — это её исходный код из основного репозитория, урезанный до того, что нужно для сборки, с пометкой на каждом изменении, и собственный платформенный слой вместо SDL.

Все пять игр используют общую небольшую среду выполнения, изначально написанную для Doom:

  • Библиотека C и файлы. picolibc, системные вызовы которой отображены на консольный UART и на FatFs, так что open, read и lseek работают с SD-картой. Порты на C++ (физика System Shock, весь Exult) используют libstdc++ из picolibc, включая исключения.
  • Обработчик ловушек. Он распределяет прерывания и эмулирует невыровненные загрузки и сохранения: процессор на них выдаёт исключение, а старый игровой код полон таких обращений.
  • Видео. Каждая игра рисует свой экран 320 × 200 с 8-битным цветом, как и раньше. Порт копирует его в один из трёх буферов отображения и вычищает из кеша данных, а прерывание обратного хода кадра направляет на него таблицу строк. Каждая строка удваивается по горизонтали и выводится на две или три строки экрана: получается та же картинка 4:3, что давал VGA-монитор (четвёртая часть).
  • Звуковые эффекты смешиваются в звуковом прерывании на частоте 12 или 24 кГц вместо 48, и каждое значение повторяется на 4 или 2 выходных отсчёта. Сами эффекты изначально записаны с частотой 11 или 22 кГц, так что ничего не теряется. Смешивание же стоит несколько процентов процессорного времени вместо десятой части.
  • Загрузка. Программы хранятся на SD-карте и выбираются в загрузочном меню. Драйвер SD читает последовательности секторов одной командой, по 32 бита за раз: по мере его доработки загрузка уровня в Doom сократилась с 4,6 с до 1,7 с.

Музыку во всех пяти играх писали под синтезатор: FM-чип Yamaha OPL на AdLib или Sound Blaster либо Roland MT-32. Ни один из них не помещается. Открытому ядру OPL2 на этой микросхеме нужно 1337 ячеек и 4 блока памяти, а свободно примерно тысяча ячеек и один блок. Программный синтезатор отнял бы 10–15% процессора ещё до того, как игра что-нибудь нарисует, а эмуляторы в самих портах считают в плавающей точке.

Поэтому музыка один раз рендерится на ПК, а во время игры проигрывается потоком с SD-карты. Для четырёх игр это эмуляция чипа OPL3 из libADLMIDI, которая играет собственными инструментами каждой игры и по правилам громкости её звукового драйвера: банк GENMIDI у Doom, банки HMI у Descent, тембры AIL у System Shock. Tyrian рендерится собственным проигрывателем и эмулятором OPL. Для Ultima VII взяты записи Exult с настоящего Roland MT-32, того синтезатора, под который музыку и писали. Каждый трек превращается в WAV 24 кГц моно с общей громкостью и проигрывается через кольцевой буфер в PSRAM: основной цикл его пополняет, а звуковое прерывание опустошает.

Объёмы получаются немаленькие: 81 МБ для Doom, 69 для Descent, 128 для Tyrian, 95 для System Shock, 208 для Ultima VII. Но SD-карты большие, так что проблемой это не стало.

System Shock потребовал ещё одного шага. Музыка в нём адаптивная: каждая тема состоит из 54 коротких фрагментов или меньше, и музыкальный ИИ запускает их максимум в восьми потоках (основная партия и слои поверх неё), выбирая продолжение по тому, что происходит в игре. Каждый фрагмент отрендерен отдельно (227 файлов), а порт реализует интерфейс музыкального драйвера поверх них: собственный ИИ игры по-прежнему выбирает фрагменты, порт проигрывает до восьми из них одновременно и смешивает.

Основная работа Doom идёт в двух циклах: R_DrawColumn рисует стены и спрайты по вертикальным столбцам, а R_DrawSpan — полы и потолки по горизонтальным полосам. Вместе они занимают 40% кадра.

Изменение (процессор тогда работал на 27 МГц)

2000 кадров

fps

Исходная версия

181,0 с

11,0

Процедуры отрисовки столбцов и полос: локальные переменные, 4 пикселя за итерацию

157,2 с

12,7

Переключение страниц в прерывании обратного хода кадра

144,5 с

13,8

Отключение проверок диапазонов

141,3 с

14,2

Отрисовка прямо в буферы отображения

138,0 с

14,5

Дальше эстафету приняло железо: процессор перешёл на синхронную работу на 36 МГц, у кеша данных появился второй путь (двухпутная ассоциативность), чтение с SD ускорилось, memset и memcpy получили cbo.zero, появился делитель radix-4. Сейчас timedemo идёт примерно на 22 fps. Пока в кеше был один путь, сдвиг буфера на несколько сотен байт менял скорость Doom на 2%: один столбец экрана затрагивает 200 строк кеша, и за них дралось всё остальное, что было горячим.

Descent — настоящая трёхмерная игра с перспективно-корректным наложением текстур, и написанная на C процедура наложения в D1X делит на глубину для каждого пикселя, по 70 тактов и больше на деление. Поначалу наложение текстур занимало две трети времени, а игра шла на 5 fps.

  • Наложение текстур теперь вычисляет точные текстурные координаты раз в 16 пикселей по одной обратной величине, а между ними шагает линейно. Обе координаты упакованы в одно слово, как в процедуре отрисовки полос Doom, четыре пикселя за итерацию.
  • Деления 64 на 32 бита, которые оригинал делал на ассемблере, теперь занимают два аппаратных деления вместо побитового цикла.
  • Кабина — полноэкранный битмап с прозрачным окном под трёхмерный вид, и D1X перерисовывал её со всеми приборами каждый кадр. Теперь её непрозрачные участки рендерятся один раз, перерисовывается она только там, куда может дотянуться трёхмерный вид, а наложение текстур пропускает куски строк, которые кабина всё равно закроет. Кабина и приборы занимали 8% времени, теперь 1%.
  • Опрос клавиш при каждом запросе проверял все 256 клавиш и отнимал 6% времени; теперь проверяются только нажатые.

На первом уровне с полной кабиной выходит около 10 fps.

Профиль Tyrian показал, что две трети времени уходят на одну функцию: она рисует три прокручивающихся слоя фона по байту за раз и сверяет каждый пиксель с краями экрана. Теперь для строк, целиком лежащих на экране, проверки пропускаются, и скорость выросла с 17 до 35 fps. Это полная скорость игры, заданная её собственным таймером, и ещё остаётся запас. Музыку Tyrian тоже пришлось рендерить: эмулятор OPL в OpenTyrian считает огибающие в плавающей точке на каждый отсчёт.

Shockolate — это портируемая версия исходников для Macintosh, которые Night Dive выпустила в 2018 году. Версия для Mac, а вслед за ней и порт рассчитывали на гораздо больший объём памяти, чем DOS-версия 1994 года: запуск первого уровня требовал 5,3 МБ кучи, а ролик — 17 МБ. В m9b для игры есть 5,1 МБ кучи.

  • Таблица ресурсов содержала запись для каждого из 32 768 возможных идентификаторов, это 1,1 МБ. Идентификаторы System Shock доходят примерно до 5500.
  • Составные ресурсы (варианты текстуры разных размеров, кадры существа) читались целиком, даже когда нужен был один элемент, и этот элемент распаковывался в копию. Теперь читается только индекс, а каждый элемент — при первом обращении.
  • Ролики загружались целиком (14 МБ на заставку), их звук распаковывался ещё в 2,5 МБ, и всё это оставалось в памяти после просмотра. Теперь они читаются из файла по ходу воспроизведения, и используются ролики низкого разрешения из DOS-версии: декодировать их вчетверо меньше.

Теперь первый уровень занимает 2,8 МБ, ролик — около 0,2 МБ. Скорость выросла за счёт двух вещей. Распаковщик текстур читал слова по нечётным адресам, и на него приходилось 97% невыровненных обращений, которые эмулировал обработчик ловушек. А деления с фиксированной точкой шли через программную 64-битную процедуру вместо аппаратного делителя. После этих исправлений и с делителем radix-4 в процессоре сохранённая игра ускорилась с 5,8 до 7,4 fps. Двойным щелчкам понадобилось отдельное исправление: при 7 fps оба щелчка успевали произойти между двумя опросами мыши, поэтому теперь кнопки опрашиваются в звуковом прерывании вместе с временем нажатия.

Оригинальная Ultima VII — это 16-битный код реального режима на собственном менеджере памяти Origin, и исходников у него нет. Exult заново реализует её движок на C++17 и играет на оригинальных данных. Запуск The Black Gate требовал 6,2 МБ кучи. m9b может выделить ей 5,8 МБ, а распределитель памяти из библиотеки C терял ещё 540 КБ на дырах, пока его не заменил malloc Дага Ли.

  • Usecode — скрипты игр, 1,5 МБ байт-кода — читались целиком при старте. Теперь каждая функция читается, когда до неё доходит вызов, а давно не использовавшиеся выгружаются, чтобы держать в памяти не больше 192 КБ.
  • Спрайты, прочитанные из файла фигур на 7,3 МБ, оставались в памяти навсегда. Теперь сверх 384 КБ выгружаются давно не использовавшиеся.
  • Ландшафт занимал 4 КБ на каждый вид фрагмента карты, а в Тринсике их 120; теперь это 3 байта на тайл. Отрисованные полы кешируются для 24 фрагментов вместо 100.

Через несколько минут игры в Тринсике пик потребления — 4,1 МБ, и ещё остаётся место для музыки MT-32 и звуковых эффектов. Игра идёт на 7–13 fps. На каждый объект Exult делает больше работы, чем движок 1992 года на 386-м, и это следующее, чем стоит заняться.

Оригинальный движок 1992 года тоже работает. Ultima7_Decomp — декомпиляция обеих DOS-игр в переносимый C и C++, а m9b-u7 даёт ей платформенный слой для платы. Игра получает ожидаемую DOS-память в арене на 4 МБ, и правок понадобилось немного: её дальняя куча держит блоки выровненными по слову, так что процессор не выдаёт исключений. Участки спрайтов и тайлы рисуются по слову за раз. При прогулке по Тринсику выходит 8–10 fps при собственном ограничении движка в 10 шагов в секунду. Музыка и звуковые эффекты — те же записи MT-32, что и в порте Exult.

  • Timedemo Doom проигрывает фиксированную демозапись и считает контрольную сумму каждого кадра. Так любое изменение железа или программ можно сравнить по времени прогона и при этом доказать, что картинка та же.
  • Тестовые обвязки на хосте запускают System Shock и Ultima VII с тем же кодом порта на 32-битном x86 Linux, ограничивая кучу объёмом, как на плате, и заполняя новую память мусором, как это было бы на ней. Диеты по памяти делались там, а потом проверялись на плате.
  • Дистанционное управление. Запущенная игра отвечает на запросы скриншотов через последовательный порт и принимает оттуда нажатия клавиш, так что игрой можно управлять и проверять её автоматически, не глядя на экран.
  • Профилирование. Профилировочная сборка снимает счётчик команд из прерывания и отправляет гистограмму через последовательный порт.

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

Исходники и благодарности

Исходники, RTL и порты игр лежат на GitHub: m9b-system объединяет SoC (m9b-core) и порты игр, у каждого из которых свой репозиторий.

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

  • SpinalHDL/VexiiRiscv (Charles Papon): процессор, собранный из форка с опциями m9b;
  • hdl-util/hdmi (Sameer Puri): кодировщик HDMI со звуком;
  • nand2mario/usb_hid_host (на основе работы hi631): USB-хост;
  • FatFs (ChaN): файловая система FAT на SD-карте;
  • picolibc: библиотека C и скрипт компоновщика программ;
  • Wohlstand/libADLMIDI: эмуляция OPL3, которой на ПК рендерится музыка.
Источник: Хабр · статьиПубликуется с разрешения правообладателя, подтверждённого редакцией.Оригинал: Doom, System Shock и Ultima VII на ПЛИС за $15: компьютер из 8640 логических ячеек ↗

Есть кому отправить?

Скопируется постоянная ссылка на этот материал.

Комментарии

Обсуждайте статью, отвечайте и оценивайте комментарии. Вход — через ваш Яндекс ID.

Загружаем обсуждение…