|
Тернарный оператор VS конструкция if-else, Какова логика языка? Почему одно не заменяет другое? |
|
|
|
Jun 7 2018, 06:53
|
Участник

Группа: Участник
Сообщений: 30
Регистрация: 4-06-18
Пользователь №: 104 848

|
Предположим, есть конечный автомат, который в работе должен пробрасывать тактовую частоту через себя, а в состоянии IDLE установить выход тактовой частоты в единицу. Тогда код может быть следующий: Код wire clk_in; wire clk_out; reg state;
assign clk_out = clk_in | ~(state^IDLE); Т.е. если state и IDLE совпадают бит-в-бит, то их побитовый XOR будет равен нулю. Инвертированный ноль - это единица. Единица или clk_in - единица. В противном случае, ноль или clk_in - это clk_in. Всё абсолютно прозрачно синтезируется. Или код может быть такой: Код wire clk_in; wire clk_out; reg state;
assign clk_out = clk_in | (state == IDLE);//На месте синтезатора я заменил бы данную конструкцию на предыдущую Или такой: Код wire clk_in; wire clk_out; reg state;
assign clk_out =(state == IDLE) ? 1 : clk_in; Но почему не прокатывает следующий вариант? Код wire clk_in; wire clk_out; reg state;
always @(state) begin if(state == IDLE) begin clk_out = 1; end else begin clk_out = clk_in; end end В таком варианте Квартус ругается на то, что "10137: Object on left-hand side of assignment must have a variable data type". Не, ну нельзя, так - нельзя. Но эмм... в списке чувствительности отсутствуют posedge/negedge. В любом случае, синтезатор будет разворачивать эту конструкцию в комбинационную логику (причем, скорее всего в такую, как в первом примере). Так чего же он лезет на рожон (регистр ему подавай)? Какова логика языка? Почему так писать некорректно?
|
|
|
|
3 страниц
1 2 3 >
|
 |
Ответов
(1 - 34)
|
Jun 7 2018, 08:05
|
Профессионал
    
Группа: Свой
Сообщений: 1 214
Регистрация: 23-12-04
Пользователь №: 1 643

|
Приветсвую! Цитата(flammmable @ Jun 7 2018, 09:53)  ... ... Но почему не прокатывает следующий вариант? Код wire clk_in; wire clk_out; reg state;
always @(state) ... clk_out = clk_in; ... end В таком варианте Квартус ругается на то, что "10137: Object on left-hand side of assignment must have a variable data type". Не, ну нельзя, так - нельзя. Но эмм... в списке чувствительности отсутствуют posedge/negedge. В любом случае, синтезатор будет разворачивать эту конструкцию в комбинационную логику (причем, скорее всего в такую, как в первом примере). Так чего же он лезет на рожон (регистр ему подавай)? Какова логика языка? Почему так писать некорректно? Это не Квартус лезет на рожон скорее это Вы пристаете к синтезатору с классическим вопросом "...Закурить найдется?"  А он Вам вежливо - не курю не могу присвоить значение переменной типа wire в процедурном блоке. Для wire допустимо использование только assign (так пацаны на сходке решили еще аж 1995 году). Если Вы смелы то объявите Код reg clk_out; Тогда Квартус примет Вас за своего и получите сигаретку назначение переменной в процедурном блоке  Удачи! Rob.
|
|
|
|
|
Jun 7 2018, 08:14
|
Гуру
     
Группа: Модераторы
Сообщений: 4 011
Регистрация: 8-09-05
Из: спб
Пользователь №: 8 369

|
Цитата(Flip-fl0p @ Jun 7 2018, 10:20)  1. Я бы вообще никаких манипуляций с частотой не производил. 2. Если уж так нужно, обратите внимание на: altera clkctrl Без обид. Смотрите внимательно, то что я напишу. Для одних - ПЛИС это кусок хлеба. И форум - это товарищи, которые помогают начать прилично зарабатывать. Поэтому их советы ценятся. Потому что на работе надо давать результат, иначе не будет зарплаты, причем результат нужен каждый день, а не когда-то потом. И я помню, что сам был в такой же ситуации. До этого я писал на VHDL, пришел на новую работу и там сказали - только Verilog. И я учился "в бою". И мне абсолютно некогда было "изобретать художества". Брал проверенные шаблоны и из них делал код, в том числе и по выходным, чтобы успеть. А для других ПЛИС - это игра. Им результат как таковой не нужен. Они "проводят изыскания" в кусках кода, в "триггерах, нарисованных вьюерами" и т.д. Им советы, выдаваемые на форуме вроде как и нужны, но они все равно все сделают по-своему, "через Альпы". Вот так и здесь. Есть основы: клоки не изобретать и проект делать синхронным; "внутри автоматов не плодить счетчики и не делать клоки. И это написано практически во всех руководствах. Ну и что? Это же так интересно, написать что-то принципиально "свое", а потом задавать об этом вопросы на форуме. Чтобы все, кто готов помочь, бросились объяснять очевидные вещи. Беда в том, что правильный ответ игруну вообще не нужен, потому что он быстро приводит к правильному результату. Тут как раз все надо делать все совершенно наоборот. Надо дать ссылку на кучу литературы, на методики, на головоломные статьи. Так чтобы до результата стало как можно дальше. Тогда игрун ощутит себя бесконечно крутым. Вот как-то так....
--------------------
www.iosifk.narod.ru
|
|
|
|
|
Jun 7 2018, 08:22
|
Профессионал
    
Группа: Свой
Сообщений: 1 975
Регистрация: 30-12-04
Из: Воронеж
Пользователь №: 1 757

|
Цитата(flammmable @ Jun 7 2018, 09:53)  Предположим, есть конечный автомат, который в работе должен пробрасывать тактовую частоту через себя, а в состоянии IDLE установить выход тактовой частоты в единицу. Тогда код может быть следующий: Код wire clk_in; wire clk_out; reg state; assign clk_out = clk_in | ~(state^IDLE); Т.е. если state и IDLE совпадают бит-в-бит, то их побитовый XOR будет равен нулю. Инвертированный ноль - это единица. Единица или clk_in - единица. В противном случае, ноль или clk_in - это clk_in. Это в идеале. IRL из-за всяких задержек вы получите на clk_out хороший такой дребезг. Нельзя просто так взять и смешать клок с обычным сигналом, тем более многоразрядным. Цитата Но эмм... в списке чувствительности отсутствуют posedge/negedge. В любом случае, синтезатор будет разворачивать эту конструкцию в комбинационную логику (причем, скорее всего в такую, как в первом примере). Так чего же он лезет на рожон (регистр ему подавай)? Какова логика языка? Почему так писать некорректно? Про разницу reg/wire вам уже сказали. Добавлю, что если в always описывается комбинационная схема, в списке чувствительности должны присутствовать все сигналы, от которых зависит выход. У вас там не хватает clk_in, поэтому вы получите не комбинационную схему, а latch.
|
|
|
|
|
Jun 7 2018, 10:32
|
Участник

