Как я могу написать большой модуль VHDL и сохранить его читабельным?

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

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

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

Я также попытался объявить отдельные процедуры для логики каждого состояния в голове одного основного процесса, и процесс просто вызывал правильную процедуру на основе текущего состояния. Это работало довольно хорошо, с модульными «функциями» и более читаемой структурой... но промежуточные сигналы каждой процедуры не видны в симуляторе (и, возможно, недоступны для тестового стенда? Я сдался до того, как пробую это.). Я использовал ISim, если это актуально.

Я делал что-то не так? Есть ли какой-то трюк, который я могу использовать, чтобы избежать одного массивного монолитного процесса?

РЕДАКТИРОВАТЬ: код модуля находится здесь.


person FusterCluck    schedule 29.08.2016    source источник
comment
Демонстрация риска описания проблем в абстрактных сигналах не является декларативными элементами подпрограммы (IEEE Std 1076-2008, 4.3 тела подпрограмм), вы бы ссылались на переменные (... промежуточные сигналы не видны... ). Можете ли вы указать конкретную проблему программирования? Как лучше всего представить аппаратное обеспечение в VHDL, скорее всего, невозможно ответить без подробностей или описания.   -  person    schedule 29.08.2016
comment
Ваш связанный код control.vhd не работает по причинам, объяснения которых не помещаются в пространство, разрешенное даже несколькими комментариями. Я предполагаю, что если бы вы предоставили функциональный код, класс проблем, которые могут возникнуть у вас с читабельностью, мог бы сильно отличаться. Например, вы должны использовать оператор case в одном процессе вместо отдельных процессов для состояний. Каждый процесс, назначающий сигнал, имеет драйвер для этого сигнала. Для разрешенных сигналов разрешенное значение является эффективным значением. is_uop всегда будет ложным, как и is_call и is_direct.   -  person    schedule 31.08.2016


Ответы (3)


Возможно, вам просто нужно использовать редактор, лучше подходящий для чтения больших файлов VHDL. Я регулярно работаю с 3000+ строковыми файлами VHDL, где большая часть пространства занимает логику одного процесса, и у меня нет проблем с их чтением благодаря редактору, поддерживающему свертывание кода.

Я использую Notepad++, но я уверен, что есть и другие редакторы, поддерживающие свертывание синтаксиса VHDL. Когда я открываю файл, я нажимаю alt+0, чтобы свернуть каждую возможную точку синтаксиса, а затем расширить по мере необходимости до той части, над которой я работаю. Вы также можете использовать скрытие строк, чтобы свернуть произвольные части вашего файла, хотя с этим немного сложнее работать.

Если у вас есть большие группы связанных параллельных операторов, вы можете легко сгруппировать их в точку свертывания с помощью name : if true generate, что также позволяет вам объявлять промежуточные сигналы за пределами вашей основной архитектуры (операторы block работают, но не поддерживаются всеми инструментами). ). Чтобы принудительно свернуть точку внутри процесса, я использую if true then.

person QuantumRipple    schedule 29.08.2016
comment
IEEE Std 1076-2008 14.5.3 Генерировать операторы Уточнение генераторного оператора состоит из замены генераторного оператора нулем или более копиями блочного оператора, декларативная часть которого состоит из декларативных элементов, содержащихся в генераторном операторе, а часть оператора состоит из параллельные операторы, содержащиеся в операторе генерации. также 11.8 Создание отчетов. Заголовки блоков (порты, дженерики, карты) не обязаны поддерживаться стандартом IEEE Std 1076.6-2004 8.9.1 (отозван). Операторы Generate не допускают заголовков блоков. XST совпадает. - person ; 30.08.2016
comment
Однажды я попытался использовать блочные операторы. Я не помню, был ли это синтезатор или симулятор, но мне пришлось использовать генерацию, чтобы все заработало, независимо от того, что может сказать официальная спецификация. - person QuantumRipple; 30.08.2016

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

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

AddSub : entity work.AdderSubtractor
port map (
  clk => clk,
  enable => decoded_instruction.addsub_enable,
  a => a,
  b => b,
  mode => decoded_instruction.addsub_mode, -- This might be an enumerated type
  output => addsub_output
);

Были бы другие сигналы _output, и в конце у вас было бы что-то вроде

OutputMux : process (all)
begin
  case decoded_instruction.output_mux_select is
    when ADD_SUB => output <= addsub_output;
    when MULT => output <= mult_output;
    when LOGIC => output <= logic_output;
  end case;
end process;

Одним из преимуществ этого способа является то, что вы можете найти эффективным несколько функций, реализованных в блоке DSP в FPGA; вы можете легко создать функциональный блок для сложения, вычитания, умножения, написанный для целевого блока DSP на вашем устройстве. Выход этого будет просто еще одним входом для вашего «выходного» мультиплексора. По моему опыту, вы должны быть в состоянии эффективно реализовать многие из ваших функций обработки, используя один блок DSP (или один объект, который описывает несколько каскадных блоков DSP, в зависимости от ширины пути данных).

Лично я предпочитаю такой подход, заключающийся в том, чтобы сделать дизайн очень модульным. В недавнем многоядерном проекте DSP у меня есть только пара файлов, содержащих около 500 строк, а в большинстве из них 200 или меньше. Это означает, что когда я возвращаюсь к части дизайна, она обычно умещается на одной странице, и ее можно легко подобрать и понять за очень короткий промежуток времени. Я также обнаружил, что при реализации тяжелой конвейерной обработки для повышения производительности проекта слишком много операций в одном процессе или объекте может на порядок усложнить эту работу.

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

person scary_jeff    schedule 30.08.2016
comment
Я думал о повышении на один уровень, но ваш ответ по-прежнему полезен и актуален (и сейчас, и позже). Я предполагаю, что мои этапы выборки/декодирования/выполнения также являются параллельными функциональными единицами — моя проблема с несколькими сигнальными драйверами была решена путем помещения этапов в один процесс, и я предполагаю, что за кулисами это имело неявный мультиплексор. Я должен иметь возможность вернуться к отдельным процессам, если вручную микширую управляющие сигналы с правильной стадии. - person FusterCluck; 31.08.2016

Как говорится, на ваш вопрос трудно ответить. О каком количестве линий идет речь?

Тем не менее, вы можете найти хорошие практики кода VHDL: - следует избегать псевдонимов (не все инструменты даже поддерживают тогда, AFAIK) - дайте сигналам/переменным четкое имя - постарайтесь сгруппировать функциональность - постарайтесь не изменять сигнал/переменную в двух местах, разделенных на 500 строк обычно есть способ - если это действительно необходимо, вы можете рассмотреть возможность использования общих переменных, представленных в VHDL93. (это, однако, не решит вашу проблему с несколькими драйверами) - не забывайте о наличии записей для группировки сигналов

О том, чтобы сделать ваши «промежуточные сигналы видимыми», вы могли бы написать

junk_proc: process(clk, rst) is
variable a,b,c: of_some_types;
begin
if rst then
//do reset stuff
elsif rising_edge(clk)
b:=func1(a);
c:=func2(b);
end if;
end process;

переменные a, b и c (в данном случае простые провода), очевидно, могут быть визуализированы в любом инструменте моделирования.

Однако если вы пишете b=func1(func2(func3(func4(a))))), не забывайте, что вы описываете, что все это происходит за один такт. Учитывая ваше описание, держу пари, вы столкнетесь с проблемами, но, возможно, это хороший способ обучения.

person chrisvp    schedule 29.08.2016