Search
Write a publication
Pull to refresh
12
0
Николай Фомин @NetNazgul

HDL-инженер

Send message
Ну там никто не мешает и как просто ПЛИС без процессора использовать на самом деле.

Но вообще — позволяет выполнить и интегрировать realtime-периферию гораздо ближе к ядру процессора, чем при отдельных кристаллах, не ограничиваясь при этом softcore-процессором внутри самой ПЛИС.
Мы тут как-то по работе недавно покупали Zynq-модули у Trenz, на выходе получается ARM-процессор + ПЛИС довольно большой ёмкости (это в одном кристалле), + SPI Flash + пара модулей на плате, выводы и отдельный Xilinx-совместимый программатор на CPLD от Lattice, и всё это чуть меньше $100.
Variable, используемые как де-факто регистры, — это один из верных способов выстрелить себе в ногу в VHDL. Их поведение сложнее контролировать в асинхронных (описывающих логику, а не триггер) процессах, в целом есть довольно высокий шанс получить на выходе т.н. защёлки (latch), которых не должно быть в проекте, если только не ставишь их туда вручную с конкретной целью.

Опять же, разные инструменты синтеза дружат с переменными очень по-разному и могут создавать из них регистры даже там, где это не требуется, после чего схема работает неправильно.
Добро пожаловать в мир HDL! Несколько придирок, на которые хотелось бы обратить внимание. :)

1. Строго говоря, код на HDL не является «программой». На это же напрямую намекает само название hardware description language — «язык описания аппаратуры». HDL скорее ближе к схемотехнике, чем к программированию. Хотя конечно ПЛИС дают некоторое впечатление программирования, т.к. результат можно увидеть буквально через пару минут, а не через три месяца как в случае со СБИС.
2. Первый процесс даст вам деление на 6, а не на 5 (отсчёты с 0 до 5, 0-1-2-3-4-5).
3. Никогда раньше не сталкивался с
shared variable
, в любом случае даже просто переменные использовать в VHDL лучше по минимуму и для промежуточных значений в процессах; основу, а тем более 100% выходов синхронных элементов, должны составлять сигналы
signal
. Если возникают сложности с использованием значений сигналов внутри процессов — следует подробнее ознакомиться к параллельными и последовательными блоками в VHDL (в Verilog логика похожа) и присвоением значений сигналам и переменным.
4. С т.з. пользователя управление с требованием зажатия нескольких кнопок выглядит не очень приятным. Я бы предложил, например, отвести одну кнопку под выбор режима (редактирование одного или другого параметра), две под инкремент/декремент и одну под включение/выключение как есть.
5. Если уж мы выполняем проверку «длина импульса должна быть меньше периода» при изменении импульса, то стоило бы проверять и обратный случай при изменении периода.
6. В целом получается, что duty/period и dig/di — это живущие своей независимой жизнью одинаковые величины, отличающиеся лишь в представлении. В общем случае более логично было бы выполнять двоично-десятичное преобразование от исходных значений. Но в данном примере подозреваю, что стоял вопрос жёсткого ограничения по площади ПЛИС.
7.
if cnt > period then

elsif cnt = duty then

Здесь опять «ошибка +1» — счётчик работает на диапазоне от 0 до периода, что приводит к заданию числом period реального значения в period+1 тактов.
8.
 variable cntX : integer range 0 to 1000 := 0;
 variable cnt : integer range 0 to 1000 := 0;

Тут опять максимальное значение 1000, что даст нам 1001 отсчётов, а еще — возможно данное описание корректно работает с обработкой переполнения счётчика (переход из 1000 в 0), но описано несколько страшновато. В самом первом процессе счётчик более корректен — там данная проверка описания переполнения выполняется явно:
	t := t + 1;
  	if t = 5 then
	  t := 0;
	  tact <= not tact;
	end if;

… и тут я понял, что пункт №2 как раз правильный, а у меня нет, всё дело в переменных, где сначала выполняется t := t + 1, получаем 5, а потом сразу меняем на 0. Но такое использование переменных может привести к избыточной логике. При использовании сигналов, новое значение устанавливается в сигнале только по выходу из процесса, т.о. если процесс сихронный, то результат t <= t + 1 мы «увидим» в этом процессе только на следующем такте (так же это работает и в реальности для триггеров). Совсем хорошо было бы так:

signal t: integer;
process(clk)
begin
  if rising_edge(clk) then
    if t = 4 then
      t <= 0;
    else
      t <= t + 1;
    end if;
  end if;
end process;

