Как стать автором
Обновить

Комментарии 36

НЛО прилетело и опубликовало эту надпись здесь
Можете пояснить, как работает ваш Мажоритарный элемент? Т.е. от каких ошибок от избавляет.
Отвечу за автора. Мажоритарный элемент это вот что. Берутся 3, 5, 7 (короче говоря нечётное число) одноразрядных регистров и собираются в сдвиговый регистр, по которому гонится сигнал с входной линии. Если большинство разрядов равны 1, выход элемента равен 1. Если 0 — то 0. Такая штуковина спасает от «иголок» — коротких помех на входной линии. Представьте себе, что на линию подана 1. Но в силу каких-то причин (например наводок), на короткое время по ней проскальзывает 0. Этот 0 попадает в наш мажоритарный элемент. Но поскольку большинство его регистров установлено в 1, на выходе так 1 и останется. Т.е. мажоритарный элемент подавит эту помеху. Если например Вы работаете с длинными соплями, а источник сигнала не слишком мощный, такую защиту ставить необходимо. Сам это делал не далее как этой весной, работая с микроконтроллером STM8.
По описанию, это не иначе как медианный фильтр.
Да, фактически так. Я в своё время изобрел это сам. И таким вопросом не заморачивался. Давит помеху и ладно. Как Конфуций с кошкой :)))
Блокирующие присваивания внутри тактируемого блока? Это грубая ошибка.

