В прошлой статье, к моему сожалению, код оказался не рабочий. Из-за недостатка опыта, я читал свои же данные на шине и воспринял это как работоспособность памяти. Исправляю это недоразумение в этой статье. Опытным разработчикам читать только из спортивного интереса :-).

Немного изменим аппаратную часть. Итак постановка задачи. Будем передавать по последовательному каналу (RS232) с компьютера маркер адреса 0x80, за которым ожидаются два байта содержащие адрес. Маркер данных 0x40, за которым ожидаются два байта данных. Команда 0x20 запись в память, команда 0x10 чтение из памяти (данные не требуются). Кроме этого будем отображать на семи сегментном индикаторе принятые адрес, данные (как переданные, так и прочитанные), и команды. Далее схемы. А так же имейте ввиду, так как я начинающий и опыта у меня мало, пожалуйста внимательно проверяйте всё, если будете повторять.

Микросхема памяти SDRAM.
Микросхема памяти SDRAM.
Интерфейс RS232.
Интерфейс RS232.

Данная задача довольно простая, не смотря на бытующее мнение о её сложности. Я имею ввиду задачу проверки чтения записи в память. Так как если вам необходим "серьёзный" контроллер, то это будет немного сложнее (есть всякие режимы обмена, например прервать чтение для записи и т.п.). Так же хочу напомнить, что это не руководство, а просто небольшой практический пример. Читайте внимательно теорию и документацию на микросхему.

Не смотря на вышесказанное, у меня возникли трудности в реализации этой задачи. Во первых, не зря говорят, что при освоении verilog опыт программирования серьёзно мешает (по крайней мере мне он мешал). Важно не забывать, что код на verilog это не программа, а описание схемы. У меня было много "детских" ошибок. Одна из них оказалась категорически не хотела обнаруживаться. Как я не пытался её найти, все глаза проглядел, ни в какую. Пришлось познакомится со средствами отладки.

Посмотрел уроки по Signal Tap. Хорошая штука, а главное не прихотливая. Попытки прикрутить её "левой ногой" увенчались успехом. И что же я увидел на временной диаграмме в момент записи? Команду чтения! Вот это поворот. Оказывается я умудрился написать в коде проверку наличие команды записи в двух состояниях автомата управления, а команда выдаётся на один такт и во втором состоянии её никогда не будет. А проверка была следующая: если ЗАПИСЬ код для записи, иначе код для ЧТЕНИЯ. Не делайте так! Теперь коды, а после небольшое резюме по тонкостям.

Это главный модуль.

