|
To RAM or no to RAM - вот в чём вопрос! |
|
|
|
Oct 5 2010, 08:52
|

Участник

Группа: Участник
Сообщений: 29
Регистрация: 6-08-10
Пользователь №: 58 790

|
Привет, ребят. Стою перед дилемой просто. Нужна обработка матриц - перемножение, сложение элементов. У меня такие мысли. С первого взгляда, достаточно удобно описывать матрицу как RAM, особенно когда нужно перемножать элементы (на аппаратных умножителях). Но в таком случае за один CLK можно работать только с одним элементом массива (считывать из памяти). Это большое но, потому что хочется максимально распараллелить обработку матрицы. То есть хочется одновременного доступа ко всем элементам - но (и опять же но) комбинаторной логике много, наверно, получается - скорость падает. Есть ли компромисс? Использовать небольшие блоки памяти? Подскажите, уважаемые коллеги! И вообще верны ли мои рассуждения?  .
--------------------
Усложнять - просто, упрощать - сложно.
|
|
|
|
3 страниц
1 2 3 >
|
 |
Ответов
(1 - 36)
|
Oct 5 2010, 08:57
|

я только учусь...
     
Группа: Модераторы
Сообщений: 3 447
Регистрация: 29-01-07
Из: Украина
Пользователь №: 24 839

|
Цитата(m0use @ Oct 5 2010, 11:52)  Привет, ребят. Стою перед дилемой просто. Нужна обработка матриц - перемножение, сложение элементов. У меня такие мысли. С первого взгляда, достаточно удобно описывать матрицу как RAM, особенно когда нужно перемножать элементы (на аппаратных умножителях). Но в таком случае за один CLK можно работать только с одним элементом массива (считывать из памяти). Это большое но, потому что хочется максимально распараллелить обработку матрицы. То есть хочется одновременного доступа ко всем элементам - но (и опять же но) комбинаторной логике много, наверно, получается - скорость падает. Есть ли компромисс? Использовать небольшие блоки памяти? Подскажите, уважаемые коллеги! И вообще верны ли мои рассуждения?  . какого размера матрицы? и элементы в матрице (их разрядность)?
--------------------
If it doesn't work in simulation, it won't work on the board.
"Ты живешь в своих поступках, а не в теле. Ты — это твои действия, и нет другого тебя" Антуан де Сент-Экзюпери повесть "Маленький принц"
|
|
|
|
|
Oct 5 2010, 11:28
|
Группа: Новичок
Сообщений: 5
Регистрация: 5-07-10
Из: Томск
Пользователь №: 58 292

|
Цитата(rv3dll(lex) @ Oct 5 2010, 17:59)  ващето память имеет скорость больше чем остальная плис. в смысле?
|
|
|
|
|
Oct 5 2010, 11:33
|

Участник

Группа: Участник
Сообщений: 29
Регистрация: 6-08-10
Пользователь №: 58 790

|
Цитата какого размера матрицы? и элементы в матрице (их разрядность)? Неплохо было бы, чтобы матрица была размером не меньше, чем 64x48. Вообще надо обрабатывать изображение 640x480, но думаю, такой объём не эффективно обрабатывать - буду разбивать на блоки порядка 64x48. Элемент матрицы - байт. rv3dll(lex), а могли бы Вы пояснить. Для того, чтобы такое провернуть, нужен отдельный CLK или можно преобразовать общий CLK и получить большую частоту?
--------------------
Усложнять - просто, упрощать - сложно.
|
|
|
|
|
Oct 5 2010, 11:58
|

Участник

Группа: Участник
Сообщений: 29
Регистрация: 6-08-10
Пользователь №: 58 790

|
Цитата Маленькие блоки RAM - это распределенная RAM. Кстати, об этом тоже хотел узнать. Вот у меня небольшой блок памяти описывается поведенчески. На RTL схеме изображается блок RAM, всё как надо. Так вот этот блок RAM - это распределённая память на LUT или блочная RAM? Просто, я как понял, именно блочную память эффективно с перемножителями использовать, потому что они в кристалле рядом. И можно ли как-то синтезатору SXT сказать, чтобы он блочную память использовал, а не распределённую даже для небольших блоков?
--------------------
Усложнять - просто, упрощать - сложно.
|
|
|
|
|
Oct 5 2010, 12:16
|