Я про вот этот кусок, например:
Заголовок спойлера
always @(posedge clk, negedge nreset)
begin
	if(!nreset) begin
		// Установка передатчика в исходное состояние
		div4 <= 2'd0;
		s <= 4'd10;
		gotdata <= 1'b0;
	end else begin	
		// Пока нет признака передачи стартового бита в этом цикле
		sendstart = 1'b0;
	
		// Начальная установка признака запроса данных на передачу
		canfetch = wr;
		
		if(div4 == 2'd0) begin
			case(s)
				4'd0:
					begin
						// Передача стартового бита будет инициирована ниже
						sendstart = 1'b1;  // ОШИБКА
						
						// Передатчик занят, нельзя запрашивать новые данные
						canfetch = 1'b0;  // ОШИБКА



Чтобы понять что не так, посмотрите доклады Каммингса (один из разработчиков стандарта SystemVerilog, а именно так называется язык с 2009 года).
Или вот этот замечательный материал из университета Berkeley (сразу предупреждаю, их материал про конечные автоматы устарел — их следует писать в одном always блоке, а не в двух или трёх)
«Или вот этот замечательный материал из университета Berkeley (сразу предупреждаю, их материал про конечные автоматы устарел — их следует писать в одном always блоке, а не в двух или трёх)»

Если не затруднит, можно ссылку на материал, подтвеждающий эти слова?
Спасибо!
Кажется, я был неправ. Вычитал эту рекомендацию на форуме Xilinx (ссылка на рабочем компьютере, сейчас найти не смог), где утверждалось, что синтезатор Xilinx может не распознавать FSM как FSM если он разбит на несколько always-блоков. А сейчас в гайде Altera увидел вот такие слова:
To ensure proper recognition and inference of state machines and to improve the quality of results, Altera recommends that you observe the following guidelines, which apply to both Verilog HDL and VHDL:
• Assign default values to outputs derived from the state machine so that synthesis does not generate unwanted latches.
• Separate the state machine logic from all arithmetic functions and data paths, including assigning output value

А дальше идут примеры кода с двумя и тремя always-блоками.

Что интересно: в гайде от Xilinx единственный пример FSM с одним always-блоком без объяснений почему.
Ни разу не ошибка, т.к. видно, что эти 2 регистра используются как локальные переменные внутри этого always — сначала присваиваются блокирующим образом, потом используются чуть ниже по коду, снаружи не используются и инфу между тактами не хранят. Для понятности их следовало, конечно же, объявить внутри begin/end, но и так сойдёт.
«Локальные переменные», «используются ниже по коду» — вы вообще хоть строчку Verilog для реального устройства в жизни написали или думаете, что это такой язык программирования? Это не язык программирования, это язык описания аппаратуры.

Я посмотрел код передатчика дальше. Всё ещё хуже — чтение сигнала, который присваивается в том же always блоке с помощью = (блокирующего присваивания) это состояние гонки, которое приводит к неопределённому поведению. Это непонимание фундаментальнейших основ Verilog и того как он синтезируется в RTL. Если это вообще работает, то автору крупно повезло.

Нет никакого «ниже по коду», есть триггеры, которые защёлкиваются по фронту частоты и всё. И если сигнал clk идёт до триггера, который читает sendstart дольше, чем до того триггера, который sendstart выставляет, то условие if (sendstart) сработает на том же такте. А такое может быть, потому что синтезатор не обязан правильно разводить неправильный код.

А вам рекомендую взять книжку Харрис «Цифровая схемотехника и архитектура компьютера» и прочитать что такое защёлкивание по фронту, а также прочесть те документы, ссылки на которые я привёл выше.
Во-первых, спасибо за полезные ссылки, код однозначно буду переделывать. Но появился вопрос:
Всё ещё хуже — чтение сигнала, который присваивается в том же always блоке с помощью = (блокирующего присваивания) это состояние гонки
. У меня sendstart сначала вычисляется и только потом читается. Последовательностей, которые Вы привели ниже в примерах (сигнал сначала читаем, а потом его же блокирующе модифицируем), у меня нет (то, что так делать нельзя, было интуитивно понятно). Просто во многих примерах в инете я видел применение блокирующих и неблокирующих присваиваний внутри одного always-блока, причем авторы утверждали, что синтезатор с этим успешно справляется. Да, в моей реализации все вычисляемые при помощи блокирующих присваиваний сигналы вполне можно вынести за always-блоки, просто формулы их вычислений повторят нынешние условия if/else и станут более громоздкими и сложными для понимания (человеком). Да, я читал статью от dsmv2014 и думал, что если мне понадобится симуляция, то я смогу воспользоваться этим хаком. Так все-таки вопрос к Вам, как профессионалу: синтезатору в моем случае все равно, где расположены блокирующие присваивания? Не симулятору, а именно синтезатору? А если не все равно, то почему именно? Можно разжевать в комментарии, можно отправить к источнику. В любом случае я буду весьма благодарен за совет.
Во-первых я не знаю каким синтезатором вы пользуетесь. Есть Vivado, есть Quartis, есть ISE, есть Sinplify и много других. Полагаться на особенности реализации, которые прощают нарушения, лучше не стоит, лучше соблюдать стандарт Verilog / SystemVerilog. А ещё лучше читать руководства (их делает каждый серьёзный производитель синтезаторов) по кодингу — где есть список поддерживаемых конструкций HDL и рекомендации по стилю и различным паттернам.
Например: Vivado — см. гл. 4 Quartus — см. гл. 11,12

Ситуация с Verilog в рунете катастрофическая. В университетах (я имею в виду даже вузы типа МАИ) зачастую учат люди, которые ПЛИС никогда в руках не держали и рассказывают какую-то кашу из синтезируемого и несинтезируемого подмножества языков. Даже в статьях на Хабре много ошибок бывает.

Теперь к коду:
У меня sendstart сначала вычисляется и только потом читается.

Что такое «потом»? Код на Verilog не императивный, он декларативный. Его обманчивая похожесть на C/Java — его самый большой недостаток. Если внутри always-блока вы напишете несколько if на одном уровне вложенности, они все будут проверяться ПАРАЛЛЕЛЬНО и ОДНОВРЕМЕННО (с поправкой на время распространения фронта тактирующего сигнала). Поэтому установка canfetch внутри этого условия
if(div4 == 2'd0) begin
и проверка условия
if (canfetch)
происходят параллельно. Что самое неприятное: нельзя сказать, успеет он в том же такте проверить флаг canfetch или нет. Как разведётся.

У триггера есть внутреннее состояние и выход. Неблокирующее присваивание <= делает так, что внутреннее состояние запоминается СЕЙЧАС (из правой части присваивания), а на выход оно подаётся СО СЛЕДУЮЩИМ ФРОНТОМ тактирующего сигнала. Синтезаторы понимают какой частотой тактируется какой триггер и следят за тем, чтобы время распространения сигнала по всей схеме не превышало такта частоты, иначе будет большая красная ошибка на этапе имплементации с сообщением о том, что timings failed.

Время в тактируемом коде на Verilog двигается квантами. Это как конвеер с шаговым двигателем. Пришёл такт частоты — выполняются все нужные действия (синтезатор гарантирует корректность в случае тактируемых блоков и неблокирующих присваиваний). Затем всё замирает и вплоть до следующего такта НИЧЕГО НЕ МЕНЯЕТСЯ — триггеры держать свои сигналы на выходе (их в это время можно спокойно читать) и всё.

Все строчки, все инструкции внутри одного always-блока выполняются ПАРАЛЛЕЛЬНО и ПОТЕНЦИАЛЬНО ОДНОВРЕМЕННО. Поэтому писать вот так

always @(posedge clk) begin
    a = b;
    b = c;
end

значит создавать неопределённое поведение в случае тактируемого блока (никто не сможет сказать что будет в а к следующему такту), а такая конструкция

always @(*) begin
    a = a + 1;
end

создаст то, что называется «комбинаторной петлёй», т.е. к схеме, которая будет работать с максимальной частотой в бесконечном цикле.

И вообще — тайминги в схемах могут меняться от температуры и напряжения питания, поэтому делать комбинаторные схемы следует в крайнем случае — когда по-другому просто нельзя. Иногда придётся прописывать много хитрых граничных условий (constraints), где указывать требования на времена распространения сигнала и пр. и пр.
Вот это доступно и полезно. Меня же никто этому не учил, спецов по ПЛИСам рядом нет. В принципиалке взаимосвязи видны, в новом для меня языке нет. В документации, естественно, тонкости упустил. Некоторые данные из интернета оказались в корне неправильные. Проект буду переделывать, это однозначно. В начало добавил предупреждение, что так делать нельзя. Статью оставил именно из-за комментариев. Ибо когда начинал, у меня таких подсказок под рукой не было.
Что такое «потом»? Код на Verilog не императивный, он декларативный. Его обманчивая похожесть на C/Java — его самый большой недостаток. Если внутри always-блока вы напишете несколько if на одном уровне вложенности, они все будут проверяться ПАРАЛЛЕЛЬНО и ОДНОВРЕМЕННО (с поправкой на время распространения фронта тактирующего сигнала).

Вообще то говоря это не совсем так. Verilog, так же как и VHDL содержат как параллельные так и последовательные конструкции. В целом описывается параллельная конструкция, но иногда для удобства описания применяются последовательные конструкции. VHDL имеет для этого более строгие понятия signal и variable, Verilog имеет блокирующие и неблокирующие присваивания.
В примере canfetch используется только внутри always блока причём только для вычисления значений по фронту clk. Это вполне нормальное применение. Синтезатор для этого примера обязан сделать большую комбинационную схему. Недостатком является как раз размер этой комбинационной схемы, но не способ описания.
И как эта большая комбинационная схема будет работать? Там параллельно выставляется значение canfetch и читается. Как я пойму что срабатывает в каком порядке? Считывает ли он выставленное значение canfetch в том же такте или на следующем?
Синтезатор всё сделает в соответствии со стандартом языка, значение canfetch будет использоваться в том же самом такте. canfetch надо рассматривать как временную переменную и не использовать вне этого блока. Автор так и делает. Всё корректно. В VHDL в таком описании используется variable, её в принципе нельзя использовать за пределами process (который является аналогом always блока). Это снижает вероятность ошибки.

Будет ли работать эта большая комбинационная схема сообщит трассировщик, если частота небольшая, например 1 МГц, то вполне может заработать.
Вы согласны с тем, что такого кода следует избегать?
Нет, не согласен. Просто надо чётко представлять во что выливается применение той или иной конструкции.
Если внутри always-блока вы напишете несколько if на одном уровне вложенности, они все будут проверяться ПАРАЛЛЕЛЬНО и ОДНОВРЕМЕННО (с поправкой на время распространения фронта тактирующего сигнала).

Вы лжёте и вводите в заблуждение начинающих. Всё, что написано внутри одного begin/end, выполняется последовательно. То, что присваивается при помощи <=, откладывает присвоение в указанный reg на дельта-время. То, что при помощи = — присваивается сразу же. Такова семантика верилога, и когда вы говорите что это не так, не забывая перейти на личности — демонстрирует именно ваши познания. Пруф: staff.ustc.edu.cn/~songch/download/IEEE.1364-2005.pdf, пункт 9.8.

Согласно верилогу, это должно выполняться последовательно, следовательно и синтезаторы, понимая это, делают соответствующую логику, и никаких 'гонок' не происходит. В данном случае, sendstart становится одним из комбинационных сигналов, подающихся через комбинационную логику на вход реальных триггеров. И именно использование блокирующего присваивания reg'а внутри begin/end как 'локальной переменной' — вполне себе нормальная практика, иногда позволяющая сильно сократить объём кода и увеличить его человекопонятность. Естественно, те reg'и, которые синтезируются в реальные триггеры, не стоит присваивать при помощи =, так как как минимум сразу можно напороться при симуляции.
always @(posedge clk) begin
    a = b;
    b = c;
end

поведение в данном случае полностью определённое и будет просинтезировано аналогично
always @(posedge clk) begin
    a <= b;
    b <= c;
end

На выходе вы получите сдвиговый регистр a < — b < — c.

Если же строчки присвоения поменять местами, то поведение данных блоков будет отличаться — в случае с неблокирующими присваиваниями по-прежнему будет трёхбитовый сдвиговый регистр, а вот в случае с блокирующими — двухбитовый a < — c, при этом сигнал b скорее всего будет исключен в процессе оптимизации синтеза.

Но никаких неопределённостей в данных конструкциях нет, просто нужно понимать как именно они «отображаются» в реальную схему.
А вот в том, что идёт в реальную схему, никогда нельзя присваивать внешние (относительно текущего begin/end) регистры как = [в always @(posedge clk) по крайней мере]. Буквально вчера наткнулся: в тестбенче кто-то давно написал = вместо <=, в моём RTL это выглядело так, будто конкретный регистр присваивается значением другого из будущего (того, что сразу после posedge clk). Суть проста — когда есть множество posedge clk, то симулятор их выполняет вообще говоря в неопределённом порядке. И вот присваивание = из тестбенчевского кода выполнилось раньше моего <=. В результате мой код увидел значение 'из будущего'.
Честно говоря, почти ничего не понял из написанного. Блокирующее присваивание выполняется мгновенно, неблокирующее выполняется после дельта-задержки. Если все always-блоки выполняются в пределах одной дельта-задержки, то действительно возможно появление результата блокирующего присваивания ранее, чем это требовалось. Поэтому выше уже упоминалось, что с этой точки зрения разделение в VHDL на сигналы и переменные удобнее и адекватнее, чем "=" и "<=" в Verilog. Но наличие возможности выстрелить себе в ногу не является достаточным для «это нельзя делать, это всё неправильно, никогда так не пишите».
Вообще использование блокирующих присваиваний внутри always вполне себе допустимо, и даже допустимо чтение сигнала в том же блоке «ниже по коду», в котором он присваивается. Просто это выльется в более длинную цепочку логики, подобные операции будут выполнены в пределах одного такта. Пока это писал, увидел, что ниже вам то же самое уже написали ранее.
В Verilog есть комбинаторные блоки и тактируемые.
Круглые скобки после always @ — это список чувствительности (при изменении сигналов, описанных там, сработает блок). Если там стоит posedge или negedge, то блок будет тактируемым (фронтом или спадом соответственно; если и то, и другое, то DDR). Если просто имена сигналов или *, то комбинаторным (и будет срабатывать при любом изменении перечисленных сигналов). Reg может быть как в комбинаторном, так и в тактируемом блоке, а сам тип логики он не определяет. С типом wire можно использовать только assign, но не = или <=. Вообще reg и wire это дурные путающие абстракции, поэтому в SystemVerilog ввели универсальный тип logic.
Тактируемые:
always @ (posedge clk) begin
    a <= b;
end

Внутри этих блоков можно использовать ТОЛЬКО <= без всяких но и если.

Комбинаторную логику (которая срабатывает «мгновенно», а на самом деле со скоростью распространения сигнала, которая зависит от разводки) можно описать двумя способами:
assign a = b & (c > 0); // здесь a должен быть wire

ИЛИ
always @ (*) begin
    a = b & (c > 0); // здесь a должен быть reg
end

Здесь в списке чувствительности (в круглых скобках) можно записать значения сигналов через запятую, но лучше ставить *, тогда список чувствительности будет формироваться автоматически (туда будут включены все сигналы, от изменения значения которых может измениться состояние блока, т.е. все правые части присваиваний, условия при if и т.д.), а вручную его формировать ТОЛЬКО если нужно исключить какой-то сигнал оттуда.
В комбинаторных блоках ТОЛЬКО блокирующие присваивания.

Больше никаких конструкций в Verilog нет. <= в комбинаторных блоках или = в тактируемых — это или не имеющая смысла чепуха в первом случае или грубейшая ошибка во втором и так писать нельзя никогда.

P.S. Писать = внутри тактируемого блока можно. Если вам нужен генератор псевдослучайных чисел.
Как получить состояние гонки (и неопределённое поведение)?
А вот так:
always @(*) begin
    a = a + 1;
end

always @(posedge clk) begin
    if (a) begin
        a = 0;
    end
    else begin
        a = 1;
    end
end
Во втором процессе нет состояния гонки и отсутствует неопределённое поведение, т.к. if проверяется лишь однажды. На выходе синтеза получится T-триггер.
Поздравляю! Много разработчиков на FPGA когда-то писали свой UART)))

