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

Отладочная плата использовалась с той же FPGA семейства Spartan6 xc6slx16. На борту также имеется 32 МБ SDRAM (MT48LC16M16A2).

Вот изображение отладочной платы:

Входные данные.

И так, из техническая спецификация на микросхеме MT48LC16M16A2 имеет структуру 4 банка по 4 млн ячеек разрядностью 16 бит (4М ячеек х 16 х 4 банка). В принципе тут все понятно.

Чтобы понять адресацию этой памяти, смотрим в даташит и видим такую ​​таблицу, нас интересует последний столбец:

daec36399ed1a9e5267b829249fbb3b7

Это показывает, что у нас есть 13 бит для адресации строки, 9 бит для адресации столбца и 2 бита для адресации банка; всего 24 бита для адресации всего объема памяти. Используя простую математику, вы можете получить тот же объем памяти, что и в таблице данных: 16 * 2 ^ 24 = 268435456 бит (33554432 байта).

Если вы посмотрите на цоколевку этой микросхемы, то увидите, что там всего 13 битов вывода для адресации + 2 бита адресации банка. На рисунке видно, что сигналы A0-A12 – это адресная шина, а BA0-BA1 – шина выбора банка, и все… Где остальные 9 бит адреса?

9c51a697b9407fda274e55283cf1e238

Не лучше ли изучить вопрос механизма работы SDRAM (или лучше заучивать материал парами, как в моем случае[я, разумеется, все забыл]), получается, что адресная шина используется дважды. Первый раз используется для выбора строки, в нашем случае используются все 13 бит адреса; и второй раз используется для выбора столбца (всего 9 битов адреса), в основном ячейки внутри этой строки. Опять же: сначала выбираем строку (N тактов), затем выбираем столбец (N + x тактов) и их пересечение даст нам ячейку памяти, конечно, не забываем выбранный банк.

75f6f857619357d853b44fd8eac15a92

Также обращает на себя внимание шина данных DQ0-DQ15. Как видите, он один и используется — и для записи, и для чтения. Мне, как человеку, часто использующему примитивы BRAM в ПЛИС, такая архитектура показалась крайне неудобной. Но если посмотреть на это с другой стороны, то сразу можно понять, что микросхема – это физическое устройство, распаянное на плате, и если бы все выводы были разделены, то это был бы просто геморрой, и никому он не нужен . Также есть адресная шина, и я не знаю случаев, когда нужно одновременно запрашивать чтение и запись по одному и тому же адресу.

Алгоритм работы.

SDRAM работает на системе управления, которая определяет режим и этапы работы. Вот список команд для микросхемы MT48LC16M16A2:

изображение с http://microsin.net/adminstuff/hardware/sdr-sdram-mt48lc16m16a2.html

Сама микросхема имеет множество различных режимов работы, например:

  • Пакеты разной длины могут быть прочитаны и записаны одновременно.

  • Автоматическая перезарядка ячеек памяти.

  • Количество участвующих банков памяти во время загрузки обновления.

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

Чтобы облегчить себе жизнь, я переключился на режим доступа по одной ячейке. Я также воспользовался тем свойством, что после команды чтения/записи вы можете выполнить следующую команду чтения/записи, если новый адрес находится в разрешенной строке. В моем случае я смог без остановки написать 512 слов, затем запускается процесс обновления ячейки памяти и ПЛК контроллера переходит в ожидание команды. В итоге у меня получился следующий модуль:

45edbaec288679a9e5698c024528ce40

Интерфейс m_* является входом для загрузки команд и данных, если команда предназначена для записи. Интерфейс s_* отображает результат чтения памяти. Данные считываются с задержкой в ​​3 такта.

ЧИТАТЬ   Корпус: как мы делали корпус контроллера

Логика модуля проста, команды чтения или записи фиксируются до тех пор, пока адрес меняется в младших 9 битах. Кроме того, команды больше не перехватываются, если тип команды изменился (читай<=>письмо). Модуль чувствителен к сигналу m_valid, если он падает, то контроллер памяти переходит к закрытию активированной линии и обновлению нагрузки в ячейках.

Несмотря на то, что в даташите указано, что максимальная частота этого чипа составляет 133 МГц, во время моей отладки модуль работал на частоте 150 МГц. Но я не стал искушать судьбу и оставил частоту 100 МГц (мне так удобно для дальнейшего использования).