Участник

Группа: Участник
Сообщений: 29
Регистрация: 6-08-10
Пользователь №: 58 790

|
Цитата а ничего что латентность чтения у этих блоков памяти разная ? smile.gif Разная - это какая? У блочной памяти время отклика больше?
Сообщение отредактировал m0use - Oct 5 2010, 12:34
--------------------
Усложнять - просто, упрощать - сложно.
|
|
|
|
|
Oct 5 2010, 13:08
|

Участник

Группа: Участник
Сообщений: 29
Регистрация: 6-08-10
Пользователь №: 58 790

|
А менеждеры клоков умеют получать из одного клока другой, более быстрый? Просто у меня на отладочной плате только и генератор на 50МГц  . И получается, если клоки разные будут для памяти и для основной логики, то надо будет решать вопрос синхронизации? Enable какой-нибудь делать? ПЛИС - Spartan3E.
--------------------
Усложнять - просто, упрощать - сложно.
|
|
|
|
|
Oct 5 2010, 14:02
|

я только учусь...
     
Группа: Модераторы
Сообщений: 3 447
Регистрация: 29-01-07
Из: Украина
Пользователь №: 24 839

|
Цитата(m0use @ Oct 5 2010, 14:33)  Неплохо было бы, чтобы матрица была размером не меньше, чем 64x48. Вообще надо обрабатывать изображение 640x480, но думаю, такой объём не эффективно обрабатывать - буду разбивать на блоки порядка 64x48. Элемент матрицы - байт.
rv3dll(lex), а могли бы Вы пояснить. Для того, чтобы такое провернуть, нужен отдельный CLK или можно преобразовать общий CLK и получить большую частоту? как по мне лучше распределенная память - на регистрах. Таким образом обеспечивается параллельный доступ ко всем элементам матрицы. Но возможно скорость обработки упадет из-за этого... Если на RAM блоках тогда нужно придумать FSM, который будет последовательно считывать данные матрицы из RAM блоков и записывать результат обратно в RAM блоки или передавать дальше на обработку... В этом случае наверное нужна двухпортовая память, чтобы "развязать" по скорости поступающих данных на входе и обработанных данных на выходе. Это в том случае если не удастся реализовать обработку входящих данных на проходе (обработка в реалтайме)... PS Почему бы не взять за основу матрицу 8х6? Как по мне ресурсоемкость меньше и т.д. ... Цитата(m0use @ Oct 5 2010, 16:08)  И получается, если клоки разные будут для памяти и для основной логики, то надо будет решать вопрос синхронизации? Enable какой-нибудь делать?
ПЛИС - Spartan3E. Выход - двухпортовая память или фифо или синхронизаторы на основе двух регистров. Можно по другому пути пойти (память (во внутреннюю или во во внешнюю пока не важно)) 1. изображение 640x480 записывать в память, а потом уже производить его обработку. 2. Изображение поступает например на частоте 5 МГц, а Ваша схема работает на частоте 50 МГц. Тогда можно организовать автомат, который на каждом десятом такте производил запись входных данных ( изображения 640x480) в память, а остальные 9 тактов производил бы обработку данных. 3. Организовать конвейерное обработку "на проходе". Т.е. производить обработку изображения по мере поступления пикселей изображения. Изображение ж на ПЛИС поступает последовательно..., правильно? Таким образом, когда будет конец кадра изображения - обработанные данные при таком подходе также будут готовы (с некоторой задержкой)
--------------------
If it doesn't work in simulation, it won't work on the board.
"Ты живешь в своих поступках, а не в теле. Ты — это твои действия, и нет другого тебя" Антуан де Сент-Экзюпери повесть "Маленький принц"
|
|
|
|
|
Oct 5 2010, 14:38
|

Участник

Группа: Участник
Сообщений: 29
Регистрация: 6-08-10
Пользователь №: 58 790