Скрытый текст
`timescale 1ns / 1ps
/* Тест работы с SDRAM. Принимаем по UART адрес данные и команду.
	В соответствии с командой пишем/читаем память.
	В процессе работы показываем на семисегментном индикаторе
	что принимается и отправляется.*/
module SDRAM3(
	output wire [3:0] dig_n,
	output wire [7:0] seg_n,

	(* chip_pin = "114" *) output wire TxD,
	(* chip_pin = "73"  *) output wire SD_BS0,
	(* chip_pin = "74"  *) output wire SD_BS1,
	(* chip_pin = "42"  *) output wire SD_LDQM,
	(* chip_pin = "55"  *) output wire SD_UDQM,
	(* chip_pin = "72"  *) output wire SD_CS_n,
	(* chip_pin = "71"  *) output wire SD_RAS_n,
	(* chip_pin = "70"  *) output wire SD_CAS_n,
	(* chip_pin = "69"  *) output wire SD_WE_n,
	(* chip_pin = "58"  *) output wire SD_CKE,
	(* chip_pin = "43"  *) output wire SD_CLK,// фаза+30 относительно clk_sys
	(* chip_pin = "115" *) input wire RxD,
	(* chip_pin = "25"  *) input wire rst_n,
	(* chip_pin = "23"  *) input wire clk,

	output wire [11:0] S_A,
	inout  wire [15:0] S_DQ
);

	wire Button_Reset;	// Кнопка сброс
	wire reset;			// Это общий сброс. Кроме кнопки, влияет состояние PLL

	wire clk_sys;    	// 50 MHz (0 deg)
	wire clk_sdram;  	// 50 MHz (30 deg)
	wire pll_locked; 	// 0 - PLL не стабильно, 1 - стабильно
	wire Empty;
	wire [7:0] dataRx;

	// Сигналы для передатчика UART
	reg startTx;
	reg [7:0] dataTx;
	wire rdyTx;
	wire key_mode;

	reg  [15:0] dq_out_ioe;
	reg  dq_oe_ioe;
	reg  [15:0] dq_in_ioe;

	reg cmd_write;
	reg cmd_read;

	reg [15:0] data_from_ram;	// Данные прочитанные из памяти
	wire SD_Ready;				// Готовность контроллера принимать команды

	reg [15:0] display_value;// = 16'h1235;

	reg fAReady = 1'd0; // Блокируют приём, до записи в SDRAM
	reg fDReady = 1'd0; // После записи в SDRAM, сбрасываются в ноль
	reg fCmdWrite;
	reg fCmdRead;
	reg [21:0] reg_addr; // bank[21:20], row[19:8], col[7:0]
	reg [15:0] reg_data; // Для записи в память
	reg nechet;
	reg rdRx_en;

	// Состояния автомата управления режимами
	reg [3:0] State;
	reg [2:0] pause;
	localparam	STATE_IDLE       = 4'd0,
				STATE_WAIT_ADDR  = 4'd1,
				STATE_WAIT_DATA  = 4'd2,
				STATE_ACK        = 4'd3,
				STATE_WAIT       = 4'd4,
				STATE_READ       = 4'd5,
				STATE_WRITE      = 4'd6,
				STATE_WRITE_WAIT = 4'd7,
				STATE_DISPLAY    = 4'd8;

 // 1 такт на активацию + 1 на команду + 2 CL + 1 на входной регистр ПЛИС
localparam RPause = 3'd5,
           WPause = 3'd5; 

	assign reset = Button_Reset | ~pll_locked;

	// Синхронная передача сигналов на периферию (внутри IOE)
	always @(posedge clk_sys) begin
	  dq_out_ioe  <= reg_data;
	  dq_in_ioe  <= S_DQ;	// Фиксация входных данных из памяти
	  dq_oe_ioe  <= (State == STATE_WRITE); // dq_oe_ioe появится в следующем
	  // такте. В (State == STATE_WRITE) жду один такт
	end

	assign S_DQ = dq_oe_ioe  ? dq_out_ioe : 16'bZ;

	assign SD_CKE  = 1'd1;
	assign SD_LDQM = 1'd0; // Не используем маскирование
	assign SD_UDQM = 1'd0; // Не используем маскирование

	Button BT_R(.TTrigQ(Button_Reset),	.X(rst_n), .C(clk_sys));

	/*-------------------------------------------------------------------------
	   Из одного входного тактового сигнала формируется два:
			-первый копия входного,
			-второй смещённый по фазе на +30 градусов. Это необходимо
			для корректной работы микросхемы памяти на высокой частоте.
			Так как не всегда все дорожки на плате одинаковые и короткие,
			что приводит к задержкам и несогласованной с контроллером работе. */
	PLL_SDRAM_30deg PLL1(
			.inclk0(clk),			// Входной тактовый сигнал
			.c0(clk_sys),			// тактирование для всех, кроме SDRAM
			.c1(clk_sdram),			// тактирование для SDRAM
			.locked(pll_locked));	// 0- сигнал не стабилен


	/* Передатчик */
	RS232T transmitter(
			.clk(clk_sys),
			.rst(reset),
			.start(startTx),	// Старт передачи
			.data(dataTx),
			.TxD(TxD),
			.rdy(rdyTx));		// Готов передавать


	/* Приёмник содержит кольцевой буфер на 8-мь байт */
	RS232R resiver(
			.clk(clk_sys),
			.rst(reset),
			.RxD(RxD),
			.done(rdRx_en),		// Прочитали, переставь указатель в очереди
			.empty(Empty),		// Буфер пустой
			.data(dataRx));		// Текущая позиция буфера


	SemSeg S1(
			.clk(clk_sys),
			.rst(reset),
			.display_value(display_value),
			.seg(seg_n),
			.dig(dig_n));


	/* Выводим сдвинутый тактовый сигнал наружу на микросхему
	   через специализированный DDIO примитив Intel/Altera.
	   Обычный assign вызовет джиттер и фазовый сдвиг, ломающий Fast I/O. */
	altddio_out #(
		.width(1)
	) sdram_clk_ddio_buf (
		.datain_h(1'b1),
		.datain_l(1'b0),
		.outclock(clk_sdram),
		.dataout(SD_CLK)
	);



//-------------  UART - SDRAM -----------------------------------------------
	always @(posedge clk_sys) begin
		if (reset) begin
			startTx   <= 1'b0;  dataTx    <= 8'd0;
			nechet    <= 1'd0;  fAReady   <= 1'd0;
			fDReady   <= 1'd0;  reg_addr  <= 22'd0;
			reg_data  <= 16'd0; cmd_write <= 1'd0;
			cmd_read  <= 1'd0;  State     <= STATE_IDLE;
			pause     <= 3'd0;  rdRx_en   <= 1'd0;
			data_from_ram <= 16'd0;
			display_value <= 16'h1235;
		end else begin
			startTx   <= 1'b0;  cmd_write <= 1'd0;  cmd_read  <= 1'd0;

			case (State)
				STATE_IDLE: begin // Ждём адрес, данные и команды
					if (!Empty) begin

						if (dataRx == 8'h80) begin
							if (!fAReady) begin
								State    <= STATE_WAIT_ADDR;
								nechet   <= 1'b0;
							end
						end

						if (dataRx == 8'h40) begin
							if (!fDReady) begin
								State    <= STATE_WAIT_DATA;
								nechet   <= 1'b0;
							end
						end

						if (dataRx == 8'h20) begin
						// Пришла команда, если всё готово, выполняем
							if (fAReady && fDReady) begin
								fCmdWrite <= 1'd1;
								State     <= STATE_ACK;
							end
						end						// иначе игнорируем

						if (dataRx == 8'h10) begin
						// Пришла команда, если всё готово, выполняем
							if (fAReady) begin
								fCmdRead  <= 1'd1;
								State     <= STATE_ACK;
							end
						end						// иначе игнорируем

						display_value[7:0] <= dataRx;
						rdRx_en  <= 1'd1;//Прочитал!Переставь указатель буфера
					end
				end

				STATE_WAIT_ADDR: begin// Ждём два байта адреса, первый младший
					if (rdRx_en == 1'd1) begin
						rdRx_en <= 1'd0;
					end else begin
						if (!Empty) begin
							if (nechet == 1'd1) begin // Старший байт адреса
								reg_addr[21:8] <= {10'd0, dataRx[3:0]};
								fAReady        <= 1'd1;
								State          <= STATE_ACK;
								display_value  <= {4'd0, dataRx[3:0], reg_addr[7:0]};
							end else begin    // Младший байт адреса
								reg_addr[7:0]  <= dataRx;
							end
							nechet  <= ~nechet;
							rdRx_en <= 1'd1;
						end
					end
				end

				STATE_WAIT_DATA: begin// Ждём два байта данных, первый младший
					if (rdRx_en == 1'd1) begin
						rdRx_en <= 1'd0;
					end else begin
						if (!Empty) begin
							if (nechet) begin // Старший байт адреса
								reg_data[15:8] <= dataRx;
								fDReady        <= 1'd1;
								State          <= STATE_ACK;
								display_value  <= {dataRx, reg_data[7:0]};
							end else begin // Младший байт адреса
								reg_data[7:0]  <= dataRx;
							end
							nechet  <= ~nechet;
							rdRx_en <= 1'd1;
						end
					end
				end

				STATE_ACK: begin// Ответ на приём
					rdRx_en <= 1'd0;
					if (fCmdWrite || fCmdRead) begin
						dataTx    <= 8'h5A;
						State     <= STATE_WAIT;
					end else begin
						dataTx    <= 8'hA5;
						State     <= STATE_IDLE;
					end
					startTx   <= 1'b1;
				end

				STATE_WAIT: begin
					if (SD_Ready && fAReady) begin
						if (fCmdWrite && fDReady) begin
							State     <= STATE_WRITE;
							cmd_write <= 1'd1;	
							pause     <= 3'd0;
						end

						if (fCmdRead) begin
							cmd_read <= 1'd1;
							State    <= STATE_READ;
							pause    <= 3'd0;
						end
					end

					if (fCmdWrite) begin
						if (~fAReady || ~fDReady) begin
							State     <= STATE_IDLE;
							fCmdWrite <= 1'd0;
						end
					end

					if (fCmdRead) begin
						if (~fAReady) begin
							State    <= STATE_IDLE;
							fCmdRead <= 1'd0;
						end
					end
				end

				STATE_READ: begin
					/* Начинается Activete.         pause == 0
					1 такт tRCD подаём команду read pause == 1
					2 такта CL                      pause == 3 читаем
					1 такт tRP предзаряд            pause == 4 */
					if (pause >= RPause) begin
						data_from_ram <= dq_in_ioe; // Читаю данные
						State         <= STATE_DISPLAY;
						fAReady       <= 1'd0;
						fCmdRead      <= 1'd0;	// Чтение завершено
						
					end else begin
						pause         <= pause + 3'd1;
					end
				end
				
				STATE_DISPLAY: begin// Ещё такт в счёт precharge
					display_value <= data_from_ram;
					State         <= STATE_IDLE;
				end

				STATE_WRITE: begin
				/* Данные на шину выставляем по состоянию
					cmd_write, через такт будет activete, ещё через один write
					в следующем такте, (activete) данные должны быть на шине.
					1 такт задержки до activete,
					1 такт от активации до команды,
					2 такта время восстановления после записи,
					1 такт время предзаряда. Итого 5-ть тактов,
					после чего можно подавать другие комманды,
					если будет задержка на один такт при переходе в Idle
					не страшно!*/
					if (pause == 3'd0) begin
						cmd_write <= 1'd0;
					end
					State <= STATE_WRITE_WAIT;
				end

				STATE_WRITE_WAIT: begin
					if (pause >= WPause) begin
						State     <= STATE_IDLE;
						fAReady   <= 1'd0;
						fDReady   <= 1'd0;
						fCmdWrite <= 1'd0; // Запись завершена
					end else begin
						pause     <= pause + 3'd1;
					end
				end
				default: State <= STATE_IDLE;
			endcase
		end
	end	
//------------------- UART - SDRAM ------------------------------------------


/* Контроллер реализован в виде автомата. На основании входных данных
		формирует сигналы управления, с учётом необходимых задержек. */
	CntrlSDRAM Controller_50MHz(
		.SD_CS(SD_CS_n),
		.SD_RAS(SD_RAS_n),
		.SD_CAS(SD_CAS_n),
		.SD_WE(SD_WE_n),
		.SD_BS0(SD_BS0),
		.SD_BS1(SD_BS1),
		.S_A(S_A),					// Адрес на шину
		.ready(SD_Ready),			// 1- готов к приёму команд
		.cmd_read(cmd_read),
		.cmd_write(cmd_write),
		.addr(reg_addr),			// Адрес от "меня", куда писать/читать
		.clk(clk_sys),
		.rst(reset)
	);
endmodule

Модуль кнопки.

Скрытый текст
`timescale 1ns / 1ps
/* Код для кнопок */
module Button(output reg TTrigQ,
              input X,
				  input C);

	initial TTrigQ <= 1'd1;
	
	reg [18:0]CTQ; // счётчик подавления дребезга контактов
	reg XQ, RSTrigQ, BQ;
	
	wire FY = !RSTrigQ & BQ; // схема выделения фронта
	
	always @(posedge C)
	begin
		XQ <= !X;
		/* &CTQ - все биты счётчика равны единице, т.е. максимум, даёт единицу
	      |CTQ - все биты счётчика равны нулю, т.е. минимум, даёт ноль	*/
		if (XQ & ~&CTQ) CTQ <= CTQ + 1'd1;
			else if (!XQ & |CTQ) CTQ <= CTQ - 1'd1;

		// счётчик досчитал до максимум, запоминаем 1
		if (&CTQ) RSTrigQ <= 1'd1;
			// счётчик досчитал до минимума, запоминаем 0
			else if (~|CTQ) RSTrigQ <= 1'd0;

		BQ <= RSTrigQ;
		TTrigQ <= FY;
	end