Группа: Участник
Сообщений: 30
Регистрация: 4-06-18
Пользователь №: 104 848

|
Цитата(Flip-fl0p @ Jun 7 2018, 10:20)  1. Я бы вообще никаких манипуляций с частотой не производил. Конкретная задача. FPGA соединена с PC при помощи моста "USB - что_угодно" FT2232H, который способен работать в режиме асинхронного FIFO. У моста есть два буфера - TX и RX. Для передачи данных в FPGA мосту надо стробировать nRD. Для отправки на PC (и захвата данных во входной буфер) этому мосту надо стробировать nWR. Правильнее было бы описать дергание стробов в состояниях конечного автомата? Цитата(RobFPGA @ Jun 7 2018, 11:05)  Приветсвую! Это не Квартус лезет на рожон скорее это Вы пристаете к синтезатору с классическим вопросом "...Закурить найдется?"  А он Вам вежливо - не курю не могу присвоить значение переменной типа wire в процедурном блоке. Для wire допустимо использование только assign (так пацаны на сходке решили еще аж 1995 году). Если Вы смелы то объявите Код reg clk_out; Тогда Квартус примет Вас за своего и получите сигаретку назначение переменной в процедурном блоке  Удачи! Rob. Добрый день! Про то, что с доп.регистром всё заработает - это да. Только этот же регистр всё равно не синтезируется. Всё сведется к XOR-у + лог.ИЛИ (как в первом примере). И это понятно по отсутствию posedge/negedge в списке чувствительности. Чем руководствовались пацаны в 1995 году, когда так договаривались? От чего они пытались оградить программистов? Не собираются ли пацаны выпустить новый стандарт? Ибо тернарный оператор выглядит костылеобразно (не круто). Спасибо! Цитата(andrew_b @ Jun 7 2018, 11:22)  Это в идеале. IRL из-за всяких задержек вы получите на clk_out хороший такой дребезг. Нельзя просто так взять и смешать клок с обычным сигналом, тем более многоразрядным. Спасибо за ценное замечание. Действительно, если клок управляется значением шины hAA и шина переключается из состояния h0F в hF0 не синхронно а как бы по пути h0F->h0A-> hAA->hA0->hF0 может произойти неприятное. Цитата(andrew_b @ Jun 7 2018, 11:22)  Добавлю, что если в always описывается комбинационная схема, в списке чувствительности должны присутствовать все сигналы, от которых зависит выход. У вас там не хватает clk_in, поэтому вы получите не комбинационную схему, а latch. Точно. Ну в любом случае синтезатор не даст такой синтаксической конструкции собраться. Цитата(iosifk @ Jun 7 2018, 11:14)  Без обид. Без обид, разумеется. Ув. iosifk мне кажется есть два (как минимум) случая, когда "ориентированность на результат" приведет к негативным последствиям: 1. Конструкция языка/библиотеки/устройства претерпела изменения за последнее время (или такое изменение напрашивается в ближайшем будущем). Пример: инициализация регистров при запуске ПЛИС "в железе". На сколько понял читая материалы в сети, в 90-е регистры инициализировались исключительно нулями. Сейчас ситуация поменялась, но ряд комментаторов по старой памяти утверждают, что это невозможно и сами не используют эту удобную опцию. 2. Хрестоматийный пример, на который нужно равняться, содержит незаметную ошибку. Пример: в LabVIEW есть пример работы с высокоскоростными модулями HSDIO. Коротко, работа с HSDIO представляет последовательность Init->Transmit/Receive->Close. Но Close может быть двух типов - "Закрыть по завершении Transmit/Receive" и "Закрыть немедленно". Так вот в официальном примере использовано "Закрыть немедленно". HSDIO в режиме Transmit/Receive работает автономно от PC и обычно гораздо быстрее. Так что программы на основе примера работают корректно. Но если забить буфер HSDIO под завязку, и выставить частоту работы по минимуму, "хвост" передачи будет обрублен. Причем без каких-либо эксепшенов. Таким образом, если времени нет, разумеется приходится "ориентироваться на результат". Но если оно есть, почему бы не выяснить как именно работает тот или иной подход и почему именно так. Кроме того есть замечательное руководство "Как правильно задавать вопросы на технических ресурсах". Надеюсь я правильно истолковываю данный кодекс, полагая что вопрос в духе " вот моя плата на 100 микросхем, вот мой код на 10.000 строк, тут что-то не работает" хуже, чем вопрос " Вот три строчки кода, которые не работают. Вот стремный воркэраунд на десять строчек, который работает. Можно ли написать красивее и правильнее?". И если у меня возникает вопрос первого типа, его нужно свести к вопросу второго типа и только потом обращаться за помощью.
Сообщение отредактировал flammmable - Jun 7 2018, 11:27
|
|
|
|
|
Jun 7 2018, 11:30
|
Участник

Группа: Участник
Сообщений: 16
Регистрация: 30-11-17
Пользователь №: 100 439

|
Цитата Но почему не прокатывает следующий вариант? Код wire clk_in; wire clk_out; reg state;
always @(state) begin if(state == IDLE) begin clk_out = 1; end else begin clk_out = clk_in; end end В таком варианте Квартус ругается на то, что "10137: Object on left-hand side of assignment must have a variable data type". Все что формируется в блоке always, не зависимо от того, что вы хотите видеть после синтеза (последовательностный элемент или комбинационный) должно быть типа reg. А вообще вы описали защелку (latch). Я бы тоже с клоками не баловался подобным образом, но если уж на то пошло, то: Код wire clk_in; reg clk_out; reg state;
always @(*) begin if(state == IDLE) begin clk_out = 1; end else begin clk_out = clk_in; end end ----------------------------------------------------------------------------- Упс, повторяюсь за выше отписавшимися...
Сообщение отредактировал Dantist2k17 - Jun 7 2018, 11:36
|
|
|
|
|
Jun 7 2018, 11:45
|
Гуру
     