|
Цитата Почему бы не взять за основу матрицу 8х6? Как по мне ресурсоемкость меньше и т.д. ... Матрицу 8x6 использую пока для отладки. Но вообще думаю о больших размерах  . Просто изображение большое - 640x480 и блоков по 8x6 получится много - то есть чтобы всю матрицу обработать, нужно много проходов. Конечная цель - получить хорошую скорость обработки именно изображения в целом. Поэтому и хочу "элементарный" блок увеличить. Почему пришлось использовать RAM. В самом начале (когдя я ещё ничего не понимал о внутренней структуре ПЛИС, и смотрел на него как на штуку, которая может много оперций делать за один такт) я всё сделал на регистрах. И всю обработку сделал параллельной. А так как из порядка 100 умножений кристалл аппаратно может сделать лишь 20 получилось много логики (под 100% ресурсов на матрице 8x6) и мало скорости (5 МГц  ). Теперь думаю умножение реализовать только на аппаратных, соответственно удобно RАМ использовать. Умножил, хорошо. Но теперь нужно сделать сумму по окрестности каждого элемента и теперь RАМ не удобна, так как даёт доступ только к одному элементу за такт (выше был предложен вариант разных клок доменов, но для меня это круто пока). Так что буду пробовать. Умножу как RAM, а складывать буду как регистровую память. Посмотрим, насколько макс.такт упадёт. Пока 120МГц. Я бы просто не хотел ниже 100 опускаться. Просто основная операция - умножение по ресурсам. И если их хотя бы больше 10 использовать, но эквивалентная частота будет больше 1ГГц.
--------------------
Усложнять - просто, упрощать - сложно.
|
|
|
|
|
Oct 5 2010, 15:08
|
Знающий
   
Группа: Свой
Сообщений: 972
Регистрация: 12-04-09
Из: Москва
Пользователь №: 47 543

|
Цитата как по мне лучше распределенная память - на регистрах. Таким образом обеспечивается параллельный доступ ко всем элементам матрицы. Но возможно скорость обработки упадет из-за этого... И резко возрастет объем проекта...
|
|
|
|
|
Oct 5 2010, 16:14
|

Участник

Группа: Участник
Сообщений: 29
Регистрация: 6-08-10
Пользователь №: 58 790

|
Цитата поделиться алгоритмом обработки изображения Из исходной матрицы I строим градиентные матрицы Ix и Iy. В простейшем случае это просто разность элементов по x и по y. Потом строим 3 матрицы из поэлементных произведений Ix * Ix, Ix * Iy и Iy * Iy. То есть элементы этих матриц равны произведению соответствующих элементов исходных матриц. А потом для каждого элемента полученных матриц строим сумму по окрестности. То есть к значению самого элемента прибавляем значения соседей. Получим матрицы A, B и С. Вот. Но это ещё не всё  . Элементы окончательная матрица H = f( A, B, C ). Функция f - простое выражение с двумя умножениями, разностью и делением. На Cи можно написать 4мя вложенными циклами. Но такой подход в кристалле тучу места займёт.
--------------------
Усложнять - просто, упрощать - сложно.
|
|
|
|
|
Oct 5 2010, 17:39
|

Участник

Группа: Участник
Сообщений: 29
Регистрация: 6-08-10
Пользователь №: 58 790

|
Цитата На константу или переменную? На переменную - тоже моя головная боль будущая. Думаю использовать тут Core Generator. Потому что самому писать реализацию деления... когда-нибудь в другой раз  . Ребят, скажите, пожалуйста, можно ли одновременно оба канала 2-port RAM использовать для чтения?
Сообщение отредактировал m0use - Oct 5 2010, 17:39
--------------------
Усложнять - просто, упрощать - сложно.
|
|
|
|
|
Oct 5 2010, 17:57
|

я только учусь...
     
Группа: Модераторы
Сообщений: 3 447
Регистрация: 29-01-07
Из: Украина
Пользователь №: 24 839

|
Цитата(m0use @ Oct 5 2010, 20:18)  На переменную - тоже моя головная боль будущая. Думаю использовать тут Core Generator. Потому что самому писать реализацию деления... когда-нибудь в другой раз  . На мой взгляд лучше реализация по предложенному мною 3 варианту. Т.е. через конвейерную обработку на проходе. Поясню более подробно (мое видение): Реализовывается задержка на требуемое кол-во строк и пикселей в строке (аналогичную как на рисунке во вложении, из-за этого и предложил уменьшить матрицу  ). Таким образом организовываем окно которое будет "скользить" по изображению. Далее реализовываем конвейерную обработку в темпе считывания изображения (оно ж последовательно поступает на ПЛИС) для значений пикселей в окне - как пример архитектуры реализации конвейера представлен на втором рисунке. PS На рисунках во вложении для примера представлена архитектура реализации медианного фильтра 3х3 для изображения. PS PS На втором рисунке горизонтальные линии - делят обработку на такты для конвейера. PS PS PS Но решение как лучше реализовывать, принимать Вам
Эскизы прикрепленных изображений
--------------------
If it doesn't work in simulation, it won't work on the board.
"Ты живешь в своих поступках, а не в теле. Ты — это твои действия, и нет другого тебя" Антуан де Сент-Экзюпери повесть "Маленький принц"
|
|
|
|
|
Oct 5 2010, 20:02
|

