Дело в том, что моему знакомству с FPGA два года. В студенческие годы, читая code examples от Altera и Xilinx, я мало уделял внимания описанию RAM и ROM, ведь в этом не было необходимости. Я знал, что существует FIFO, но даже не догадывался, зачем оно мне нужно. Так я защитил диплом, сделав свой проект «в лоб».
Работу я нашел благодаря своим «знаниям» в области FPGA. Тут же вылезли проблемы, о которых я не подозревал: существующие проекты занимали под 90% логических элементов, а моей работой было разместить в проектах цифровые фильтры. Понятно, что «в лоб» решить эту проблему было невозможно. Переписать существующий код? Помогло, но не сильно. И только тогда я понял, как можно играть с RAM, чтобы решить поставленные задачи.
Внимание! Здесь не будут моргать светодиоды. Вместо этого будет рассмотрена реализация сдвигового регистра в виде обертки для M4K. Данная публикация предполагает минимальное знакомство с FPGA и языком Verilog.
Начнем с того, что сделав «в лоб» необходимый фильтр в отдельном проекте стало понятно, где в нем тонкое место. Им оказался сдвиговый регистр длины 444*16 бит (444 — порядок фильтра, 16 бит — размерность слова). С этим нужно что-то было делать. Мысль пришла к тому, что сдвиг — операция тривиальная. Достать текущее значение регистра на шаге N и записать туда значение регистра, взятого на шаге N-1. Перечитав code examples я нашел, как это сделать с помощью RAM:
Это взято прямо из examples, изменились только параметры. Теперь остается заставить этот RAM работать, как было описано выше. Для этого было написано то, что я назвал оберткой RAM. Простенький конечный автомат:
Работает в два этапа + одно состояние, которое может использоваться при сбросе. Конечный автомат предельно прост — никаких условий перехода, только фронт тактового сигнала. На первом шаге (State1) идет захват данных, либо предыдущего выходного значения с RAM. Получилась такая вот обратная связь. Ко второму шагу устанавливается сигнал записи в единицу и RAM захватит то, что нужно.
У этого подхода есть существенный минус — сдвиг занимает 2 такта, но и эту проблему можно легко решить. Другой существенный минус — невозможно за один такт «вытащить» хотя бы два значения из сдвигового регистра. Это значит, что сверточный кодер на таком уже не сделаеш. Плюсом такого подхода является экономия логических элементов (LE или Slice) за счет памяти.
Как решить проблему 2 тактов и, что интереснее, как сделать на основе этого полностью параметризованный цифровой КИХ фильтр я расскажу, если кому-нибудь это будет интересно.
Работу я нашел благодаря своим «знаниям» в области 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 тактов и, что интереснее, как сделать на основе этого полностью параметризованный цифровой КИХ фильтр я расскажу, если кому-нибудь это будет интересно.