Verilog. Обертки RAM и зачем это нужно

Дело в том, что моему знакомству с FPGA два года. В студенческие годы, читая code examples от Altera и Xilinx, я мало уделял внимания описанию RAM и ROM, ведь в этом не было необходимости. Я знал, что существует FIFO, но даже не догадывался, зачем оно мне нужно. Так я защитил диплом, сделав свой проект «в лоб».

Работу я нашел благодаря своим «знаниям» в области FPGA. Тут же вылезли проблемы, о которых я не подозревал: существующие проекты занимали под 90% логических элементов, а моей работой было разместить в проектах цифровые фильтры. Понятно, что «в лоб» решить эту проблему было невозможно. Переписать существующий код? Помогло, но не сильно. И только тогда я понял, как можно играть с RAM, чтобы решить поставленные задачи.

Внимание! Здесь не будут моргать светодиоды. Вместо этого будет рассмотрена реализация сдвигового регистра в виде обертки для M4K. Данная публикация предполагает минимальное знакомство с FPGA и языком Verilog.

Начнем с того, что сделав «в лоб» необходимый фильтр в отдельном проекте стало понятно, где в нем тонкое место. Им оказался сдвиговый регистр длины 444*16 бит (444 — порядок фильтра, 16 бит — размерность слова). С этим нужно что-то было делать. Мысль пришла к тому, что сдвиг — операция тривиальная. Достать текущее значение регистра на шаге N и записать туда значение регистра, взятого на шаге N-1. Перечитав code examples я нашел, как это сделать с помощью RAM:

module pip
#(parameter DATA_WIDTH=16, parameter ADDR_WIDTH=9)
	(
	input [(DATA_WIDTH-1):0] data,
	input [(ADDR_WIDTH-1):0] read_addr, write_addr,
	input we, clk,
	output reg [(DATA_WIDTH-1):0] q
	);

(*ramstyle = "M4K"*)reg [DATA_WIDTH-1:0] ram[2**ADDR_WIDTH-1:0];

always @ (posedge clk)
begin
	q <= ram[read_addr];
	if (we)
		ram[write_addr] <= data;
end

endmodule


Это взято прямо из examples, изменились только параметры. Теперь остается заставить этот RAM работать, как было описано выше. Для этого было написано то, что я назвал оберткой RAM. Простенький конечный автомат:

module upr
#(parameter DATA_WIDTH = 16, parameter ADDR_WIDTH = 9)
	(
	input wire							clk,
	input wire							en,
	input wire	[ (DATA_WIDTH-1) :  0 ]	ram_upr,
	input wire 	[ (DATA_WIDTH-1) :  0 ]	data_in,

	output wire	[ (DATA_WIDTH-1) :  0 ]	upr_ram,
	output wire							we_ram,
	output wire	[ (ADDR_WIDTH-1) :  0 ]	adr_out
	);

assign upr_ram = ram;
assign we_ram = r_we;
assign adr_out = r_adr;

reg [  2 :  0 ]	r_state = state0;
localparam		state0 = 3'b001,
				state1 = 3'b010,
				state2 = 3'b100;

reg [ (ADDR_WIDTH-1) :  0 ]	r_adr = {ADDR_WIDTH{1'b0}};

reg	[ (DATA_WIDTH-1) :  0 ]	ram = {DATA_WIDTH{1'b0}};
reg							r_we = 1'b0;

always @(posedge clk)
if(en)
begin
	case(r_state)
		state0:
			r_state <= state1;

		state1:
			r_state <= state2;

		state2:
			r_state <= state1;

	endcase
end

always @(posedge clk)
case(r_state)
	state0:
		begin
			r_we <= 1'b0;
			r_adr <= {ADDR_WIDTH{1'b0}};
			ram <= data_in;
		end

	state1:
		begin
			r_we <= 1'b1;
			if(r_adr == {ADDR_WIDTH{1'b0}})
				ram <= data_in;
			else
				ram <= ram_upr;
		end

	state2:
		begin
			r_adr <= r_adr + 1'b1;
			r_we <= 1'b0;
		end

endcase

endmodule


Работает в два этапа + одно состояние, которое может использоваться при сбросе. Конечный автомат предельно прост — никаких условий перехода, только фронт тактового сигнала. На первом шаге (State1) идет захват данных, либо предыдущего выходного значения с RAM. Получилась такая вот обратная связь. Ко второму шагу устанавливается сигнал записи в единицу и RAM захватит то, что нужно.

У этого подхода есть существенный минус — сдвиг занимает 2 такта, но и эту проблему можно легко решить. Другой существенный минус — невозможно за один такт «вытащить» хотя бы два значения из сдвигового регистра. Это значит, что сверточный кодер на таком уже не сделаеш. Плюсом такого подхода является экономия логических элементов (LE или Slice) за счет памяти.

Как решить проблему 2 тактов и, что интереснее, как сделать на основе этого полностью параметризованный цифровой КИХ фильтр я расскажу, если кому-нибудь это будет интересно.

Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 4

    0
    1. Подсветка кода ужасная.

    2. Вы делаете FIFO на базе RAM-блоков FPGA. Можно воспользоваться генератором CORE, что будет корректнее и эффективнее для реализации в конкретной платформе с точки зрения экономии логических элементов. Но переносимости кода с такими блоками не будет, ведь в разных платформах разные корки.
    Для создания кода, переносимого между разными FPGA, или придется создавать HDL-код, не использующий в разводке проекта все возможности платформы и, как следствие, неэффективно укладывающийся в кристалл. Или создавать код, в котором анализируются текущая платформа и используются соответствующие вставки из Language Templates.

    3. Требовать от FIFO возможности за один такт вытащить два значения — это не минус. Сдвиг за два такта — необычно для FIFO, но тоже все-таки не минус.

    Посмотрите, как сделан FIFO: www.asic-world.com/examples/vhdl/syn_fifo.html#Synchronous_FIFO
      0
      Спасибо за критику, но, откровенно говоря, это недописанная статья, и я не хотел, чтобы ее кто-то читал в таком виде. Поэтому возникают вопросы, которых не должно быть. И тема тут не ФИФО раскрывается, хотя конечно рядом.
      Тема — как раз контроль verilog`ом за ресурсами, которые нужно задействовать. В данном случае это RAM.
      Итогом топика должен был стать сдвиговый регистр на RAM за 1 такт на чистом verilog с минимальными вставками логики и регистров. И конечный продукт — цифровой фильтр, который в FPGA занимает не более 100 LE (slice) + RAM.
      Раз уж топик всплыл по непонятным для меня причинам, придется дописывать.
      0
      1. Да, статья не дописана. Не понятно какое в итоге описание памяти используете. То что привели из примера классическое описание памяти, которое инферится правильно в всех САПРах для FPGA.
      А если правильно написать Verilog под архитектуру блоков ПЛИС, то синтезатор сам вставит блоки память РАМ/ROM вместо массива regs на триггерах.
      2. Режет глаза «input wire, output wire». Verilog приятен по сравнению с VHDL, тем что соблюдая несколько правил аналогичных код занимает в 2 раза меньшее строк. Имеет смысл только писать «output reg», чтобы сэкономить еще 1 строчку в Verilog2001.
      3. У вас FSM вообще без какого-либо ресета. Если проверяли, то это будет работать только во встроенном симуляторе Квартуса. Для нормального симулятора (ModelSim и т.п.) должен использоваться синхронный/асинхронный ресет для всех типов reg. Иначе ничего нормально не промоделируете.
        0
        Продолжение можно почитать здесь.

      Only users with full accounts can post comments. Log in, please.