Участник

Группа: Участник
Сообщений: 29
Регистрация: 6-08-10
Пользователь №: 58 790

|
Офигеть! Maverick, спасибо, что поведал про такую красивую идею! А это откуда картинки? Просто первоисточник хотел бы посмотреть! Наверно, хорошая книжка  . И, может быть Вы в принципе можете что-то посоветовать по теме таких вот стандартных схемотехнических решений? Буду очень благодарен. А кстати, FIFO в простейшем случае (который здесь как задержка выступает) - это просто нужное количество регистров?
--------------------
Усложнять - просто, упрощать - сложно.
|
|
|
|
|
Oct 5 2010, 20:25
|
Знающий
   
Группа: Свой
Сообщений: 972
Регистрация: 12-04-09
Из: Москва
Пользователь №: 47 543

|
Цитата что делать? человек всегда становится перед выбором Иногда очень легко стать или перед пределом максимально возможной емкости отдельно взятой микросхемы, или (чаще) отдельно выделенного количества денег... Шустрые плисины - они того, дорогие очень.
|
|
|
|
|
Oct 6 2010, 06:26
|

я только учусь...
     
Группа: Модераторы
Сообщений: 3 447
Регистрация: 29-01-07
Из: Украина
Пользователь №: 24 839

|
Цитата(m0use @ Oct 5 2010, 23:02)  Офигеть! Maverick, спасибо, что поведал про такую красивую идею! Всегда пожалуйста!  Цитата А это откуда картинки? Просто первоисточник хотел бы посмотреть! Наверно, хорошая книжка  . И, может быть Вы в принципе можете что-то посоветовать по теме таких вот стандартных схемотехнических решений? Буду очень благодарен. Первоисточник - в личке. Это даже не книжка - магистерская работа студента из Америки кажется.  Но довольно не плохая, как по мне... Цитата А кстати, FIFO в простейшем случае (который здесь как задержка выступает) - это просто нужное количество регистров? Да
--------------------
If it doesn't work in simulation, it won't work on the board.
"Ты живешь в своих поступках, а не в теле. Ты — это твои действия, и нет другого тебя" Антуан де Сент-Экзюпери повесть "Маленький принц"
|
|
|
|
|
Oct 6 2010, 07:21
|

я только учусь...
     
Группа: Модераторы
Сообщений: 3 447
Регистрация: 29-01-07
Из: Украина
Пользователь №: 24 839

|
Цитата(CaPpuCcino @ Oct 6 2010, 09:51)  а может всё-таки огласите весь список? хотя бы название. спб Не проблема... Первоисточник (где-то на форуме уже выкладывал) Размер архива 814.79 килобайт Там как пример рассмотрена реализация медианного фильтра и в конце приведено описание на VHDL. PS Как по мне там красиво и подробно расписана эта идея и приведен пример реализации. Цитата(m0use @ Oct 5 2010, 23:02)  Подумайте, может можно заменить операцию деления на операцию сдвига (деления на числа степени двойки). Будет проще реализация
--------------------
If it doesn't work in simulation, it won't work on the board.
"Ты живешь в своих поступках, а не в теле. Ты — это твои действия, и нет другого тебя" Антуан де Сент-Экзюпери повесть "Маленький принц"
|
|
|
|
|
Oct 6 2010, 09:23
|

Участник

Группа: Участник
Сообщений: 29
Регистрация: 6-08-10
Пользователь №: 58 790