Вы можете перестать занимать PLL, решить вопрос «чтобы не завязавать скорость последующей обработки данных на скорость обмена по UART» и убрать мажоритарную логику и FIFO для данных. И сделать более практичное применение. Просто тактируете модуль частотой обработки данных, а в его параметрах прописываете значение частоты и скорости по UART. Из частоты и скорости можно получить длину счётчика бита UART. Упал Rx, включился счётчик. Дребезг по входу уже будет неважен. Хотя по идее в нормальных условиях и при правильно спроектированной схеме его и не должно быть — сигнал с RS232 надо заводить на FPGA через буфер. Досчитал счётчик до середины — считали бит. Сбрасываем счётчик и отсчитываем бит. Читаем. И так — весь байт. Бита чётности у вас нет, значит потом будет стоп-бит, на середине которого можно проверить его уровень и сбросить счётчик, опять начав ожидать старт-бит — таким нехитрым образом на каждом байте будет осуществляться тактовая подстройка приёма в FPGA под передатчик в ПК.
PLL и FIFO в данном случае для имитации. А также я разбирался, как использовать IP Cores.
Предложенный алгоритм интересен, хотя его применение тоже имеет ограничения. Врядли он будет работать, если частота обработки данных не в разы превышает скорость обмена по UART.
Врядли он будет работать, если частота обработки данных не в разы превышает скорость обмена по UART.