Группа: Модераторы
Сообщений: 4 011
Регистрация: 8-09-05
Из: спб
Пользователь №: 8 369

|
Цитата(flammmable @ Jun 7 2018, 13:32)  Конкретная задача. FPGA соединена с PC при помощи моста "USB - что_угодно" FT2232H, который способен работать в режиме асинхронного FIFO. У моста есть два буфера - TX и RX. Для передачи данных в FPGA мосту надо стробировать nRD. Для отправки на PC (и захвата данных во входной буфер) этому мосту надо стробировать nWR. Правильнее было бы описать дергание стробов в состояниях конечного автомата?
Таким образом, если времени нет, разумеется приходится "ориентироваться на результат". Но если оно есть, почему бы не выяснить как именно работает тот или иной подход и почему именно так. Для начала - http://www.sunburst-design.com/Ищем статьи отцов-основателей. Вот здесь: http://www.sunburst-design.com/papers/И если поискать, то найдете, что когда пишите assert, то это для wire... А когда есть события - на которые указывает "@", то это все в основном для регистров. Если Вы знаете, что такое "автомат", то попробуйте представить себе набор автоматов - мастер-слэйв-слэйв. Нижний работает только на уровне бита при последовательной передаче или слова при параллельной. Средний - передает слова или кадры. А верхний - сообщения, которые могут состоять из нескольких кадров. Причем "внутри" описания автомата я не рекомендую врезать какие-либо счетчики или что-то еще. Автомат должен только лишь выдавать стробы для управления всеми другими узлами. Что еще? Основной "подход" - это не тонкости описания, а именно то, как Вы строите архитектуру вычислительного узла. Вот скажем проблема со стробами nWR Вас интересует, а протокол передачи данных по линии связи видимо пока еще нет. А парсер протокола делать будете?
--------------------
www.iosifk.narod.ru
|
|
|
|
|
Jun 7 2018, 12:16
|
Профессионал
    
Группа: Свой
Сообщений: 1 214
Регистрация: 23-12-04
Пользователь №: 1 643

|
Приветсвую! Цитата(flammmable @ Jun 7 2018, 13:32)  Про то, что с доп.регистром всё заработает - это да. Только этот же регистр всё равно не синтезируется. Всё сведется к XOR-у + лог.ИЛИ (как в первом примере). Увы тип reg ввоодит в заблуждение - переменная этого типа не всегда синтезируется в регистр как многие ожидают от названия. Это зависит где и как Вы работаете с этой переменной. Цитата(flammmable @ Jun 7 2018, 13:32)  И это понятно по отсутствию posedge/negedge в списке чувствительности. Чем руководствовались пацаны в 1995 году, когда так договаривались? От чего они пытались оградить программистов? Не собираются ли пацаны выпустить новый стандарт? Ибо тернарный оператор выглядит костылеобразно (не круто). Пацаны руководствовались тем что было - бригада собралась молодая, дерзкая, но ума/силенок на первых порах не хватало - приходилось отбиваться от банды с соседнего VHDL двора. Сейчас уже поумнев добавили возможность разработчикам использовать always @(*) или даже always_comb. A чтобы не путать неокрепшие умы молодой братвы вместо reg использовать bit или logic. Но тернарный оператор не обижайте! - я за него "...пасть порву, моргалы выколю ..." ®Доцент Главное в любой разработке (а не только для FPGA) это не тупое использование чьих-то шаблонов для заработка куска хлеба а старание понять почему и для чего они сделаны. От чего зависит применении тех или иных конструкций языка. Во что это выливается в конкретном симуляторе/синтезаторе/железе. И Ваше желание разобраться в этом похвально. Тогда со временем сможете намазать чем то FPGAшный кусок хлеба ну или сами начнете писать тупые шаблоны для других  . Удачи! Rob.
|
|
|
|
|
Jun 7 2018, 12:26
|
Участник

Группа: Участник
Сообщений: 30
Регистрация: 4-06-18
Пользователь №: 104 848

|
Цитата(iosifk @ Jun 7 2018, 14:45)  Для начала - http://www.sunburst-design.com/Ищем статьи отцов-основателей. Вот здесь: http://www.sunburst-design.com/papers/И если поискать, то найдете, что когда пишите assert, то это для wire... А когда есть события - на которые указывает "@", то это все в основном для регистров. Если Вы знаете, что такое "автомат", то попробуйте представить себе набор автоматов - мастер-слэйв-слэйв. Нижний работает только на уровне бита при последовательной передаче или слова при параллельной. Средний - передает слова или кадры. А верхний - сообщения, которые могут состоять из нескольких кадров. Причем "внутри" описания автомата я не рекомендую врезать какие-либо счетчики или что-то еще. Автомат должен только лишь выдавать стробы для управления всеми другими узлами. Что еще? Основной "подход" - это не тонкости описания, а именно то, как Вы строите архитектуру вычислительного узла. Вот скажем проблема со стробами nWR Вас интересует, а протокол передачи данных по линии связи видимо пока еще нет. А парсер протокола делать будете? Т.к. я пишу и клиентскую часть и прошивку для FPGA, то протокол могу менять сам, подстраивая его под возможности и ограничения оборудования. Процесс итеративен - я смотрю что можно "выжать" из LabVIEW, FT2232H и MAX-10 по максимуму. И как это правильнее сделать, что бы это не был "write only code". Огромное спасибо, ув. iosifk за Ваши советы!
|
|
|
|
|
Jun 7 2018, 15:17
|
Участник

Группа: Участник
Сообщений: 30
Регистрация: 4-06-18
Пользователь №: 104 848

|
Цитата(iosifk @ Jun 7 2018, 15:30)  Тогда вот О!! Большое спасибо.
|
|
|
|
|
Jun 7 2018, 17:27
|
Участник

Группа: Участник
Сообщений: 21
Регистрация: 18-12-16
Пользователь №: 94 676