|
Цитата есть еще такой вариант: использовать RAM с шириной шины данных N*(сколько_у_вас_там_бит), соответственно можно распараллелить вычисления Я думал так делать, но пока нацелился на конвеерный подход, который Maverick подкинул. Цитата еще многопортовый доступ можно задействовать. Многопортоый? В смысле двухпортовый? Я просто сделал, чтобы за такт два значения читалось. Можно больше? Ну, это я больше из интереса спрашиваю  . У меня просто произведения элементов матриц между собой и самих на себя требут 3 умножителей. Двухпортовое считывание позволяет 6 умножителей использовать. А подход с размерностью памяти, скажем, в три байта позволит 18 умножений делать за так. Из 20 аппаратных умножителей ещё 2 останутся - там потом ещё надо будет умножения делать. В целом, наверно, тут уже предел будет для этой ПЛИС. Цитата Первоисточник - в личке. Спасибо, тема близка - думаю, мне это сильно поможет. Только вот там VHDL, который я пока не изучал  . Длинный P.S. У меня с этим конвеерным подходом небольшой вопрос появился. Мне нужная сумма 9 элементов на конвеере (условно горизонтальном). Соответственно, чтобы частота не падала - нужен конвеер уже в вертикальном направлении - из 8 "складывателей"  и промежуточными регистрами. Если это так, то тогда можете подсказать, как оптимально такой вопрос решать. Так как это матрица, то мне не все суммы нужны (граничные эффект - когда окно к краям матрицы подходит). Как мне следующему модулю передать только нужные суммы? Я тут три подхода вижу: 1. Передавать все, а обрабатывать только нужные. 2. Передавать только нужные каким-нибудь сигналом, что на выходе нужная сумма. Но тут такой момент возникает, что от того момента, когда на конвеере будет нужная последовательность до того, как их сумма попадёт на выход пройдёт 4 такта. То есть и сигнал нужно задерживать на столько же тактов. Это регистром сдвига делать? 3. Другой подход. Какой  ?
--------------------
Усложнять - просто, упрощать - сложно.
|
|
|
|
|
Oct 6 2010, 09:34
|

тоже уже Гуру
     
Группа: Свой
Сообщений: 2 047
Регистрация: 13-06-05
Из: Кёлн - Санкт-Петербург
Пользователь №: 5 973

|
Цитата(m0use @ Oct 6 2010, 12:23)  Многопортоый? В смысле двухпортовый? Я просто сделал, чтобы за такт два значения читалось. Можно больше? Ну, это я больше из интереса спрашиваю  . нет больше пока на одну RAMину нельзя, но можно дублировать контент (параллельная запись в несколько RAM никем не противопоказана) Цитата(m0use @ Oct 6 2010, 12:23)  Я думал так делать, но пока нацелился на конвеерный подход, который Maverick подкинул. а скомбинировать? Цитата(m0use @ Oct 6 2010, 12:23)  3. Другой подход. Какой  ? сдвиговый регистр вполне хорош, т.к. прост как табуретка и ресурсов по минимуму
--------------------
И снова на арене цирка - дрессированные клоуны!! Оказываем консультации по электронике за симпу круглосуточно.
|
|
|
|
|
Oct 6 2010, 09:35
|

Участник

Группа: Участник
Сообщений: 29
Регистрация: 6-08-10
Пользователь №: 58 790

|
Цитата Подумайте, может можно заменить операцию деления на операцию сдвига (деления на числа степени двойки). Будет проще реализация wink.gif Надо подумать. Как-то очевидное решение не приходит. Если с = 2^n + r, то a = b / с = b / ( 2^n + r ) = b >> n / ( 1 + r >> n ). А дальше ряд Тейлора и ещё про дробную часть не забывать?.. Надо почитать про алгоритмы деления быстрые будет, но я бы пока на IP остановился.
--------------------
Усложнять - просто, упрощать - сложно.
|
|
|
|
|
Oct 6 2010, 09:51
|

Участник

Группа: Участник
Сообщений: 29
Регистрация: 6-08-10
Пользователь №: 58 790

|
Цитата можно дублировать контент (параллельная запись в несколько RAM никем не противопоказана) Ага - хорошее дополнение. Я немного по-другому делал - разбивал одну RAM на две меньших (без дублирования) - но тут опять же предел в 20 умножителей. Вариантов по его достижению уже немало было рассмотрено. По быстродействию и ресурсам они, наверно, очень близки - надо просто выбрать один  . Цитата а скомбинировать? Пока так и делаю, потому что умножение написано на RAM - а дальше конвеер на сложения пойдёт. Получится освоить этот подход на сложении - попробую и поумножать с его поможью. Просто этот подход очень привлекателен с той точки зрения, что входные данные плывут со своей скоростью, а мы их обрабатываем и не задерживаем их. А с RAM я жду заполнения памяти. Хотя можно, наверно попробовать уже считывать из RAM до того, как она заполнилась... попробовать можно, конецно.
--------------------
Усложнять - просто, упрощать - сложно.
|
|
|
|
|
Oct 6 2010, 13:34
|

я только учусь...
     
Группа: Модераторы
Сообщений: 3 447
Регистрация: 29-01-07
Из: Украина
Пользователь №: 24 839

|
Цитата(m0use @ Oct 6 2010, 12:23)  У меня с этим конвеерным подходом небольшой вопрос появился. Мне нужная сумма 9 элементов на конвеере (условно горизонтальном). Соответственно, чтобы частота не падала - нужен конвеер уже в вертикальном направлении - из 8 "складывателей"  и промежуточными регистрами. Если это так, то тогда можете подсказать, как оптимально такой вопрос решать. Так как это матрица, то мне не все суммы нужны (граничные эффект - когда окно к краям матрицы подходит). Как мне следующему модулю передать только нужные суммы? Я тут три подхода вижу: 1. Передавать все, а обрабатывать только нужные. 2. Передавать только нужные каким-нибудь сигналом, что на выходе нужная сумма. Но тут такой момент возникает, что от того момента, когда на конвеере будет нужная последовательность до того, как их сумма попадёт на выход пройдёт 4 такта. То есть и сигнал нужно задерживать на столько же тактов. Это регистром сдвига делать? 3. Другой подход. Какой  ? поясните, плиз 2 пункт
--------------------
If it doesn't work in simulation, it won't work on the board.
"Ты живешь в своих поступках, а не в теле. Ты — это твои действия, и нет другого тебя" Антуан де Сент-Экзюпери повесть "Маленький принц"
|
|
|
|
|
Oct 7 2010, 14:48
|

Участник

Группа: Участник
Сообщений: 29
Регистрация: 6-08-10
Пользователь №: 58 790

|
Цитата поясните, плиз 2 пункт Неясно выразился, возможно. Например, матрица 4x4. На конвеер она поступает последовательно: ...-13-[12-11-10]-9-[8-7-6]-5-[4-3-2]-1-... Но оконная функция не все комбинации выбирает, а только: *__*__*__4 *__*__*__8 *__*__*__12 13_14_15_16 1__*__*__* 5__*__*__* 9__*__*__* 13_14_15_16 и т.п. То есть сумма 13+12+11+9+8+7+5+4+3, к примеру, нам не нужна. Но на выходе она есть. Поэтому нужна дополнительная информация. Которая должна быть синхронизирована с потоком основных данных.
Сообщение отредактировал m0use - Oct 7 2010, 14:50
--------------------
Усложнять - просто, упрощать - сложно.
|
|
|
|
|
Oct 7 2010, 21:30
|

Участник

Группа: Участник
Сообщений: 29
Регистрация: 6-08-10
Пользователь №: 58 790

|
Ребят, всем спасибо за советы и помощь. Я бы хотел теперь поделиться практическими результатами. Написал две реализации модуля, который принимает входное изображение, получает градиентные матрицы и перемножает их элементы. Одна реализация - конвеер с отводами, другая основана на использовании RAM. Входная матрица - 8x6. Речь прежде всего о скорости. И вот какие результаты: конвеер - порядка 220 МГц, RAM - порядка 120 МГц. При этом в первом случае для полного прохода ещё и тактов меньше требуется. Прикладываю RTL-схемы модулей для наглядности. Возможно, что я не оптимально реализовал вариант с RAM. Там больше логики, потому что весь процесс разбивается на этапы и окончание одного запускает другой. Тут, наверно, причина в том, что RAM имеет ограниченное число портов и нельзя считывать данные одновременно с процессом их записи. Поэтому приходится ждать. К чему я. Возникло предположение, что подход с конвеером здесь более эффективен. Получается, где поток данных "проходит" через кристалл, не задерживаясь, RAM не к месту? RAM оправдана там, где данные "задерживаются" для многократного вторичного использования? Или как  ?
Эскизы прикрепленных изображений
--------------------
Усложнять - просто, упрощать - сложно.
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|