Возвращаясь к счётчикам — для cntX переход через 0 не проверяется, а для cnt, как уже выше было сказано, посчитается как +1.
Название проекта конечно более-менее запоминающееся, но при этом уж очень плохо индексируемое, распространить это не удастся уже только по данной причине.
Немного добавить — в Европе почему-то ярлык «картофельной нации» закрепился за латышами, и если искать шутки про латышей или про картофель — обязательно найдётся пересечение, а вот про беларусов и картофель мало кто знает.
La tortilla de patatas это не только «не совсем драник», но и «совсем не драник», потому что это жареная картошка с яйцом и луком. Драники, как и бабка, делаются из тёртого картофеля, в то время как в тортилье картофель медальонами, возможно слегка «помятый».
Все думают, что нужно заставить всех перейти на единый банк и валюту, но почему-то каждый видит в качестве единой свою собственную валюту и собственный банк ;)
Стоит заметить, что для складского учёта NFC-метки так себе — в таких случаях используют UHF-метки, работающие на частотах в районе 900МГц (±, несущая зависит от региона использования). UHF RFID работает на расстояниях до 10 метров, протокол специально рассчитан для быстрой инвентаризации и позволяет достаточно быстро «прозвонить» целый грузовик товара с расстояния.

NFC конечно более удобен для домашнего использования, т.к. достаточно только смартфона под рукой.
Лично у меня этот «нормальный лаунчер» вёл себя примерно так же, как описано в статье. Сама игра весит 50-70 ГБ, при этом для нормального обновления еще требуется свободных столько же (но это наугад), ничего про это лаунчер не говорит и может потратить несколько часов времени на обновление, после чего сказать «у меня тут закончилось место, обновление прервано, жмите Repair». На починке игра по сути перескачивалась заново. По итогу почти все обновления у меня проходили с третьего-четвёртого раза.
Значит и здесь тоже не помню; в любом случае это не очень красивый код, который будет хуже читаться.
Честно говоря, почти ничего не понял из написанного. Блокирующее присваивание выполняется мгновенно, неблокирующее выполняется после дельта-задержки. Если все always-блоки выполняются в пределах одной дельта-задержки, то действительно возможно появление результата блокирующего присваивания ранее, чем это требовалось. Поэтому выше уже упоминалось, что с этой точки зрения разделение в VHDL на сигналы и переменные удобнее и адекватнее, чем "=" и "<=" в Verilog. Но наличие возможности выстрелить себе в ногу не является достаточным для «это нельзя делать, это всё неправильно, никогда так не пишите».
Для первого 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? Выходит, что он должен сохранить своё текущее состояние. Но для этого нужен элемент памяти, т.к. ситуация невозможна, если использовать только комбинаторные элементы.
Во втором процессе нет состояния гонки и отсутствует неопределённое поведение, т.к. if проверяется лишь однажды. На выходе синтеза получится T-триггер.
always @(posedge clk) begin
    a = b;
    b = c;
end

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

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

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

Но никаких неопределённостей в данных конструкциях нет, просто нужно понимать как именно они «отображаются» в реальную схему.
Вообще использование блокирующих присваиваний внутри always вполне себе допустимо, и даже допустимо чтение сигнала в том же блоке «ниже по коду», в котором он присваивается. Просто это выльется в более длинную цепочку логики, подобные операции будут выполнены в пределах одного такта. Пока это писал, увидел, что ниже вам то же самое уже написали ранее.
Ну разве нет в русском языке достаточного количества слов, чтобы перевести «merchant»? При том что
Информация сверяется с кодом категории продавца (Merchant category code, MCC).
Вы любите «праворукую» эргономику

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

Ну и опять же если у вас присвоения задержек есть на синхросигналах и на сигналах сброса, то добавление задержек не будет соответствовать результатам синтеза, т.к. величины задержек будут разными. Синтез строит clock tree и reset tree, добавляя буферы так, чтобы итоговая задержка по цепям сброса и синхронизации соответствовала распространению данных. В системах с большим количеством (да в общем-то просто больше одного) асинхронных друг другу частотных доменов это может быть сложно отмоделировать правильно, а внесённая руками задержка может изменить работу схемы.
Вы проигнорировали всё, что old_bear вам написал. Описанная проблема заключается в том, что из-за особенностей моделирования у присвоения «clk2 <= clk1» нет прямого эквивалента в синтезе, т.к. в моделировании такая конструкция создаёт задержку (пусть и дельта), а в синтезе — нет.

То, что вы в итоге добавляете задержки в присвоения, по сути имеет ту же заложенную проблему — подобные присвоения не будут эквивалентными результатам синтеза.

Information

Rating
Does not participate
Location
Минск, Минская обл., Беларусь
Date of birth
Registered
Activity