endmodule

Модуль семи сегментного индикатора.

Скрытый текст
`timescale 1ns / 1ps
module SemSeg(
	output reg [7:0] seg,
	output reg [3:0] dig,
	input [15:0] display_value,
	input clk,
	input rst
);

/* ---------------------------------------------------------------------------
 1. Делитель частоты для развертки индикатора
 Для 50 МГц частота переключения разрядов должна быть
 около 1 КГц (250 Гц на разряд) 50 000 000 / 2^16 = ~762 Гц,
 что отлично подходит (мерцания не будет) */
	reg [15:0] clk_div = 16'd0;
	always @(posedge clk) begin
	  clk_div <= clk_div + 1'b1;
	end
 
	// Старшие 2 бита делителя используются как счетчик
	// текущего активного разряда (0, 1, 2, 3)
	wire [1:0] refresh_counter = clk_div[15:14];
	reg  [3:0] mask = 4'b0000;

	// 2. Селектор данных (Мультиплексор)
	// Выбираем, какую именно тетраду (4 бита) из общего
	// числа передавать на дешифратор
	reg [4:0] current_digit;
	always @(*) begin
		case (refresh_counter)
		2'b00: current_digit = {mask[0], display_value[3:0]};  // Разряд 0 п.
		2'b01: current_digit = {mask[1], display_value[7:4]};  // Разряд 1
		2'b10: current_digit = {mask[2], display_value[11:8]}; // Разряд 2
		2'b11: current_digit = {mask[3], display_value[15:12]};// Разряд 3 л.
		default: current_digit = {1'd0, 4'h0};
		endcase
	end
 
	// 3. Управление анодами (Активация нужного знакоместа)
	// Подаем логический "0" на общий анод выбранного индикатора
	// (для схемы с общим анодом)
	always @(*) begin
	  case (refresh_counter)
			2'b00: dig = 4'b1110; // Включен разряд 0
			2'b01: dig = 4'b1101; // Включен разряд 1
			2'b10: dig = 4'b1011; // Включен разряд 2
			2'b11: dig = 4'b0111; // Включен разряд 3
			default: dig = 4'b1111; // Все выключены
	  endcase
	end
 
	// 4. Дешифратор 4-в-7 (HEX в код сегментов)
	// Порядок битов в segments: [6:0] -> {g, f, e, d, c, b, a}.
	// Логический "0" включает сегмент.
	always @(*) begin
		case (current_digit)
			5'h0: seg = 8'b11000000; // "0"
			5'h1: seg = 8'b11111001; // "1"
			5'h2: seg = 8'b10100100; // "2"
			5'h3: seg = 8'b10110000; // "3"
			5'h4: seg = 8'b10011001; // "4"
			5'h5: seg = 8'b10010010; // "5"
			5'h6: seg = 8'b10000010; // "6"
			5'h7: seg = 8'b11111000; // "7"
			5'h8: seg = 8'b10000000; // "8"
			5'h9: seg = 8'b10010000; // "9"
			5'hA: seg = 8'b10001000; // "A"
			5'hB: seg = 8'b10000011; // "b"
			5'hC: seg = 8'b11000110; // "C"
			5'hD: seg = 8'b10100001; // "d"
			5'hE: seg = 8'b10000110; // "E"
			5'hF: seg = 8'b10001110; // "F"
			default: seg = 8'b11111111; // Выключено
		endcase
	end
	//--------------------------------------------------------------------------------------
	
endmodule

Код модуля приёма по UART.

Скрытый текст
`timescale 1ns / 1ps

// RS232 Передатчик скорость 19200 (бит/сек), 8 bit data, без чёт/нечет
// clk 50 MHz
module RS232R(
    input clk, rst,
	 input RxD,
	 input done,		// Данные прочитали
	 output empty,		// Буфер пустой
    output [7:0] data);

wire endtick, midtick, endbit;

wire [11:0] limit;
// 1 Выполняется приём
reg run;
// Для обнаружения фронта
reg Q0, Q1;
reg [11:0] tick;
reg [3:0] bitcnt;
reg [7:0] shreg;

reg [2:0] rd_ptr;		// Счёт от нуля до семи и снова ноль.
reg [2:0] wr_ptr;
reg [7:0] fifo[7:0];	// Кольцевой буфер

// Длинна бита в тактах, при данной скорости обмена
assign limit = 12'd2604;
// Флаг конец посылки
assign endtick = tick == limit;
// limit/2 Флаг середина бита
assign midtick = tick == {1'b0, limit[11:1]};
// Флаг конец бита
assign endbit = bitcnt == 4'd8;

assign data = fifo[rd_ptr];
assign empty = (wr_ptr == rd_ptr);

always @ (posedge clk) begin
	if (rst) begin

		Q0      <= 1'b1;
		Q1      <= 1'b1;
		run     <= 1'b0;
		tick    <= 12'd0;
		bitcnt  <= 4'd0;
		shreg   <= 8'd0;
		rd_ptr  <= 3'd0;
		wr_ptr  <= 3'd0;
		
	end else begin
/* Фиксируем задний фронт, начало посылки (сообщения) */
		Q0 <= RxD; Q1 <= Q0;
		
/* Есть спад, начинаем приём.
Конец бита и конец всей посылки, стоп приём. (иначе храним состояние) */
		run <= (Q1 & ~Q0) ? 1'd1 : (endtick & endbit) ? 1'd0 : run;

		tick <= (run & ~endtick) ? tick + 1'd1 : 1'd0;

/* Счётчик бит */
		bitcnt <= (endtick & ~endbit) ? bitcnt + 1'd1 :
		 (endtick & endbit) ? 1'd0 : bitcnt;

/* В середине бита заносим принятый бит в сдвиговый регистр */
		shreg <= midtick ? {Q1, shreg[7:1]} : shreg;

/* Конец бита и конец посылки */
		if (endtick & endbit) begin
			fifo[wr_ptr] <= shreg;
			wr_ptr <= wr_ptr + 3'd1;
		end

/* Данные прочитаны (done) */
		if (done && ~empty) begin
			rd_ptr <= rd_ptr + 3'd1;
		end
		
	end
end

endmodule

Код модуля передатчика по UART.

Скрытый текст
`timescale 1ns / 1ps  // NW 4.5.09 / 15.8.10 / 15.11.10

// RS232 transmitter for 19200 bps, 8 bit data, без чётности/нечётности
// clock is 50 MHz; 50000 / 2604 = 19.2 KHz

module RS232T(
    input clk, rst,
    input start, // request to accept and send a byte
    input [7:0] data,
    output rdy,
    output TxD);

wire endtick, endbit;
wire [11:0] limit;
reg run;
reg [11:0] tick;
reg [3:0] bitcnt;
reg [8:0] shreg;

assign limit = 12'd2604; // Длина бита в тактах, при данной скорости обмена
assign endtick = tick == limit; // Флаг конца бита
assign endbit = bitcnt == 4'd9; // Флаг конца битовой последовательности
assign rdy = ~run;     // Не работаем? установим флаг готовность к передаче.
assign TxD = shreg[0]; // Выдаём в линию младший бит сдвигового регистра

always @ (posedge clk) begin
/* Если сброс, или конец тика и конец бита, то ноль;
       иначе проверяем старт.
		Если старт, то еденица,
	       иначе храним значение.	*/
  run <= (rst | endtick & endbit) ? 1'd0 : start ? 1'd1 : run;

/* Пока run и не конец тика, инкремент переменной тик,
        иначе ноль. */
  tick <= (run & ~endtick) ? tick + 1'd1 : 1'd0;

/* Конец тика и не конец бита, увеличиваем счетчик бит;
		Иначе, если конец тика и конец бита, то ноль,
	      иначе храним значение. */
  bitcnt <= (endtick & ~endbit) ? bitcnt + 1'd1 :
    (endtick & endbit) ? 1'd0 : bitcnt;

/* В сдвиговом регистре, если сброс, единица (высокий на линии)
	   иначе, есть старт, выдаём стартовый бит
		  (ноль в младшем разряде сдвигового регистра),
            иначе, если конец тика, сдвиг вправо (в сторону мл. разряда),
					         иначе храним значение. */
  shreg <= (rst) ? 1'd1 : start ? {data, 1'b0} :
    endtick ? {1'b1, shreg[8:1]} : shreg;
end
endmodule

Код контроллера SDRAM.

Скрытый текст
`timescale 1ns / 1ps
module CntrlSDRAM(
	output	reg SD_CS,
	output	reg SD_RAS,
	output	reg SD_CAS,
	output	reg SD_WE,
	output	reg SD_BS0,
	output	reg SD_BS1,
	output	reg [11:0] S_A,	// Адрес на шину
	output	reg ready,		// 1 - готов к приёму команд
	input		cmd_read,
	input		cmd_write,
	/* bank[21:20], row[19:8], col[7:0] */
	input		[21:0] addr,// Адрес от пользователя
	input		clk,
	input		rst
);
	// Состояния конечного автомата
   localparam ST_INIT_NOP  = 4'd0,
              ST_INIT_PRE  = 4'd1,
              ST_INIT_REF  = 4'd2,
              ST_INIT_MRS  = 4'd3,
              ST_IDLE      = 4'd4,
              ST_ACTIVATE  = 4'd5,
              ST_WRITE     = 4'd6,
              ST_READ      = 4'd7,
              ST_PRECHARGE = 4'd8,
              ST_REFRESH   = 4'd9;

	// Команды SDRAM {CS, RAS, CAS, WE}
   localparam CMD_NOP      = 4'b0111,
              CMD_PRECHARGE= 4'b0010,
              CMD_REFRESH  = 4'b0001,
              CMD_LOAD_MODE= 4'b0000,
              CMD_ACTIVATE = 4'b0011,
              CMD_READ     = 4'b0101,
              CMD_WRITE    = 4'b0100,
              CMD_INHIBIT  = 4'b1111;

	// Константы задержек для 50 МГц
	 // 1 / (50*10^6) = 20нс => 20 * 10000 = 200мкс
	localparam WAIT_200US = 14'd10000;
	localparam WAIT_TRP   = 14'd2; /* Было 1, (20 ns) ?добавлю один такт*/
	localparam WAIT_TRFC  = 14'd4; /* 65 ns - 4 такта */
	localparam WAIT_TMRD  = 14'd2; 
	localparam WAIT_TRCD  = 14'd1;
	
	/* Микросхема HY57V641620FTP-H содержит 4096 строк,
		минимальное время удержания заряда в ячейках памяти
		составляет 64 мс. 64 / 4096 = 0.015625,
		округляем в меньшую сторону для надёжности получаем
		15.6 мкс - интервал автообновления ячеек.
		15.6 / 20 нс = 780 тактов. */
   localparam REFRESH_INTERVAL = 10'd779;
	/* Пишут что 8-мь необязательно, вроде бы это для DDR,
		По документации нужно что бы больше 2-х было. */
	localparam INIT_REFRESH_CNT = 4'd8;
	
	reg [9:0]  refresh_timer;
	reg        refresh_req;
	reg [3:0]  state;
	reg [13:0] delay_cnt;
	reg [3:0]  init_ref_cnt;
	reg        initOk;
	reg        fW;
	reg        fR;

	// --- ЕДИНЫЙ СИНХРОННЫЙ АВТОМАТ УПРАВЛЕНИЯ И КОМАНД ---
	always @(posedge clk or posedge rst) begin
		if (rst) begin
			initOk       <= 1'd0;
			state        <= ST_INIT_NOP;
			delay_cnt    <= 14'd0;
			init_ref_cnt <= 4'd0;
			ready        <= 1'b0;
			fW           <= 1'd0;
			fR           <= 1'd0;
	// Безопасное состояние при сбросе: память полностью отключена (CS_n = 1)
			{SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_INHIBIT;
			{SD_BS1, SD_BS0}               <= 2'b00;
			S_A                            <= 12'd0;
			refresh_timer <= 10'd0;
			refresh_req   <= 1'b0;
		end else begin
			if (initOk) begin
				if (refresh_timer >= REFRESH_INTERVAL) begin
					refresh_timer <= 10'd0;
					refresh_req   <= 1'b1; 
				end else begin
					refresh_timer <= refresh_timer + 1'b1;
				end
			end

			case (state)						
// ----------- Стадия инициализации -----------
				ST_INIT_NOP: begin
				  {SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_INHIBIT;				  
				  if (delay_cnt >= WAIT_200US) begin
						state                          <= ST_INIT_PRE;
						delay_cnt                      <= 14'd0;
						/* Выдаем команду PRECHARGE заранее
						(появится на пинах при переходе в ST_INIT_PRE) */
						{SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_PRECHARGE;
						// A10=1 (Precharge All)               |
						S_A                            <= 12'b0100_0000_0000;
				  end else begin
						delay_cnt <= delay_cnt + 1'b1;
				  end
				end

				ST_INIT_PRE: begin 
				  if (delay_cnt >= WAIT_TRP) begin
						state                          <= ST_INIT_REF;
						delay_cnt                      <= 14'd0;
						// Заранее готовим первую команду REFRESH
						{SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_REFRESH;
				  end else begin
						{SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_NOP;
						delay_cnt                      <= delay_cnt + 1'b1;
				  end
				end

				ST_INIT_REF: begin
				  if (delay_cnt >= WAIT_TRFC) begin
						delay_cnt <= 14'd0;
						// Если отработали нужное количество циклов регенерации
						if (init_ref_cnt == INIT_REFRESH_CNT) begin
							 state                          <= ST_INIT_MRS;
							 {SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_LOAD_MODE;
							 {SD_BS1, SD_BS0}               <= 2'b00;
							  // CL=2, BL=1
							 S_A                       <= 12'b0000_0010_0000;
						end else begin
							 // Увеличиваем счетчик выполненных регенераций
							 init_ref_cnt              <= init_ref_cnt + 1'b1;
							 // Посылаем СЛЕДУЮЩУЮ команду REFRESH
							 {SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_REFRESH;
						end
				  end else begin
						{SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_NOP;
						S_A                            <= 12'd0;
						delay_cnt                      <= delay_cnt + 1'b1;
				  end
				end

				ST_INIT_MRS: begin 
				  if (delay_cnt >= WAIT_TMRD) begin
						state                          <= ST_IDLE;
						delay_cnt                      <= 14'd0;
						initOk                         <= 1'd1;
				  end else begin
						delay_cnt                      <= delay_cnt + 1'b1;
				  end
				  {SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_NOP;
				  S_A                            <= 12'd0;
				end
// ----------- Стадия инициализации завершена -----------

				ST_IDLE: begin
				  ready                          <= 1'b1;
				  delay_cnt                      <= 14'd0; 
				  {SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_NOP;
				  if (refresh_req) begin
						state                          <= ST_REFRESH;
						ready                          <= 1'b0;
						// Заранее выставляем регенерацию
						{SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_REFRESH;
						S_A              <= 12'd0;
						refresh_req                    <= 1'd0;
				  end else begin
						if (cmd_read || cmd_write) begin
							state                          <= ST_ACTIVATE;
							ready                          <= 1'b0;
							// Заранее активируем строку
							{SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_ACTIVATE;
							{SD_BS1, SD_BS0}               <= addr[21:20];
							S_A                            <= addr[19:8];
						end
						/* В следующем такте команду cmd_read снимут */
						if (cmd_read) begin
							fR <= 1'd1; // Поэтому заменим на этот флаг
						end
						if (cmd_write) begin
							fW <= 1'd1;
						end
				  end
				end	

				ST_ACTIVATE: begin 
				  if (delay_cnt >= (WAIT_TRCD - 1)) begin
						delay_cnt <= 14'd0;
						{SD_BS1, SD_BS0}             <= addr[21:20];
						// A10=1 (Авто-пречардж)
						S_A                          <= {4'b0100, addr[7:0]};
						if (fW) begin
							state                          <= ST_WRITE;
							{SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_WRITE;
							fW                             <= 1'd0;
						end
						if (fR) begin
							state                          <= ST_READ;
							{SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_READ;
							fR                             <= 1'd0;
						end
				  end else begin
						{SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_NOP;
						S_A                            <= 12'd0;
						delay_cnt                      <= delay_cnt + 14'b1;
				  end
				end

				ST_WRITE: begin
					{SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_NOP;
					// Удерживаем адрес и банки, пока выполняется запись!
					{SD_BS1, SD_BS0}               <= addr[21:20];
					 // A10=1 (Авто-пречардж)
					S_A                            <= {4'b0100, addr[7:0]};

					if (delay_cnt >= 14'd2) begin//tWR 2 tRP 1 - 1 уже был = 2
						delay_cnt            <= 14'd0; 
						state                <= ST_IDLE;
						{SD_BS1, SD_BS0}     <= 2'd0;
						S_A                  <= 12'd0;
					end else begin
						delay_cnt <= delay_cnt + 14'b1;
					end
				end

				ST_READ: begin
					{SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_NOP;
					//2CL + 1 предзаряд-1 задержка=2
					if (delay_cnt >= 14'd2) begin
						delay_cnt            <= 14'd0; 
						state                <= ST_IDLE;
						{SD_BS1, SD_BS0}     <= 2'd0;
						S_A                  <= 12'd0;
					end else begin
						delay_cnt <= delay_cnt + 14'b1;
					end
				end

				ST_REFRESH: begin
				  if (delay_cnt >= WAIT_TRFC) begin
						delay_cnt                  <= 14'd0;
						state                      <= ST_IDLE; 
				  end else begin
						delay_cnt                  <= delay_cnt + 1'b1;
				  end
					{SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_NOP;
					S_A                            <= 12'd0;
				end

				default: begin
					state     <= ST_INIT_NOP;
					delay_cnt <= 14'd0;
				end
			endcase
			{SD_BS1, SD_BS0} <= addr[21:20];//??
		end
	end
endmodule

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

Не забываем, что в ПЛИС Altera есть специальные элементы быстрого вывода (могу немного не корректно в части терминологии говорить, надеюсь начинающим понятно). Что бы их задействовать нужно указать в настройках выводов опции “Fast Input Register On”, “Fast Output Enable Register On” (для двунаправленной шины данных) и “Fast Output Register On” (для выходов управления). Кроме этого, следите за сообщениями компилятора, он может игнорировать эти опции. Одна из причин, если "на входе сигнала" некая комбинационная схема.

Если в документации к вашей микросхеме нет временных диаграмм, посмотрите аналоги. Вот я нашёл у одного из аналогов хорошую диаграмму.

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

Надеюсь в этот раз обойдёмся без фиаско.

Комментарии (2)


  1. yamifa_1234
    15.07.2026 14:15

    Из-за недостатка опыта, я читал свои же данные на шине и воспринял это как работоспособность памяти.

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

    Почему не обновили код в предыдущей статье а решили написать новую?


    1. JackKatch Автор
      15.07.2026 14:15

      Почему не обновили код в предыдущей статье а решили написать новую?

      Аппаратное решение здесь другое, плюс негативный опыт это тоже опыт.

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

      С этим тоже пришлось повозится.