Верно. Именно потому после написания кода нужно выводить ограничение на связку параметров CLOCK и Speed. Но обычно в ПЛИСе десятки мегагерц, на практике пока не сталкивался с ограничением.
Зато подобный подход может дать интересные плюшки. Например, можно отслеживать стабильность уровня Rx в заданном диапазоне около точки чтения бита, можно детектить гвозди на линии, можно отфильтровывать помехи, превышающие длительность половины бита UART и т.д. Прелесть в том, что всё это просто выводится в телеметрию модуля, анализируя которую потом можно сделать вывод о состоянии линии связи.
Имелось ввиду что UART может быть не только «компьютерным», где максимальная частота ограничивается сотнями килогерц. Никто не мешает связать асинхронно парочку ПЛИСов (хотя конечно лучше для этого использовать стандартные протоколы обмена).
А вот в случае внешнего интерфейса я согласен, ваш алгоритм достаточно полезный. Возьму себе на заметку.
Насчет убрать мажоритарную логику — не согласен. Особенно если сопли по которым гоняется связь длинные, а передатчик слабый.
Искренне поздравляю! В нашем полку прибыло! :)))
Я тоже начинал в своё время именно с uart. Более того, любой мой проект начинается с того, что я копирую в него uart. И в дальнейшем подключаю к нему всю отладку и тестирование проекта. Исключительно удобная штука.
Для первого HDL-кода выглядит вполне недурно.

