Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Block RAM на VHDL в Spartan3
Форум разработчиков электроники ELECTRONIX.ru > Программируемая логика ПЛИС (FPGA,CPLD, PLD) > Работаем с ПЛИС, области применения, выбор
Vadim_nsk
При описании памяти как массива производится чтение и запись по разным адресам, при этом чтение производится ассинхронно, а запись синхронно.
При объявлении массива указываю атрибут:
attribute syn_ramstyle : string;
attribute syn_ramstyle of data_array_1 : signal is "block_ram";--"no_rw_check";

При этом Synplify Pro 8.4 в отчете выдает следующее сообщение:
Could not implement Block RAM. Is the read address registered using the same clock as the RAM?

Код примерно следующий:

entity name is
clock : in std_logic;
din : in signed( 15 downto 0 );
write : in std_logic;
dout : out signed( 15 downto 0 );
...
end name;

architecture name_body of name is

subtype data_type is signed( 15 downto 0 );
type data_array_type is array( 0 to 7 ) of data_type;
signal data_array_1 : data_array_type := ( others => to_signed( 0, data_type'length ) );
signal data_array_2 : data_array_type := ...;
attribute syn_ramstyle : string;
attribute syn_ramstyle of data_array_1 : signal is "block_ram";--"no_rw_check";
attribute syn_ramstyle of data_array_2 : signal is "block_ram";--"no_rw_check";

signal rd1_addr, wr1_addr : unsigned( 2 downto 0 ) := to_unsigned( 0, 4 );
signal rd2_addr, wr2_addr : unsigned( 2 downto 0 ) := to_unsigned( 0, 4 );
signal A, C : data_type := to_signed( 0, data_type'length );

body

A <= data_array_1( to_integer( rd1_addr ) );
C <= data_array_2( to_integer( rd2_addr ) );

process( reset, clock )
begin
if reset = '1' then
A <= to_signed( 0, data_type'length );
C <= to_signed( 0, data_type'length );
rd1_addr <= ...;
wr1_addr <= ...;
rd2_addr <= ...;
wr2_addr <= ...;
...
elsif rising_edge( clock ) then
dout <= A * C;
if write = '1' then
data_array_1( to_integer( wr1_addr ) ) <= din;
end if;
rd1_addr <= rd1_addr + 1;
wr1_addr <= wr1_addr + 1;
rd2_addr <= rd2_addr + 1;
end if;
end process;

end name_body;

Как нужно описывать RAM в виде массива, чтобы Synplify сделал из него BlockRAM?
oval
Цитата(Vadim_nsk @ Jun 28 2006, 13:38) *
При описании памяти как массива производится чтение и запись по разным адресам, при этом чтение производится ассинхронно, а запись синхронно.
При объявлении массива указываю атрибут:
attribute syn_ramstyle : string;
attribute syn_ramstyle of data_array_1 : signal is "block_ram";--"no_rw_check";

При этом Synplify Pro 8.4 в отчете выдает следующее сообщение:
Could not implement Block RAM. Is the read address registered using the same clock as the RAM?

Код примерно следующий:

entity name is
clock : in std_logic;
din : in signed( 15 downto 0 );
write : in std_logic;
dout : out signed( 15 downto 0 );
...
end name;

architecture name_body of name is

subtype data_type is signed( 15 downto 0 );
type data_array_type is array( 0 to 7 ) of data_type;
signal data_array_1 : data_array_type := ( others => to_signed( 0, data_type'length ) );
signal data_array_2 : data_array_type := ...;
attribute syn_ramstyle : string;
attribute syn_ramstyle of data_array_1 : signal is "block_ram";--"no_rw_check";
attribute syn_ramstyle of data_array_2 : signal is "block_ram";--"no_rw_check";

signal rd1_addr, wr1_addr : unsigned( 2 downto 0 ) := to_unsigned( 0, 4 );
signal rd2_addr, wr2_addr : unsigned( 2 downto 0 ) := to_unsigned( 0, 4 );
signal A, C : data_type := to_signed( 0, data_type'length );

body

A <= data_array_1( to_integer( rd1_addr ) );
C <= data_array_2( to_integer( rd2_addr ) );

process( reset, clock )
begin
if reset = '1' then
A <= to_signed( 0, data_type'length );
C <= to_signed( 0, data_type'length );
rd1_addr <= ...;
wr1_addr <= ...;
rd2_addr <= ...;
wr2_addr <= ...;
...
elsif rising_edge( clock ) then
dout <= A * C;
if write = '1' then
data_array_1( to_integer( wr1_addr ) ) <= din;
end if;
rd1_addr <= rd1_addr + 1;
wr1_addr <= wr1_addr + 1;
rd2_addr <= rd2_addr + 1;
end if;
end process;

end name_body;

Как нужно описывать RAM в виде массива, чтобы Synplify сделал из него BlockRAM?


Канал чтения должен быть также синхронным. Не поленитесь, посмотрите в документацию на Synplify, там все достаточно подробно расписано smile.gif
avesat
Проще использовать готовые RAM блоки, рекомендованые под архитектуру спартана. И они сразу будут размещаться в BlockRAM.
des00
откуда у блочной памяти асинхронные сбросы ?
читаем lib.pdf от хилых про BRAM изучаем
Vadim_nsk
Цитата(oval @ Jun 28 2006, 16:43) *
Канал чтения должен быть также синхронным. Не поленитесь, посмотрите в документацию на Synplify, там все достаточно подробно расписано smile.gif

Да в том то и дело, что не поленился... Может я конечно чего-то недопонимаю...
Пример из файла reference.pdf, стр. 10-84 (документация Synplify):
"Two-write Port RAM Example"

WRITE_RAM : process (clk)
begin
if rising_edge(clk) then
if (wren_a = '1') then
mem(to_integer(unsigned(addr_a))) <= data_a;
end if;
if (wren_b='1') then
mem(to_integer(unsigned(addr_b))) <= data_b;
end if;
addr_a_reg <= addr_a;
addr_b_reg <= addr_b;
end if;
end process WRITE_RAM;
q_a <= mem(to_integer(unsigned(addr_a_reg)));
q_b <= mem(to_integer(unsigned(addr_b_reg)));

Как видно, чтение ассинхронное.

Чтение внутрь процесса вносил (под клок), результат тот же...

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

Готовые RAM блоки, рекомендованые под архитектуру спартана использовать можно, но их тяжеловато подстраивать под конкретный размер массива данных. Я пытался сделать компонент в общем виде под задаваемую разрядность и объем данных.
des00
какая разница ? вы позволили синтезатору доопределить поведение памяти при сигналах сброса, т.к. память описана в таком процессе. Вот он и доопределил.
Опишите память в отдельных процессах. и будет вам благо. Ест-но латентность памяти на доступ нужно учесть ручками.
vetal
Возможно, что дело еще в:
Код
rd1_addr <= rd1_addr + 1;
wr1_addr <= wr1_addr + 1;
rd2_addr <= rd2_addr + 1;



Добавьте дополнительные регистры в код :rd1_addr_br,wr1_addr_br,rd2_addr_br и делайте примерно так:
Код
...
--synthesis translate_off
rd1_addr <= ...;
wr1_addr <= ...;
rd2_addr <= ...;
wr2_addr <= ...;
--synthesis translate_on
...
data_array_1( to_integer( wr1_addr_br ) ) <= din;
...
wr1_addr_br<=wr1_addr+1;
wr1_addr<=wr1_addr+1;
...
oval
Цитата(Vadim_nsk @ Jun 28 2006, 15:47) *
Цитата(oval @ Jun 28 2006, 16:43) *
Канал чтения должен быть также синхронным. Не поленитесь, посмотрите в документацию на Synplify, там все достаточно подробно расписано smile.gif

Да в том то и дело, что не поленился... Может я конечно чего-то недопонимаю...
Пример из файла reference.pdf, стр. 10-84 (документация Synplify):
"Two-write Port RAM Example"

WRITE_RAM : process (clk)
begin
if rising_edge(clk) then
if (wren_a = '1') then
mem(to_integer(unsigned(addr_a))) <= data_a;
end if;
if (wren_b='1') then
mem(to_integer(unsigned(addr_b))) <= data_b;
end if;
addr_a_reg <= addr_a;
addr_b_reg <= addr_b;
end if;
end process WRITE_RAM;
q_a <= mem(to_integer(unsigned(addr_a_reg)));
q_b <= mem(to_integer(unsigned(addr_b_reg)));

Как видно, чтение ассинхронное.

Чтение внутрь процесса вносил (под клок), результат тот же...


Обратите внимание, что адрес чтения защелкивается. Посмотрите в документе Synplicity FPGA Synthesis раздел Design Optimization -> Inferring RAMs.

Цитата
Что же касается сброса, то он относится не к памяти, а к всему вычислительному блоку (надо же мне както инициализировать сигналы).


Разнесите по разным процессам память и остальную логику.
Gorby
Извините, что врываюсь...

Почему-то никто ни слова не произнес про COREGEN. Это ж самый верный способ получить требуемую память. Прекрасно конфигурируется и заведомо работает. А в Симплифае вставляется как блэк-бокс. Правда, непереносимо. Так и BRAM сама по себе непереносима.
Mad Makc
Цитата
Почему-то никто ни слова не произнес про COREGEN.

Потому что не удобно.Сегодня я хочу память в 2К слов.Завтра дадут ценное указание и память станет 4К слов.А послезавтра разрядность увеличить придётся.
По мне править код намного проще и удобней, чем занового генерить ядра.
Wild
Если адрес чтения успевает устанавливаться к серидине такта, а данные на выходе обязательно надо получить к началу следующего такта, можно писать по фронту, а читать по спаду.
maior
Hо ведь есть еще стандартные примитивы памяти (блочной и распределенной),
на базе которых можно сделать универсальные параметризиремые (через generic)
бибиотечные элементы. Я так и делал для виртексов 2 и 4 (один элемент годился для обоих
и позволял строить память почти любой конфигурации!).
Для спартанов - тоже не должно быть проблем.
И не заморачивался ни с coregen, ни с behaviorial, которое по-разному и неустойчиво
интерпретируется разными синтезаторами в разных условиях и для
разных (даже слегка разных!) чипов.
Gorby
Цитата(maior @ Jun 29 2006, 18:26) *
Hо ведь есть еще стандартные примитивы памяти (блочной и распределенной),
на базе которых можно сделать универсальные параметризиремые (через generic)
бибиотечные элементы. Я так и делал для виртексов 2 и 4 (один элемент годился для обоих
и позволял строить память почти любой конфигурации!).
Для спартанов - тоже не должно быть проблем.
И не заморачивался ни с coregen, ни с behaviorial, которое по-разному и неустойчиво
интерпретируется разными синтезаторами в разных условиях и для
разных (даже слегка разных!) чипов.


Фактически, вы руками доделали некоторую часть работы. Которую запросто мог за вас сделать Кореген. Более того, вам пришлось разбираться с тонкостями использования тех примитивов - причем обременительными тонкостями, типа зануления старших адресов если используется не вся память и построение дешифраторов, если требуется бОльшая. И зачем?! Когда есть кореген, одним движением пальца делающий все это.

Насчет переносимости, разных синтезаторов и проч. Сдается мне, что проблему сильно преувеличивают.

Не знаю, кто как, но я только один раз кардинально менял окружение. С Леонардо Спектрум для Atmel FPSLIC прыгнул на Spartan2 и XST (ISE). Разумеется, блоки памяти потребовали кардинальной переделки. Которая заключалась всего лишь в генерации оных в Корегене.
Затем перешел на Спартан 3, так ничего и не поменялось. Сгенерировал то же самое в Корегене.

А теперь пожалуйста ответьте, как часто вам, уважаемые коллеги, пришлось переходить (в ходе разработки ОДНОГО изделия) на кардинально другие FPGA и кардинально другие синтезаторы?

Ото ж... А на рихтовку своих исходников с целью сделать их универсальными вы потратите очень много времени с непредсказуемым результатом. (речь идет об аппаратно-специфических вещах, память, DDR регистры и проч.).

Моя идеология проста: делаем на plain VHDL все, что можно. По возможности без атрибутов и прочих бубенцов. Все специфическое включаем в блэк-боксы, их генерируем в целевой среде. Констрейны и прочие весьма зависимые прибамбасы (распиновка, типы выводов и тд) делаем исключительно в целевой среде. И не паримся. При переходе на что-то другое у нас остается везде компилируемый чистый VHDL, блэк-боксы создаем по-новой - тут уж никуда не деться. А констрейны, пинауты и иже с ними в новой среде все равно придется вбивать по-новой. Каламбурчик.
maior
Цитата(Gorby @ Jun 30 2006, 13:32) *
... Фактически, вы руками доделали некоторую часть работы. Которую запросто мог за вас сделать Кореген....

... Более того, вам пришлось разбираться с тонкостями использования тех примитивов - причем обременительными тонкостями,....

.. А на рихтовку своих исходников с целью сделать их универсальными вы потратите очень много времени с непредсказуемым результатом. (речь идет об аппаратно-специфических вещах, память, DDR регистры и проч.)...


ОДИН раз сделал - и больше уже не пришлось делать что-то еще.. Вот уже пару лет как
пользую свою библиотеку памяти для разных проектов. Идея была
создать для зайлинкса такой же набор LPM памяти какая есть у
альтеры. У меня они даже совместимы по entity и generic, так-что можно
прыгать с зайлинкса на альтеру без труда. Посмотрел бы я на вас с
вашим corgen в такой ситуации.
Coregen (для памяти) - в отстое уже давно. Он явно неудобен, о чем тут
уже говорилось, не стоит об этом спорить.
А с plane (behaviorial) HDL - все равно будут проблемы, и в конце
концов проковыряешся больше, что собственно и доказывают посты
в этой ветке.

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

А времени потратил до смешного немного. Даже удивительно, отчего это
зайлинкс сам не сделал эту работу, например как альтера.

Но опять же - на вкус и цвет...
des00
МОЕ ИМХО на весь этот счет, у памяти 2 конфигурации, 1 чистый бехавор для симуляции и первоначальной сборки (1 раз потратил день на изучение как правильно нужно писать память, после этого ни разу не было проблем при синтезе, для симуляции попробуйте на вставленых БРАМАХ при объеме памяти кил в 100-200 симуляться и натравите профайлер).
2 конфигурация это уже конфигурация сборки, с реальными вставленными брамами, к которым к тому же можно и RLOC/LOC пристегнуть.
Выбор конфигурации в ВХДЛ 1 строка. в том же топ файле.
qwqw
У меня Spartan2 а не 3, но думаю это непринципиально.
Мне нужно написать двухпортовую RAM с разными клоками на концах
с одной стороны только запись с we а с другой только чтение.
на сайте Xilinx есть примеры описания
НО мне нужна память с разной разрядностью шин на разных портах, там такого нет.
Есть вариант использовать готовую память типа RAMB4_Sm_Sn или, как говорилось выше, использовать COREGEN, но хочется иметь возможность изменять под себя (выбирать фронты и пр.) ну и по-возможности не использовать черных ящиков. Очевидно, что написать самому возможно, но пока не получается.
Вероятно кто-то уже возился с этим.
Пишу на VHDL, но Verilog тоже прочитаю.
BSV
Пытался делать такую штуку в ISE 4.2. Были проблемы с синтезом (надо было писать и читать по обоим портам). Судя по тому, что приводится в Language templates, ситуация мало изменилась, хотя попробовать и не мешает:

Код
process (<clockA>)
begin
   if (<clockA>'event and <clockA> = '1') then
      if (<enableA> = '1') then
         if (<write_enableA> = '1') then
            <ram_name>(conv_integer(<addressA>)) <= <input_dataA>;
         end if;
         <ram_outputA> <= <ram_name>(conv_integer(<addressA>));
      end if;
   end if;
end process;

process (<clockB>)
begin
   if (<clockB>'event and <clockB> = '1') then
      if (<enableB> = '1') then
         <ram_outputB> <= <ram_name>(conv_integer(<addressB>));
      end if;
   end if;
end process;


Единственный выход в данном случае - использовать библиотечные элементы.

По поводу разной разрядности на портах - попробуйте все это описать на поведенческом уровне следующим образом - массив (который память) используйте с минимальной используемой разрядностью, а при описании чтения и записи по другому порту используйте несколько индексов массива. Например так:
Код
  
<ram_outputB> <= <ram_name>(conv_integer(<addressB>*2 + 1)) & <ram_name>(conv_integer(<addressB>*2));

Это в случае, если разрядность различается в 2 раза. Посмотрите, что Вам синтезатор на это скажет. Если схавает - ваша взяла, если нет - ничего не попишешь.

Кстати, тут возможна и другая проблема - в библиотечных элементах может не найтись памяти с нужной комбинацией разрядности портов и/или размером (например мне в Спартане 2е понадобилась память объемом 4КБайт и разрядностью 8 и 32). Тут может оказаться удобным объединение выходов нескольких BRAM при помощи OR или XOR и использовании входов сброса выходных регистров BRAM. Понятно как - на те BRAM, что не используем подаем сигнал сброса, после OR или XOR получаем то, что нужно. Правда, это может оказаться слишком медленно.
qwqw
разрядность портов:
A (запись) = 8
B(чтение) = 32
глубина от 1024 байт и выше

ваш вариант (и смежные с ним) я уже пробовал
примерно так:
Код
-------------- A side -------------------------
my_RAM_A_side:process(clkA)
begin
if clkA'event and clkA = '1' then
---
if WEA='1' then ram_block(conv_integer(AddrA))<=DIA;end if;
---
end if; -- clk
end process my_RAM_A_side;
-----------------------------------------------
-------------- B side -------------------------
my_RAM_B_side:process(clkB)
begin
if clkB'event and clkB = '1' then
---
--read_addr<=4*AddrB;--conv_integer(AddrB);
read_addr0<=4*AddrB;
read_addr1<=4*AddrB+1;
read_addr2<=4*AddrB+2;
read_addr3<=4*AddrB+3;
---
end if; -- clk
end process my_RAM_B_side;
----
----
--DOB <= ram_block(read_addr)&ram_block(read_addr+1)&ram_block(read_addr+2)&ram_block(read_addr+3);
DOB( 7 downto  0)<= ram_block(read_addr0);
DOB(15 downto  8)<= ram_block(read_addr1);
DOB(23 downto 16)<= ram_block(read_addr2);
DOB(31 downto 24)<= ram_block(read_addr3);
-----------------------------------------------

но увы не получается
я могу использовать 2 закаскадированых RAMB4_S4_S16, но хочется все же самому закодить
BSV
А синтезатор-то что говорит? В Вашей реализации я вижу ошибку - чтение должно быть обязательно! по фронту клока, а вот адреса чтения как раз и не нужно делать регистровыми.
BSV
Попробовал - таки да, не хавает, говорит, что вместо памяти триггеров навставляет. Ну и шут с ним.
Для вас есть вариант - объединить 4 синтезированных блока xxx_S8_S8 способом, который я описал выше, хотя, вам же по 8-разрядному порту не надо читать - так что это и не нужно. С двумя блоками фокус не пройдет - выравнивание поедет (PORTB(x) /= PORTA(x*4+3) & PORTA(x*4+2) & PORTA(x*4+1) & PORTA(x*4)).
qwqw
насчет ошибки я не согласен, если посмотрите в примерах там сделано аналогично.
Да и раньше я делал такую память(только с одинаковой разрядностью) и все благополучно паковалось в BRAM.
Цитата
Для вас есть вариант - объединить 4 синтезированных блока xxx_S8_S8 способом, который я описал выше, хотя, вам же по 8-разрядному порту не надо читать - так что это и не нужно. С двумя блоками фокус не пройдет - выравнивание поедет (PORTB(x) /= PORTA(x*4+3) & PORTA(x*4+2) & PORTA(x*4+1) & PORTA(x*4)).

я уже сделал, как говорил выше: каскадировал 2 блока RAMB4_S4_S16 и как раз получается 8/32
и глубина 1024. Соответственно 1-й RAMB4_S4_S16 - на вход младшая тетрада на 2-й - старшая
и выход разобран соответствующим образом.
Все работает и в BRAM упаковалось. Однако камень предкновения во фронтах.
Если не получится с положительными сделать, придется тратить GBUF с CLKDLL (брать противофазный клок), а с ними тоже напряг.
BSV
Зачем Вам противофазный клок? Если нужен отрицательный фронт - просто заводите на тактовый вход not CLK и все - а синтезатор, маппер или что там еще пусть разбирается (инверторы на тактовом входе имеются в любом блоке, в том числе и в BLOCKRAM).
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.