|
Цитата(iosifk @ Jun 7 2018, 09:14)  Вот так и здесь. Есть основы: клоки не изобретать и проект делать синхронным; "внутри автоматов не плодить счетчики и не делать клоки. Простите, а что плохого в счетчиках внутри автоматов? Цитата(flammmable @ Jun 7 2018, 11:32)  инициализация регистров при запуске ПЛИС "в железе". На сколько понял читая материалы в сети, в 90-е регистры инициализировались исключительно нулями. Сейчас ситуация поменялась, но ряд комментаторов по старой памяти утверждают, что это невозможно и сами не используют эту удобную опцию. Я сам с Альтерой не работал, но слышал, что там до сих пор инициализация только нулями. У lattice тоже не у всех семейств поддерживается инициализация.
|
|
|
|
|
Jun 7 2018, 18:18
|
Гуру
     
Группа: Модераторы
Сообщений: 4 011
Регистрация: 8-09-05
Из: спб
Пользователь №: 8 369

|
Цитата(Viktuar @ Jun 7 2018, 20:27)  Простите, а что плохого в счетчиках внутри автоматов? Синхронный проект должен работать по клокам. Если Вы из автомата даете только одиин сигнал разрешения, то счетчик и будет работать по клокам. И по разрядам счетчика разойдется только один сигнал... А если при описании автомата, Вы запихиваете счетчик "внутрь" комбинационной части, то что будет подано на тактовые входы счетчика? Но даже если компилятор сообразит подать туда клоки, то все равно, к комбинационной части добавятся еще N разрядов счетчика. И все это надо будет уложить в период тактовой частоты. Поэтому я так стараюсь никогда не делать. Вообще, примите методу проектирования, когда автомат только роздает управляющие воздействия и более ничего. И тогда проект становится значительно легче. Вы делаете "исполнительные узлы" и потом в нужные моменты времени автоматом только даете управляющие воздействия. Если хотите, то могу росказать подробнее.
--------------------
www.iosifk.narod.ru
|
|
|
|
|
Jun 7 2018, 18:39
|
Профессионал
    
Группа: Свой
Сообщений: 1 214
Регистрация: 23-12-04
Пользователь №: 1 643

|
Приветствую! Цитата(iosifk @ Jun 7 2018, 21:18)  Синхронный проект должен работать по клокам. Если Вы из автомата даете только одиин сигнал разрешения, то счетчик и будет работать по клокам. И по разрядам счетчика разойдется только один сигнал... А если при описании автомата, Вы запихиваете счетчик "внутрь" комбинационной части, то что будет подано на тактовые входы счетчика? Но даже если компилятор сообразит подать туда клоки, то все равно, к комбинационной части добавятся еще N разрядов счетчика. И все это надо будет уложить в период тактовой частоты. Поэтому я так стараюсь никогда не делать. Простите но это просто глупость - Вы действительно считаете что счетчик описанный в FSM будет по другому синтезирован чем счетчик описанный вне FSM? Может приведете примеры такого "безобразия" ? Успехов! Rob.
|
|
|
|
|
Jun 7 2018, 19:20
|
Гуру
     
Группа: Свой
Сообщений: 2 435
Регистрация: 6-10-04
Из: Петербург
Пользователь №: 804

|
Цитата(flammmable @ Jun 7 2018, 13:32)  1. Конструкция языка/библиотеки/устройства претерпела изменения за последнее время (или такое изменение напрашивается в ближайшем будущем). Пример: инициализация регистров при запуске ПЛИС "в железе". На сколько понял читая материалы в сети, в 90-е регистры инициализировались исключительно нулями. Сейчас ситуация поменялась, но ряд комментаторов по старой памяти утверждают, что это невозможно и сами не используют эту удобную опцию.
Таким образом, если времени нет, разумеется приходится "ориентироваться на результат". Но если оно есть, почему бы не выяснить как именно работает тот или иной подход и почему именно так. Плясать надо не от библиотек, а от семейства fpga и его структуры. Как раньше говорили, если семейство на базе статической ячейки памяти, регистровая структура инициализируется нулями, что можно использовать для начальной установки регистров в любое значение. Другое дело что (например Альтера) ранние синтезаторы не позволяли это делать простым присваиванием в теле модуля. А теперь могут. Суть от времени не меняется. CODE `timescale 1 ns / 1 ps `define ena_90 module initial_set #( parameter set = 8'd8 ) ( input clk, input [7:0] data_in, input ena, output [7:0] data_out );
`ifdef ena_90 reg [7:0] dffe_rg = 8'd0;
assign data_out = dffe_rg ^ set;
always @ (posedge clk) begin if (ena) dffe_rg <= data_in ^ set; end
`else
reg [7:0] dffe_rg = 8'd8;
assign data_out = dffe_rg;
always @ (posedge clk) begin if (ena) dffe_rg <= data_in; end
`endif
endmodule
|
|
|
|
|
Jun 8 2018, 02:18
|
Знающий
   
Группа: Свой
Сообщений: 802
Регистрация: 11-05-07
Из: Томск
Пользователь №: 27 650

|
Цитата(RobFPGA @ Jun 8 2018, 01:39)  Приветствую!
Простите но это просто глупость - Вы действительно считаете что счетчик описанный в FSM будет по другому синтезирован чем счетчик описанный вне FSM? Может приведете примеры такого "безобразия" ?
Успехов! Rob. Думаю тут речь идёт больше о стиле написания кода, формально счётчик конечно будет одинаково синтезироваться в обоих случаях. Я тоже стараюсь в автоматы ничего лишнего не запихивать по двум причинам : 1. Код легче читается - в автомате ты сосредотачиваешься только на переходах и условиях перехода, всё остальное - в отдельные процессы. 2. При таком стиле труднее допустить ошибку (управление автоматом асинхронным сигналом, забыл описать else после if - получил латч и т.п.). Меньше портянка FSM - сложнее облажаться в мелочах. Это больше про coding style ИМХО. P.S. Топикстартеру - у меня есть корка именно под FT2232H Async 245 интерфейс, правда она под Xilinx-ISE-XPS, но никто же не обещал что будет легко. Надо?
|
|
|
|
|
Jun 8 2018, 05:31
|
Участник

Группа: Участник
Сообщений: 30
Регистрация: 4-06-18
Пользователь №: 104 848

|
Цитата(Bad0512 @ Jun 8 2018, 05:18)  Думаю тут речь идёт больше о стиле написания кода, формально счётчик конечно будет одинаково синтезироваться в обоих случаях. Я тоже стараюсь в автоматы ничего лишнего не запихивать по двум причинам : 1. Код легче читается - в автомате ты сосредотачиваешься только на переходах и условиях перехода, всё остальное - в отдельные процессы. 2. При таком стиле труднее допустить ошибку (управление автоматом асинхронным сигналом, забыл описать else после if - получил латч и т.п.). Меньше портянка FSM - сложнее облажаться в мелочах. Это больше про coding style ИМХО.
P.S. Топикстартеру - у меня есть корка именно под FT2232H Async 245 интерфейс, правда она под Xilinx-ISE-XPS, но никто же не обещал что будет легко. Надо? Буду очень признателен!
|
|
|
|
|
Jun 8 2018, 06:43
|
Частый гость
 
Группа: Свой
Сообщений: 134
Регистрация: 9-11-12
Из: г. Брянск
Пользователь №: 74 311

|
Цитата(RobFPGA @ Jun 7 2018, 21:39)  Простите но это просто глупость - Вы действительно считаете что счетчик описанный в FSM будет по другому синтезирован чем счетчик описанный вне FSM? Может приведете примеры такого "безобразия" ? Здесь, наверное, имелось ввиду то, что если вынести счетчик за пределы автомата и управлять им одним сигналом, то в реализации между автоматом и счетчиком путь сигнала разрешения будет более "компактным", чем если считать прямо в автомате. В этом случае между автоматом и счетчиком будет куча дополнительной логики. По таймингам такое решение будет хуже. Сам грешен, делаю счетчики прямо в автоматах. По таймингам обычно проблем с этим нет. Но что-то подсказывает, что первый вариант более правильный. Во-первых, мы упрощаем описание автомата, делая его более читабельным. Во-вторых, даем синтезатору больше свободы для раскладывания по кристаллу остальной части схемы, там где могут быть более чувствительные к задержкам пути.
|
|
|
|
|
Jun 8 2018, 06:58
|
Знающий
   
Группа: Свой
Сообщений: 802
Регистрация: 11-05-07
Из: Томск
Пользователь №: 27 650

|
Цитата(flammmable @ Jun 8 2018, 12:31)  Буду очень признателен! Держите.
|
|
|
|
|
Jun 8 2018, 07:39
|
Участник

Группа: Участник
Сообщений: 30
Регистрация: 13-04-17
Из: Зеленоград
Пользователь №: 96 508

|
Цитата(flammmable @ Jun 7 2018, 13:32)  Пример: инициализация регистров при запуске ПЛИС "в железе". На сколько понял читая материалы в сети, в 90-е регистры инициализировались исключительно нулями. Сейчас ситуация поменялась, но ряд комментаторов по старой памяти утверждают, что это невозможно и сами не используют эту удобную опцию. И на сегодняшний день есть достаточное количество причин не использовать начальную инициализацию регистров. 1. Только для динамически реконфигурируемых схем, не поддерживается в ASIC. 2. Накладывает ограничение на синтез, такие регистры не могут быть оптимизированы. 3. Использование инициализации вместо сброса ограничивает reuse модуля в других проектах где сброс реально нужен. 4. Нет гарантии поддержки инициализации всеми синтезаторами. 5. В случае использования инициализации вместо сброса нет задержки на время стабилизации клока от PLL 6. В случае использования инициализации вместо сброса нет возможности задержки выхода схемы из сброса. 7. В случае использования инициализации вместо сброса нет возможности сброса схемы в процессе работы 8. Не поддерживается ALTERA Partial Reconfiguration. 9. Стандарты могут требовать сброса в явном виде.
|
|
|
|
|
Jun 8 2018, 08:35
|
Профессионал
    
Группа: Свой
Сообщений: 1 214
Регистрация: 23-12-04
Пользователь №: 1 643

|
Приветствую! Цитата(dima32rus @ Jun 8 2018, 09:43)  Здесь, наверное, имелось ввиду то, что если вынести счетчик за пределы автомата и управлять им одним сигналом, то в реализации между автоматом и счетчиком путь сигнала разрешения будет более "компактным", чем если считать прямо в автомате. В этом случае между автоматом и счетчиком будет куча дополнительной логики. По таймингам такое решение будет хуже. Сам грешен, делаю счетчики прямо в автоматах. По таймингам обычно проблем с этим нет. Но что-то подсказывает, что первый вариант более правильный. Во-первых, мы упрощаем описание автомата, делая его более читабельным. Во-вторых, даем синтезатору больше свободы для раскладывания по кристаллу остальной части схемы, там где могут быть более чувствительные к задержкам пути. Очень бы хотелось примеры увидить что бы быть уверенным что мы говорим об одном и том же. Типа такого - думаете что для cnt1 будет более оптимальный путь управления чем для cnt2 ? CODE module test #(CNT_WH=4) ( input wire clk , input wire rst , input wire cnt_en, output logic [CNT_WH-1:0] cnt1 , output logic [CNT_WH-1:0] cnt2 );
typedef enum logic [1:0] { ST_0 = '0, ST_1, ST_2, ST_3 } e_ST_t;
e_ST_t st_nx, st_fsm;
logic cnt1_clr; logic cnt1_inc; logic [CNT_WH-1:0] cnt2_nx;
always_ff @(posedge clk) begin if (rst) begin st_fsm <= ST_0; cnt1 <= '0; cnt2 <= '0; end else begin st_fsm <= st_nx; cnt2 <= cnt2_nx;
if (cnt1_clr) begin cnt1 <= '0; end else if (cnt1_inc) begin cnt1 <= cnt1 + 1; end
// if (st_fsm==ST_0) begin // cnt1 <='0; // end // else if (st_fsm==ST_1 || st_fsm==ST_2) begin // cnt1 <= cnt1 + 1; // end end end
always_comb begin : p_st_fsm st_nx <= st_fsm; cnt2_nx <= cnt2;
cnt1_clr<= 0; cnt1_inc<= 0;
case (st_fsm) ST_0: begin cnt1_clr<= 1; cnt2_nx <= '0; st_nx <= ST_1; end
ST_1: begin if (cnt_en) begin cnt1_inc<= 1; cnt2_nx <= cnt2 + 1;
if (cnt1==((1<<CNT_WH)/2-1)) begin st_nx <= ST_2; end end end
ST_2: begin if (cnt_en) begin cnt1_inc<= 1; cnt2_nx <= cnt2 + 1;
if (cnt2==((1<<CNT_WH)-1)) begin st_nx <= ST_3; end end end
ST_3: begin if (cnt2==cnt1) begin st_nx <= ST_0; end end
default : st_nx <= ST_0; endcase end endmodule Удачи! Rob.
|
|
|
|
|
Jun 8 2018, 10:20
|
Частый гость
 
Группа: Свой
Сообщений: 134
Регистрация: 9-11-12
Из: г. Брянск
Пользователь №: 74 311

|
Ну вот такой теоретический пример CODE module cnt_test ( //Global input CLK_i , input nRESET_i ,
//Input input IN_PULSE0_i , input IN_PULSE1_i ,
//Control input ENA_CNT_i , input CLR_CNT_i ,
//Output of counter output [31:0] OUT_CNT0_o32 , output [31:0] OUT_CNT1_o32 );
reg [31:0] out_cnt0_o32; reg [31:0] out_cnt1_o32; assign OUT_CNT0_o32 = out_cnt0_o32; assign OUT_CNT1_o32 = out_cnt1_o32;
reg [7:0] state; //FSM localparam integer sIDLE = 0, sCOUNT = 1, sSTOP = 2;
reg cnt_1_en; reg cnt_1_clr;
always @(posedge CLK_i or negedge nRESET_i) begin if(~nRESET_i) begin out_cnt0_o32 <= 0; cnt_1_en <= 0; cnt_1_clr <= 0; state <= sIDLE; end else begin case(state)
sIDLE: begin cnt_1_clr <= 0; if(ENA_CNT_i) state <= sCOUNT; end
sCOUNT: begin if(ENA_CNT_i) begin if(IN_PULSE_i) begin out_cnt0_o32 <= out_cnt0_o32 + 1'b1; cnt_1_en <= 1'b1; end else cnt_1_en <= 0; end else begin cnt_1_en <= 0; state <= sSTOP; end end
sSTOP: begin state <= sIDLE; if(CLR_CNT_i) begin out_cnt0_o32 <= 0; cnt_1_clr <= 1'b1; end end
endcase end
end
always @(posedge CLK_i or negedge nRESET_i) begin if(~nRESET_i) begin out_cnt1_o32 <= 0; end else begin if(cnt_1_en) out_cnt1_o32 <= out_cnt1_o32 + 1'b1; else if(cnt_1_clr) out_cnt1_o32 <= 0; end end
endmodule Два счетчика. Один работает напрямую из автомата, второй через сигналы разрешения и сброса. Насчет упрощения кода при выносе счетчика из автомата я ошибся - проще не получилось. Для работы встроенного счетчика достаточно всего двух строк. Выносного счетчика - целых 5. Вынос счетчика усложнил код. Плюс на выносной счетчик будет работать с задержкой в один такт. И это надо учитывать там, где критично. Но с точки зрения времянок выносной счетчик выиграет. Как раз за счет промежуточных триггеров на сигналах разрешения/сброса. На практике я это не проверял, это просто рассуждения. Warning from admin: Используйте codebox для оформления длинных блоков кода. Не загромождайте сообщения!
Сообщение отредактировал makc - Jun 8 2018, 10:27
Причина редактирования: предупреждение про codebox
|
|
|
|
|
Jun 8 2018, 10:48
|
Профессионал
    
Группа: Свой
Сообщений: 1 214
Регистрация: 23-12-04
Пользователь №: 1 643

|
Приветствую! Цитата(dima32rus @ Jun 8 2018, 13:20)  ... Два счетчика. Один работает напрямую из автомата, второй через сигналы разрешения и сброса. Насчет упрощения кода при выносе счетчика из автомата я ошибся - проще не получилось. Для работы встроенного счетчика достаточно всего двух строк. Выносного счетчика - целых 5. Вынос счетчика усложнил код. И это еще простой случай - а если еще надо загружать разные счетчики разными значениями и считать их вверх/вниз/поперек? Цитата(dima32rus @ Jun 8 2018, 13:20)  Плюс на выносной счетчик будет работать с задержкой в один такт. И это надо учитывать там, где критично. Но с точки зрения времянок выносной счетчик выиграет. Как раз за счет промежуточных триггеров на сигналах разрешения/сброса. Вот оно что - понятное дело если вы пропускаете сигналы управления через регистр то это уменьшает времянку - но это ведь разная логика работы счетчиков!!! При чем тут реализация счетчиков в FSM? Удачи! Rob.
|
|
|
|
|
Jun 8 2018, 10:50
|

В поисках себя...
   
Группа: Свой
Сообщений: 729
Регистрация: 11-06-13
Из: Санкт-Петербург
Пользователь №: 77 140

|
Цитата(dima32rus @ Jun 8 2018, 13:20)  Да собственно с точки зрения синтезатора - ему без разницы где счетчик описан. Внутри FSM, или снаружи. Лично проверял когда только-только начинал изучать FPGA. Выдача сигнала разрешения автоматом, либо счет счетчика в каком-то из состояний синтезируются в одно и тоже. Тут дело в другом, как я считаю. При стиле описания "все в одном": 1. Легче допустить ошибку в описании счетчика - и сложнее её обнаружить. 2. Увеличивается количество строк кода, что неизменно ухудшает его читаемость. Да и документировать такой код становится несколько сложнее. А документирование кода очень, и очень важная составляющая HDL проектов. 3. Увеличивается время отладки. Ибо чем больше строк в коде - тем дольше этот код отлаживать. При стиле описания "Автомат управляет счетчиком": 1. Легче описывать автомат, т.е нет необходимости описывать счетчики. Счетчик пишется один раз, и далее используется повторно. 2. Упрощается автомат - банально меньше писанины. И вместо инкрементирования счетчика внутри автомата - автомат выдает сигнал разрешения работы счетчику. 3. Упрощается тестирование - не работает счтчик, значит проблема в автомате. Проще найти ошибку. 4. Отдельному описанию счетчика можно задать кучу параметров: остановка счета при максимальном значении, максимальное значение счета, направление счета, вывод своего состояния на консоль и пр. И главное - счетчик описан один раз, и дальше повторно используется, и совершенствуется добавлением новых параметров. При таком подходе - мы имеем собственную проверенную библиотеку, которая имеет 100% работоспособность. И из этой библиотеки мы собираем свои проекты как из конструктора. За счет всевозможных настроек имеем большую гибкость, и уменьшение вероятности совершить "глупую" ошибку, которую, как правило, найти сложнее всего. Я от такого подхода вижу только полюсы. Цитата И это еще простой случай - а если еще надо загружать разные счетчики разными значениями и считать их вверх/вниз/поперек? wacko.gif Обычный счетчик с синхронной загрузкой, и сигналом разрешения загрузки. Автомат выдает сигнал разрешения загрузки и выдает данные, которые надо загрузить.
|
|
|
|
|
Jun 8 2018, 11:06
|
Частый гость
 
Группа: Свой
Сообщений: 134
Регистрация: 9-11-12
Из: г. Брянск
Пользователь №: 74 311

|
Цитата(RobFPGA @ Jun 8 2018, 13:48)  Вот оно что - понятное дело если вы пропускаете сигналы управления через регистр то это уменьшает времянку - но это ведь разная логика работы счетчиков!!! При чем тут реализация счетчиков в FSM? Согласен, если через триггеры не пропускать, то, наверное, особой разницы в реализации не будет. Цитата(Flip-fl0p @ Jun 8 2018, 13:50)  Упрощается автомат - банально меньше писанины. И вместо инкрементирования счетчика внутри автомата - автомат выдает сигнал разрешения работы счетчику. Получается, наоборот. Если счетчик внутри автомата - достаточно просто одной строчкой делать инкремент там, где это надо. Если счетчик выносим, то надо выдать сигнал разрешения, а потом его еще снять. Получается 2 и более строк кода на одну только функцию. А еще есть сброс, загрузка...
|
|
|
|
|
Jun 8 2018, 11:26
|

В поисках себя...
   
Группа: Свой
Сообщений: 729
Регистрация: 11-06-13
Из: Санкт-Петербург
Пользователь №: 77 140

|
Цитата(dima32rus @ Jun 8 2018, 14:06)  Согласен, если через триггеры не пропускать, то, наверное, особой разницы в реализации не будет.
Получается, наоборот. Если счетчик внутри автомата - достаточно просто одной строчкой делать инкремент там, где это надо. Если счетчик выносим, то надо выдать сигнал разрешения, а потом его еще снять. Получается 2 и более строк кода на одну только функцию. А еще есть сброс, загрузка... А всё дело в том, что Вы сигнал разрешения выдаете в "синхронной части" автомата. Поэтому ещё надо ещё где-то описать его снятие. Я все делаю в "комбинацонной" части автомата. Примерно вот так: CODE --==================================================================== -- Кобинационная часть работы автомата --==================================================================== output_data : process(all) begin --============================= -- Сигналы по-умолчанию --============================= irdreq <= '0'; -- Запроса на чтение буффера нет aclr_buff <= '0'; -- Беффер не сбрасываем V_offset_ena <= '0'; -- Счет счетчику запрещен V_offset_sld_ena <= '0'; -- Ничего в него не загружаем H_offset_ena <= '0'; -- Счет счетчику запрещен H_offset_sld_ena <= '0'; -- Ничего в него не загружаем case pres_state is -- Анализируем текущее состояние автомата --================================================================================ =============================== when V_offset => -- В состоянии ожидания вертикального смещения if (DE_re = '1') then -- При начале периода активных данных V_offset_ena <= '1'; -- Инкрементируем счетчик вертикального смещения end if; --================================================================================ =============================== when H_offset => -- В состоянии горизонтального смещения H_offset_ena <= '1'; -- Инкрементируем счетчик горизонтального смещения --================================================================================ =============================== when PIP_image => -- В состоянии вывода масштабированного изображения irdreq <= '1'; -- Разрешаем чтение буффера с данными --================================================================================ =============================== when clear_buff => -- В состоянии сброса буффера aclr_buff <= '1'; -- Разрешаем его сброс --================================================================================ =============================== when wait_active_video => -- В состоянии ожидания начала периода активных данных H_offset_sld_ena <= '1'; -- Загружаем в счетчик горизонтального смещения значение этого смещения --================================================================================ =============================== when stop => -- В осостоянии остановки отрисованного изображание V_offset_sld_ena <= '1'; -- Загружаем счетчики необходимым значением смещения H_offset_sld_ena <= '1'; when others => null; end case; end process; А вообще, Вы можете в синхронной части задать значение "по-умолчанию" и в нужном состоянии выдавать сигналы разрешения. Т.е "по-умолчанию" сигнал разрешения снимается. А в каком-то из состояний он выставляется. Получите то-же самое, но только сдвинутое на такт. Более того. При таком стиле как у меня, если комбинационный сигнал зависит только от состояния автомата, то он идет с триггера, защелкнувшего это состояние, если идет кодировка "One-Hot" И выходы автомата у меня получаются сфоримрованны не комбинационной схемой, а триггером  Например сигнал "aclr_buff " - это выход триггера. Соответственно он не имеет иголок, и можно применить как асинхронный сброс. Во всяком случае у Altera DC FIFO имеет только такой тип сброса....
|
|
|
|
|
Jun 8 2018, 11:30
|
Профессионал
    
Группа: Свой
Сообщений: 1 214
Регистрация: 23-12-04
Пользователь №: 1 643

|
Приветствую! Цитата(Flip-fl0p @ Jun 8 2018, 13:50)  Обычный счетчик с синхронной загрузкой, и сигналом разрешения загрузки. Автомат выдает сигнал разрешения загрузки и выдает данные, которые надо загрузить.  Ну и прикинте во что это выливается со внешним счетчиком. Фактически в автомате Вы сделаете все кроме непосредственного инкремента. А если считать надо не на +/-1 ? В общем случае для счетчика нужно 4 сигнала управления - set, set_value, count, count_value. Получается: Цитата(Flip-fl0p @ Jun 8 2018, 13:50)  Тут дело в другом, как я считаю. При стиле описания "все в одном": 1. Легче допустить ошибку в описании счетчика - и сложнее её обнаружить. 2. Увеличивается количество строк кода, что неизменно ухудшает его читаемость. Да и документировать такой код становится несколько сложнее. А документирование кода очень, и очень важная составляющая HDL проектов. 3. Увеличивается время отладки. Ибо чем больше строк в коде - тем дольше этот код отлаживать. Встроенный счетчик в FSM 2 линий для переменных cnt_cur, cnt_next 2 линий для сброса и cnt_cur<=cnt_next 1 линии начале FSM (default value) cnt_next <= cnt_cur 1 линия в каждой ветви где счетчиком надо управлять. Цитата(Flip-fl0p @ Jun 8 2018, 13:50)  При стиле описания "Автомат управляет счетчиком": 1. Легче описывать автомат, т.е нет необходимости описывать счетчики. Счетчик пишется один раз, и далее используется повторно. 2. Упрощается автомат - банально меньше писанины. И вместо инкрементирования счетчика внутри автомата - автомат выдает сигнал разрешения работы счетчику. При раздельном описании 9 линий кода для инстацирования модуля счетчика, 5 линии кода для описания сигналов управления, и собственно счетчика 4 линии для сигналов управления в начале FSM (default value) 2 (в среднем) линии для сигналов управления в каждой ветви где счетчиком надо управлять. Интересная экономия на размере кода получается  Цитата(Flip-fl0p @ Jun 8 2018, 13:50)  . Отдельному описанию счетчика можно задать кучу параметров: остановка счета при максимальном значении, максимальное значение счета, направление счета, вывод своего состояния на консоль и пр. И главное - счетчик описан один раз, и дальше повторно используется, и совершенствуется добавлением новых параметров. Это из другой оперы - тут счетчик полностью управляется FSM и не может считать как ему хочется. Удачи! Rob.
|
|
|
|
|
Jun 8 2018, 11:49
|
Профессионал
    
Группа: Свой
Сообщений: 1 214
Регистрация: 23-12-04
Пользователь №: 1 643

|
Приветствую! Цитата(Flip-fl0p @ Jun 8 2018, 14:39)  Экономия получается на отладке в Modelsim...., которая занимает ~80% времени. На чем именно экономия? Ведь отлаживать Вы будете генерацию сигналов управления в FSM точно так же. А кода при этом как минимум в 2 раза больше. Удачи! Rob.
|
|
|
|
|
Jun 8 2018, 12:08
|

В поисках себя...
   
Группа: Свой
Сообщений: 729
Регистрация: 11-06-13
Из: Санкт-Петербург
Пользователь №: 77 140

|
Цитата(RobFPGA @ Jun 8 2018, 14:49)  .... При раздельном описании 9 линий кода для инстацирования модуля счетчика, 5 линии кода для описания сигналов управления, и собственно счетчика 4 линии для сигналов управления в начале FSM (default value) 2 (в среднем) линии для сигналов управления в каждой ветви где счетчиком надо управлять. .... Приветствую! На чем именно экономия? Ведь отлаживать Вы будете генерацию сигналов управления в FSM точно так же. А кода при этом как минимум в 2 раза больше. Удачи! Rob. Вот только мы экономим не строчки кода, а время. И из всех строчек кода мы фактически должны проверить только сигналы управления счетчиком. Ибо все остальное было проверено и отлажено ранее. Если взять счетчик с сигналом разрешения работы, и синхронной загрузкой мы в FSM формируем только эти сигналы, ну возможно ещё данные, которые загружаем в счетчик, соответственно и проверяем только их. Остальное мы отладили раньше. А если в счетчике есть параметр вывода его значения на консоль - то мы можем вообще смотреть в консоли то что выдает счетчик. Или вообще писать все это добро в файл. И включать этот режим очень быстро и просто - в настройках модуля. А вообще спор ни о чем. Каждый делает так, как считает правильным . Ибо так как правильно зависит от личного опыта, и субъективного взгляда на организацию HDL проектов.
|
|
|
|
|
Jun 8 2018, 12:19
|
Частый гость
 
Группа: Свой
Сообщений: 134
Регистрация: 9-11-12
Из: г. Брянск
Пользователь №: 74 311

|
Переделал модуль. Теперь счетчик, вынесенный из автомата, описывается в комбинационной части. Да, если так, то писанины стало меньше. С точки зрения реализации же, по-моему, оба счетчика абсолютно одинаковы. Но теперь читается хуже. Счетчик, который в автомате, сразу виден в одном месте. А вот работа второго счетчика не так очевидна. Для этого надо смотреть на сам автомат, а также на логику, которая управляет счетчом. Причем каждый сигнал описывается отдельно. CODE module cnt_test ( //Global input CLK_i , input nRESET_i ,
//Input input IN_PULSE_i ,
//Control input ENA_CNT_i , input CLR_CNT_i ,
//Output of counter output [31:0] OUT_CNT0_o32 , output [31:0] OUT_CNT1_o32 );
reg [31:0] out_cnt0_o32; reg [31:0] out_cnt1_o32; assign OUT_CNT0_o32 = out_cnt0_o32; assign OUT_CNT1_o32 = out_cnt1_o32;
reg [7:0] state; //FSM localparam integer sIDLE = 0, sCOUNT = 1, sSTOP = 2;
wire cnt_1_en = (state == sCOUNT) & ENA_CNT_i & IN_PULSE_i; wire cnt_1_clr = (state == sSTOP) & CLR_CNT_i;
always @(posedge CLK_i or negedge nRESET_i) begin if(~nRESET_i) begin out_cnt0_o32 <= 0; state <= sIDLE; end else begin case(state)
sIDLE: begin if(ENA_CNT_i) state <= sCOUNT; end
sCOUNT: begin if(ENA_CNT_i) begin if(IN_PULSE_i) begin out_cnt0_o32 <= out_cnt0_o32 + 1'b1; end end else begin state <= sSTOP; end end
sSTOP: begin state <= sIDLE; if(CLR_CNT_i) begin out_cnt0_o32 <= 0; end end
endcase end
end
always @(posedge CLK_i or negedge nRESET_i) begin if(~nRESET_i) begin out_cnt1_o32 <= 0; end else begin if(cnt_1_en) out_cnt1_o32 <= out_cnt1_o32 + 1'b1; else if(cnt_1_clr) out_cnt1_o32 <= 0; end end
endmodule
Если же делать так, чтобы счетчик считал только в одном конкретном состоянии автомата, тогда надо добавлять состояний. В одном счетчик тикает, в другом простаивает. Однако тогда между автоматом и счетчиком будет минимум логики. Особенно при кодировке One-Hot.
|
|
|
|
|
Jun 13 2018, 08:31
|
Участник

Группа: Участник
Сообщений: 30
Регистрация: 4-06-18
Пользователь №: 104 848

|
Цитата(iosifk @ Jun 7 2018, 14:45)  Если Вы знаете, что такое "автомат", то попробуйте представить себе набор автоматов - мастер-слэйв-слэйв. Отличный паттерн разработки! Спасибо! А не подскажите ли, как лучше по вашему опыту: все автоматы делать в одном блоке always - в разных case (я сейчас так сделал) или "один автомат - один модуль"?
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|