По поводу частоты семплирования:
UART является асинхронным интерфейсом — без передачи синхросигнала и без восстановления частоты из данных. Скорость передачи данных по сути «оговаривается» заранее, определить её из потока данных довольно проблематично.
Но даже если оба устройства знают на какой частоте они обмениваются данными, данные частоты всё равно не будут идентичными. Во-первых, в каждом устройстве в общем случае будет свой источник тактирования (кварц, генератор, что угодно), имеющий внутренний дрифт. Во-вторых, часто невозможно получить целочисленное деление тактовой частоты для подходящего baud rate в UART: контроллер например работает на 24МГц, а чтобы получить 115200, нужно 24МГц разделить на 208.(3).
В общем к чему это я всё — семплировать входящие данные лучше на как можно большей частоте. К примеру, распространённым механизмом является работа приёмника на частоте x16: по заднему фронту входного сигнала (начало старт-бита) отсчитываем 8 тактов, попадая таким образом на середину бита; далее через каждые 16 тактов читаем значение входного бита. С точки зрения частотной ошибки мы можем себе позволить расхождение по частоте на ±8 периодов нашей частоты семплирования (т.к. точка семплирования в таком случае выпадет за пределы бита), и соответственно на ±1 период частоты семплирования на бит.
На первом такте при начале старт-бита мы заведомо не знаем попал ли наш отчёт ровно на задний фронт или нет, точность этого — как раз один период частоты семплирования. Соответственно при более низкой частоте (как х4 в вашем случае) стартовая ошибка может быть больше, а запас по отклонению частот — меньше.