Вот код модуля:
`timescale 1ns / 1ps

module ctrl_sdram_v2
#(
	parameter [2:0] CL = 'd3
)
(
    input clk,
	 input rst,
	 //user interface)
	 input [15:0] m_data, // valid if m_we==1
	 input [23:0] m_addr, //2bit BANK, 13bit ROW, 9bit COLUMM
	 input		 m_we	  , // 0 - read, 1 - write)
	 input		 m_valid,
	 output      m_ready,
	 output reg[15:0] s_data,
	 output reg    s_valid,
	 input	      s_ready,// invalid ready. only s_valid
	 	 
	 //SDRAM interface
	 output reg sd_cke,
	 output sd_clk,
	 output sd_dqml,
	 output sd_dqmh,
	 output reg  sd_cas_n,
	 output reg sd_ras_n,
	 output reg sd_we_n,
	 output reg sd_cs_n,
	 output reg [14:0] sd_addr,
	 inout  [15:0] sd_data
    );


reg [3:0] state_main = 'd0;
reg [15:0] state_tri ;
reg [15:0] sd_data_o ;
wire [15:0] sd_data_i ;

reg [23:0] m_addr_set="d0;
reg flg_first_cmd = "d1;
wire new_row_addr;
reg [15:0] cnt_wait="d0;
reg [15:0] cnt_wait_buf = "d0;
reg [10:0] cnt_refresh_sdram = 'd0;
always@(posedge clk)
begin
	if(rst) begin
		state_main <= 'd0;
		m_addr_set <= 'd0;
		flg_first_cmd <= 'd1;
	end else begin
		case(state_main)
			0: begin //wait 100 us
				if(cnt_wait >= 8000) state_main<= 'd1;
				else cnt_wait <= cnt_wait + 1;
			end
			1: begin //set NOP
				if(cnt_wait >= 10000) begin 
					state_main<= 'd2;
					cnt_wait <= 'd0;
				end else cnt_wait <= cnt_wait + 1;
			end
			2: begin //cmd PRECHARGE ALL
				if(cnt_wait >= 'd1) begin 
					cnt_wait <= 'd0;
					state_main <= 'd3;
				end else cnt_wait <= cnt_wait + 1'b1;
			end
			3: begin // AUTO REFRESH 0
				if(cnt_wait[14:0] >= 'd6) begin 
					cnt_wait[14:0] <= 'd0;
					if(cnt_wait[15]) begin
					state_main <= 'd4;
					cnt_wait[15] <= 'd0;
					end else cnt_wait[15] <= 'd1;
				end else cnt_wait <= cnt_wait + 1'b1;
			end
			4: begin //cmd LOAD MODE
				if(cnt_wait >= 'd1) begin 
					cnt_wait <= 'd0;
					state_main <= 'd5;
				end else cnt_wait <= cnt_wait + 1'b1;
			end
			5: begin //IDLE state
				if(m_valid) begin
					state_main <= 'd6;
					cnt_refresh_sdram <= 'd0;
				end else begin
					if(&cnt_refresh_sdram) begin
						state_main <= 'd8;
						cnt_refresh_sdram <= 'd0;
					end else cnt_refresh_sdram <= cnt_refresh_sdram + 1;
				end
			end
			6: begin // cmd ACTIVATE row
				if(cnt_wait >= CL) begin 
					cnt_wait <= 'd0;
					if(m_we) begin //cmd WRITE
						state_main <= 'd7;
					end else begin //cmd READ
						state_main <= 'd9;
					end
					m_addr_set <= m_addr;
					flg_first_cmd <= 'd1;
				end else cnt_wait <= cnt_wait + 1'b1;
			end
			7: begin //WRITE
				m_addr_set <= m_addr;
				if(flg_first_cmd) begin					
					flg_first_cmd <= 'd0;
				end else begin
					if(new_row_addr=='d1 || m_valid == 'd0 || m_ready == 'd0) begin//goto precharge
						state_main <= 'd8;
					end
				end
			end
			8: begin //cmd PRECHARGE after write
				if(cnt_wait >= 'd3) begin 
					cnt_wait <= 'd0;
					state_main <= 'd5;
				end else cnt_wait <= cnt_wait + 1'b1;
			end
			9: begin //READ and reading data from SDRAM
				m_addr_set <= m_addr;
				if(flg_first_cmd) begin					
					flg_first_cmd <= 'd0;
				end else begin
					if(new_row_addr=='d1 || m_valid == 'd0 || m_ready == 'd0) begin//
						state_main <= 'd10;
						cnt_wait_buf <= cnt_wait;
					end
				end
				cnt_wait <= cnt_wait + 1'b1;
			end
			10: begin //reading data from SDRAM
				if(cnt_wait == cnt_wait_buf+CL) begin
					state_main <= 'd11;
					cnt_wait <= 'd0;
				end else cnt_wait <= cnt_wait + 1;
			end
			11: begin // cmd AUTO REFRESH after read
				if(cnt_wait >= 'd3) begin 
					cnt_wait <= 'd0;
					state_main <= 'd5;
				end else cnt_wait <= cnt_wait + 1'b1;			
			end
		endcase
	end
end


assign new_row_addr = (m_addr_set[23:9] != m_addr[23:9]) ?  'd1 : 'd0;

assign m_ready = (state_main == 'd7 && m_we == 'd1 && new_row_addr == 'd0) ? 'd1 : 
					  (state_main == 'd9 && m_we == 'd0 && new_row_addr == 'd0) ? 'd1 :
					  'd0;
					  

always@(posedge clk)
begin
	s_data <= sd_data_i;
	s_valid <= ((state_main == 'd9 || state_main == 'd10) && cnt_wait > CL) ? 'd1 : 'd0;
end

assign sd_dqml	=0;
assign sd_dqmh	=0;

always@(posedge clk)
begin

 state_tri <= (state_main == 'd7) ? 16'd0 : 16'hFFFF; 
 sd_data_o <= (state_main == 'd7) ? m_data : 'd0;

 sd_cke	<= (state_main == 'd0) ? 'd0 : 	'd1;

 sd_cas_n<=			(state_main == 'd1) ? 'd1 : // INIT NOP
						(state_main == 'd2 && cnt_wait==0) ? 'd1 : //PRECHARGE
						(state_main == 'd2 && cnt_wait>0)  ? 'd1 :
						(state_main == 'd3 && cnt_wait[14:0]==0)  ? 'd0 : //autorefresh
						(state_main == 'd3 && cnt_wait[14:0]!=0)  ? 'd1 ://nop
						(state_main == 'd4 && cnt_wait==0)  ? 'd0 : //load mode
						(state_main == 'd4 && cnt_wait!=0)  ? 'd1 : //nop
						(state_main == 'd5)  ? 'd1 : //nop
						(state_main == 'd6 && cnt_wait==0)  ? 'd1 : //activate
						(state_main == 'd6 && cnt_wait!=0)  ? 'd1 : //nop
						(state_main == 'd7 && m_valid=='d1 && m_ready=='d1  ) ? 'd0  : //WRITE
						(state_main == 'd7 && (m_valid=='d0 || m_ready=='d0)) ? 'd1  : //nop
						(state_main == 'd8 && cnt_wait==0) ? 'd1 : //precharge after write
						(state_main == 'd8 && cnt_wait!=0) ? 'd1 : //nop
						(state_main == 'd9 && m_valid=='d1 && m_ready=='d1  ) ? 'd0  : //READ
						((state_main == 'd9 || state_main == 'd10) && (m_valid=='d0 || m_ready=='d0)) ? 'd1 : // nop
						(state_main == 'd11 && cnt_wait==0) ? 'd1: //'d0 : //auto REFRESH(1) //precharge after read
						(state_main == 'd11 && cnt_wait!=0) ? 'd1 : // nop
						'd1;
 sd_ras_n<=	(state_main == 'd1) ? 'd1 : 
						(state_main == 'd2 && cnt_wait==0) ? 'd0 :
						(state_main == 'd2 && cnt_wait>0)  ? 'd1 :
						(state_main == 'd3 && cnt_wait[14:0]==0)  ? 'd0 :
						(state_main == 'd3 && cnt_wait[14:0]!=0)  ? 'd1 :
						(state_main == 'd4 && cnt_wait==0)  ? 'd0 :
						(state_main == 'd4 && cnt_wait!=0)  ? 'd1 :
						(state_main == 'd5)  ? 'd1 :
						(state_main == 'd6 && cnt_wait==0)  ? 'd0 :
						(state_main == 'd6 && cnt_wait!=0)  ? 'd1 :
						(state_main == 'd7 && m_valid=='d1 && m_ready=='d1  ) ? 'd1  :
						(state_main == 'd7 && (m_valid=='d0 || m_ready=='d0)) ? 'd1  :
						(state_main == 'd8 && cnt_wait==0) ? 'd0 :
						(state_main == 'd8 && cnt_wait!=0) ? 'd1 :
						(state_main == 'd9 && m_valid=='d1 && m_ready=='d1  ) ? 'd1  :
						((state_main == 'd9 || state_main == 'd10) && (m_valid=='d0 || m_ready=='d0)) ? 'd1 :
						(state_main == 'd11 && cnt_wait==0) ? 'd0: //'d0 :
						(state_main == 'd11 && cnt_wait!=0) ? 'd1 :
						'd1;
 sd_we_n	<=  (state_main == 'd1) ? 'd1 : 
						(state_main == 'd2 && cnt_wait==0) ? 'd0 :
						(state_main == 'd2 && cnt_wait>0)  ? 'd1 :
						(state_main == 'd3 && cnt_wait[14:0]==0)  ? 'd1 :
						(state_main == 'd3 && cnt_wait[14:0]!=0)  ? 'd1 :
						(state_main == 'd4 && cnt_wait==0)  ? 'd0 :
						(state_main == 'd4 && cnt_wait!=0)  ? 'd1 :
						(state_main == 'd5)  ? 'd1 :
						(state_main == 'd6 && cnt_wait==0)  ? 'd1 :
						(state_main == 'd6 && cnt_wait!=0)  ? 'd1 :
						(state_main == 'd7 && m_valid=='d1 && m_ready=='d1  ) ? 'd0  :
						(state_main == 'd7 && (m_valid=='d0 || m_ready=='d0)) ? 'd1  :
						(state_main == 'd8 && cnt_wait==0) ? 'd0 :
						(state_main == 'd8 && cnt_wait!=0) ? 'd1 :
						(state_main == 'd9 && m_valid=='d1 && m_ready=='d1  ) ? 'd1  :
						((state_main == 'd9 || state_main == 'd10) && (m_valid=='d0 || m_ready=='d0)) ? 'd1 :
						(state_main == 'd11 && cnt_wait==0) ? 'd0 ://'d1 :
						(state_main == 'd11 && cnt_wait!=0) ? 'd1 :
						'd1;
 sd_cs_n	<=			(rst == 'd1) ?  'd1 : 
						(state_main == 'd1) ? 'd0 : 
						(state_main == 'd2 && cnt_wait==0) ? 'd0 :
						(state_main == 'd2 && cnt_wait>0)  ? 'd0 :
						(state_main == 'd3 && cnt_wait[14:0]==0)  ? 'd0 :
						(state_main == 'd3 && cnt_wait[14:0]!=0)  ? 'd0 :
						(state_main == 'd4 && cnt_wait==0)  ? 'd0 :
						(state_main == 'd4 && cnt_wait!=0)  ? 'd0 :
						(state_main == 'd5)  ? 'd0 :
						(state_main == 'd6 && cnt_wait==0)  ? 'd0 :
						(state_main == 'd6 && cnt_wait!=0)  ? 'd0 :
						(state_main == 'd7 && m_valid=='d1 && m_ready=='d1  ) ? 'd0  :
						(state_main == 'd7 && (m_valid=='d0 || m_ready=='d0)) ? 'd0  :
						(state_main == 'd8 && cnt_wait==0) ? 'd0 :
						(state_main == 'd8 && cnt_wait!=0) ? 'd0 :
						(state_main == 'd9 && m_valid=='d1 && m_ready=='d1  ) ? 'd0  :
						((state_main == 'd9 || state_main == 'd10) && (m_valid=='d0 || m_ready=='d0)) ? 'd0 :
						(state_main == 'd11 && cnt_wait==0) ? 'd0: //'d0 :
						(state_main == 'd11 && cnt_wait!=0) ? 'd0 :
						'd0; 
 sd_addr[14:13] <= m_addr[23:22];
 sd_addr[12:0]	<=  (state_main == 'd2 && cnt_wait==0) ? {4'b0,1'b1,10'b0} :  //[10] = 1
						(state_main == 'd4 && cnt_wait==0)  ? {2'b00,3'b000,1'b1,2'b00,CL[2:0],1'b0,3'b000} :  //BA[1:0]==0,A[12:10]==0,WRITE_BURST_MODE = 0,OP_MODE = 'd0, CL = 2, TYPE_BURST = 0, BURST_LENGTH = 1
						(state_main == 'd6 && cnt_wait==0)  ? m_addr[21:9] :
						(state_main == 'd7) ? {5'd0,m_addr[8:0]} : 
						(state_main == 'd8 && cnt_wait==0) ? {4'b0,1'b1,10'b0} :  //[10] = 1
						(state_main == 'd9) ? {7'd0,m_addr[8:0]} :
						(state_main == 'd11 && cnt_wait==0) ? {4'b0,1'b1,10'b0} :  //[10] = 1
						'd0;

end



ODDR2 #(
	.DDR_ALIGNMENT("NONE"), // Sets output alignment to "NONE", "C0" or "C1" 
	.INIT(1'b0),    // Sets initial state of the Q output to 1'b0 or 1'b1
	.SRTYPE("SYNC") // Specifies "SYNC" or "ASYNC" set/reset
) ODDR2_inst (
	.Q		(sd_clk),   // 1-bit DDR output data
	.C0	(clk),   // 1-bit clock input
	.C1	(!clk),   // 1-bit clock input
	.CE	(!rst), // 1-bit clock enable input
	.D0	(1), // 1-bit data input (associated with C0)
	.D1	(0), // 1-bit data input (associated with C1)
	.R		(0),   // 1-bit reset input
	.S		(0)    // 1-bit set input
);


genvar i;
generate
	for (i=0; i < 16; i=i+1) 
	begin: tri_state
		OBUFT #(
			.DRIVE(12),   // Specify the output drive strength
			.IOSTANDARD("DEFAULT"), // Specify the output I/O standard
			.SLEW("SLOW") // Specify the output slew rate
		) OBUFT_inst (
			.O(sd_data[i]),     // Buffer output (connect directly to top-level port)
			.I(sd_data_o[i]),     // Buffer input
			.T(state_tri[i])      // 3-state enable input 
		);
		
		
		IBUF #(
			.IOSTANDARD("DEFAULT")    // Specify the input I/O standard
		)IBUF_inst (
			.O(sd_data_i[i]),     // Buffer output
			.I(sd_data[i])      // Buffer input (connect directly to top-level port)
		);
	end
endgenerate

endmodule

Заключение. Статья получилась не исчерпывающей и пояснительной, а на Этот сайт есть русскоязычная версия техпаспорта и с комментариями автора. Я опирался на него, когда осваивал материал.

ЧИТАТЬ   Реализация внутриигровых миссий на Unity (P2)
PS как я закрепил модуль VGA и контроллер SDRAM работают.
Структурная схема взаимодействия модуля VGA с памятью.

Структурная схема взаимодействия модуля VGA с памятью.

В проекте было два тактовых домена: 100 МГц и 25 МГц. Из-за того, что контроллер памяти работал на частоте 100 МГц, он теоретически мог записать в себя 3 новых кадра до того, как изображение отрисовывалось на монитор.

Автомат работает в двух состояниях, либо загружает новое изображение, либо вычитает существующее изображение для последующего рендеринга. Режим по умолчанию — это режим записи нового кадра в память, при поступлении сигнала из FIFO о том, что он почти пуст, автомат переключается на чтение памяти и вычитает необходимое количество. В данном случае сигнал почти_пусто повышается, когда в FIFO осталось 100 значений, это сделано для того, чтобы машина успела перейти в режим чтения и модуль Ctrl_SDRAM успел выполнить предыдущую команду. Машина считывает следующие 900 значений пикселей из памяти и возвращается в режим записи.

FIFO — это два такта, с глубиной около 1000 значений. На частоте 100МГц пишет в него, а модуль VGA вычитает из своей частоты 25МГц. Если оценить время, через которое нужно будет снова переключить машину на чтение, то оно таково: в память считывается 100 значений + 900 новых значений – четверть значений\ u200b, которые успевают вычесть за этот период, в итоге имеем 750 значений в FIFO. В итоге модуль VGA прочитает следующие 650 циклов значений, до того, как поднимется почти пустой флаг, переводим его в 100 МГц, получаем 2600 циклов записи в память, этого более чем достаточно. Естественно, здесь вы должны понимать, что модуль VGA не читает FIFO в областях, где изображение не отрисовывается: около 160 тактов в конце каждой строки пикселей и 7200 тактов в конце кадра на частоте 25 МГц. , всего 336 000 циклов бездействия на частоте 100 МГц.

ЧИТАТЬ   Эксперт назвал два способа использования старого Wi-Fi-роутера

Source

От admin