По поводу использования блокирующих и неблокирующих присваиваний:
«Идеологически правильный» код потенциально может привести к формированию защёлок (latch) в синтезе: блок always @(*) комбинаторный, следовательно для каждого из сигналов, используемых в левой части присвоений, должны быть описаны все условия ветвления. В данном примере это обходится (возможно, по счастливой случайности) наличием блокирующих присваиваний сигналов перед ветвлениями, что по сути станет неявным присваиванием во всех ветвлениях, где это не описано явно. Примерно так развернутся эти присваивания:
always @(*)
begin
	if(nreset) begin
		if(div4 == 2'd0) begin
			case(s)
				4'd0: begin
						sendstart = 1'b1;
						canfetch = 1'b0;
					end
				4'd9: begin
						sendstart = 1'b0;
						canfetch = wr;
					end
				4'd10:
					begin
						sendstart = 1'b0;
						canfetch = wr;
					end
				default:
					begin
						sendstart = 1'b0;
						canfetch = 1'b0;
					end
			endcase		
		end else begin
			sendstart = 1'b0;
			if(s < 4'd9) begin
				canfetch = 1'b0;
			end
			else begin
				canfetch = wr;
			end
		end
		
		if(canfetch && idle) begin
			sendstart = 1'b1;
		end
		else begin
			sendstart = 1'b0;
		end
	end else begin
		sendstart = 1'b0;
		canfetch = 1'b0;
	end
end	

Собственно, если бы присваивания были неблокирующими, ваш вариант скорее всего работал бы некорректно (не помню как в Verilog, но в VHDL повторное неблокирующее присваивание в блоке считается ошибкой).
Вне зависимости от того, блокирующие у нас присваивания или неблокирующие, пропуск одной из веток приведёт к тому, что синтезатор не будет знать, что ему делать с сигналом в данном условии, и примет решение создать элемент памяти. Но т.к. в блоке не указаны фронты (а то и вообще комбинаторный список чувствительности), будет установлена защёлка. Мета-пример подобной ситуации:
always @(*)
begin
	if(nreset) begin
		if(canfetch) begin
			sendstart = 1'b1;
		end
	end else begin
		sendstart = 1'b0;
	end
end	

Что должно происходить с сигналом sendstart при nreset == 1 и canfetch == 0? Выходит, что он должен сохранить своё текущее состояние. Но для этого нужен элемент памяти, т.к. ситуация невозможна, если использовать только комбинаторные элементы.
(не помню как в Verilog, но в VHDL повторное неблокирующее присваивание в блоке считается ошибкой).

В VHDL повторное присваивание внутри процесса (аналог always) прекрасно работает. Выполняться будет последнее по тексту.
Значит и здесь тоже не помню; в любом случае это не очень красивый код, который будет хуже читаться.
Вот мне совершенно не нравиться «идеологически правильный» код передатчика. Как уже писали выше это потенциальный источник ошибок. На мой взгляд лучше использовать конструкцию always (@postedge clk) и внутри описывать нужную логику.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации