В прошлой статье, к моему сожалению, код оказался не рабочий. Из-за недостатка опыта, я читал свои же данные на шине и воспринял это как работоспособность памяти. Исправляю это недоразумение в этой статье. Опытным разработчикам читать только из спортивного интереса :-).
Немного изменим аппаратную часть. Итак постановка задачи. Будем передавать по последовательному каналу (RS232) с компьютера маркер адреса 0x80, за которым ожидаются два байта содержащие адрес. Маркер данных 0x40, за которым ожидаются два байта данных. Команда 0x20 запись в память, команда 0x10 чтение из памяти (данные не требуются). Кроме этого будем отображать на семи сегментном индикаторе принятые адрес, данные (как переданные, так и прочитанные), и команды. Далее схемы. А так же имейте ввиду, так как я начинающий и опыта у меня мало, пожалуйста внимательно проверяйте всё, если будете повторять.
Данная задача довольно простая, не смотря на бытующее мнение о её сложности. Я имею ввиду задачу проверки чтения записи в память. Так как если вам необходим «серьёзный» контроллер, то это будет немного сложнее (есть всякие режимы обмена, например прервать чтение для записи и т.п.). Так же хочу напомнить, что это не руководство, а просто небольшой практический пример. Читайте внимательно теорию и документацию на микросхему.
Не смотря на вышесказанное, у меня возникли трудности в реализации этой задачи. Во первых, не зря говорят, что при освоении 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;// Это общий сброс. Кроме кнопки, влияет состояние PLLwire 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;// Сигналы для передатчика UARTreg 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; // Блокируют приём, до записи в SDRAMreg 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;localparamSTATE_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) жду один тактendassign 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) beginif (reset) beginstartTx <= 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 beginstartTx <= 1'b0; cmd_write <= 1'd0; cmd_read <= 1'd0;case (State)STATE_IDLE: begin // Ждём адрес, данные и командыif (!Empty) beginif (dataRx == 8'h80) beginif (!fAReady) beginState <= STATE_WAIT_ADDR;nechet <= 1'b0;endendif (dataRx == 8'h40) beginif (!fDReady) beginState <= STATE_WAIT_DATA;nechet <= 1'b0;endendif (dataRx == 8'h20) begin// Пришла команда, если всё готово, выполняемif (fAReady && fDReady) beginfCmdWrite <= 1'd1;State <= STATE_ACK;endend// иначе игнорируемif (dataRx == 8'h10) begin// Пришла команда, если всё готово, выполняемif (fAReady) beginfCmdRead <= 1'd1;State <= STATE_ACK;endend// иначе игнорируемdisplay_value[7:0] <= dataRx;rdRx_en <= 1'd1;//Прочитал!Переставь указатель буфераendendSTATE_WAIT_ADDR: begin// Ждём два байта адреса, первый младшийif (rdRx_en == 1'd1) beginrdRx_en <= 1'd0;end else beginif (!Empty) beginif (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;endnechet <= ~nechet;rdRx_en <= 1'd1;endendendSTATE_WAIT_DATA: begin// Ждём два байта данных, первый младшийif (rdRx_en == 1'd1) beginrdRx_en <= 1'd0;end else beginif (!Empty) beginif (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;endnechet <= ~nechet;rdRx_en <= 1'd1;endendendSTATE_ACK: begin// Ответ на приёмrdRx_en <= 1'd0;if (fCmdWrite || fCmdRead) begindataTx <= 8'h5A;State <= STATE_WAIT;end else begindataTx <= 8'hA5;State <= STATE_IDLE;endstartTx <= 1'b1;endSTATE_WAIT: beginif (SD_Ready && fAReady) beginif (fCmdWrite && fDReady) beginState <= STATE_WRITE;cmd_write <= 1'd1;pause <= 3'd0;endif (fCmdRead) begincmd_read <= 1'd1;State <= STATE_READ;pause <= 3'd0;endendif (fCmdWrite) beginif (~fAReady || ~fDReady) beginState <= STATE_IDLE;fCmdWrite <= 1'd0;endendif (fCmdRead) beginif (~fAReady) beginState <= STATE_IDLE;fCmdRead <= 1'd0;endendendSTATE_READ: begin/* Начинается Activete. pause == 01 такт tRCD подаём команду read pause == 12 такта CL pause == 3 читаем1 такт tRP предзаряд pause == 4 */if (pause >= RPause) begindata_from_ram <= dq_in_ioe; // Читаю данныеState <= STATE_DISPLAY;fAReady <= 1'd0;fCmdRead <= 1'd0;// Чтение завершеноend else beginpause <= pause + 3'd1;endendSTATE_DISPLAY: begin// Ещё такт в счёт prechargedisplay_value <= data_from_ram;State <= STATE_IDLE;endSTATE_WRITE: begin/* Данные на шину выставляем по состояниюcmd_write, через такт будет activete, ещё через один writeв следующем такте, (activete) данные должны быть на шине.1 такт задержки до activete,1 такт от активации до команды,2 такта время восстановления после записи,1 такт время предзаряда. Итого 5-ть тактов,после чего можно подавать другие комманды,если будет задержка на один такт при переходе в Idleне страшно!*/if (pause == 3'd0) begincmd_write <= 1'd0;endState <= STATE_WRITE_WAIT;endSTATE_WRITE_WAIT: beginif (pause >= WPause) beginState <= STATE_IDLE;fAReady <= 1'd0;fDReady <= 1'd0;fCmdWrite <= 1'd0; // Запись завершенаend else beginpause <= pause + 3'd1;endenddefault: State <= STATE_IDLE;endcaseendend//------------------- 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)beginXQ <= !X;/* &CTQ - все биты счётчика равны единице, т.е. максимум, даёт единицу |CTQ - все биты счётчика равны нулю, т.е. минимум, даёт ноль*/if (XQ & ~&CTQ) CTQ <= CTQ + 1'd1;else if (!XQ & |CTQ) CTQ <= CTQ - 1'd1;// счётчик досчитал до максимум, запоминаем 1if (&CTQ) RSTrigQ <= 1'd1;// счётчик досчитал до минимума, запоминаем 0else if (~|CTQ) RSTrigQ <= 1'd0;BQ <= RSTrigQ;TTrigQ <= FY;endendmodule
Модуль семи сегментного индикатора.
Скрытый текст
`timescale 1ns / 1psmodule 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 @(*) begincase (refresh_counter)2'b00: current_digit = {mask[0], display_value[3:0]}; // Разряд 0 п.2'b01: current_digit = {mask[1], display_value[7:4]}; // Разряд 12'b10: current_digit = {mask[2], display_value[11:8]}; // Разряд 22'b11: current_digit = {mask[3], display_value[15:12]};// Разряд 3 л.default: current_digit = {1'd0, 4'h0};endcaseend // 3. Управление анодами (Активация нужного знакоместа)// Подаем логический "0" на общий анод выбранного индикатора// (для схемы с общим анодом)always @(*) begin case (refresh_counter)2'b00: dig = 4'b1110; // Включен разряд 02'b01: dig = 4'b1101; // Включен разряд 12'b10: dig = 4'b1011; // Включен разряд 22'b11: dig = 4'b0111; // Включен разряд 3default: dig = 4'b1111; // Все выключены endcaseend // 4. Дешифратор 4-в-7 (HEX в код сегментов)// Порядок битов в segments: [6:0] -> {g, f, e, d, c, b, a}.// Логический "0" включает сегмент.always @(*) begincase (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; // Выключеноendcaseend//--------------------------------------------------------------------------------------endmodule
Код модуля приёма по UART.
Скрытый текст
`timescale 1ns / 1ps// RS232 Передатчик скорость 19200 (бит/сек), 8 bit data, без чёт/нечет// clk 50 MHzmodule 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) beginif (rst) beginQ0 <= 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) beginfifo[wr_ptr] <= shreg;wr_ptr <= wr_ptr + 3'd1;end/* Данные прочитаны (done) */if (done && ~empty) beginrd_ptr <= rd_ptr + 3'd1;endendendendmodule
Код модуля передатчика по 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 KHzmodule 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;endendmodule
Код контроллера SDRAM.
Скрытый текст
`timescale 1ns / 1psmodule CntrlSDRAM(outputreg SD_CS,outputreg SD_RAS,outputreg SD_CAS,outputreg SD_WE,outputreg SD_BS0,outputreg SD_BS1,outputreg [11:0] S_A,// Адрес на шинуoutputreg ready,// 1 - готов к приёму командinputcmd_read,inputcmd_write,/* bank[21:20], row[19:8], col[7:0] */input[21:0] addr,// Адрес от пользователяinputclk,inputrst);// Состояния конечного автомата 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) beginif (rst) begininitOk <= 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 beginif (initOk) beginif (refresh_timer >= REFRESH_INTERVAL) beginrefresh_timer <= 10'd0;refresh_req <= 1'b1; end else beginrefresh_timer <= refresh_timer + 1'b1;endendcase (state)// ----------- Стадия инициализации -----------ST_INIT_NOP: begin {SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_INHIBIT; if (delay_cnt >= WAIT_200US) beginstate <= 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 begindelay_cnt <= delay_cnt + 1'b1; endendST_INIT_PRE: begin if (delay_cnt >= WAIT_TRP) beginstate <= 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; endendST_INIT_REF: begin if (delay_cnt >= WAIT_TRFC) begindelay_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; endendST_INIT_MRS: begin if (delay_cnt >= WAIT_TMRD) beginstate <= ST_IDLE;delay_cnt <= 14'd0;initOk <= 1'd1; end else begindelay_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) beginstate <= 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 beginif (cmd_read || cmd_write) beginstate <= 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) beginfR <= 1'd1; // Поэтому заменим на этот флагendif (cmd_write) beginfW <= 1'd1;end endendST_ACTIVATE: begin if (delay_cnt >= (WAIT_TRCD - 1)) begindelay_cnt <= 14'd0;{SD_BS1, SD_BS0} <= addr[21:20];// A10=1 (Авто-пречардж)S_A <= {4'b0100, addr[7:0]};if (fW) beginstate <= ST_WRITE;{SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_WRITE;fW <= 1'd0;endif (fR) beginstate <= 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; endendST_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 уже был = 2delay_cnt <= 14'd0; state <= ST_IDLE;{SD_BS1, SD_BS0} <= 2'd0;S_A <= 12'd0;end else begindelay_cnt <= delay_cnt + 14'b1;endendST_READ: begin{SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_NOP;//2CL + 1 предзаряд-1 задержка=2if (delay_cnt >= 14'd2) begindelay_cnt <= 14'd0; state <= ST_IDLE;{SD_BS1, SD_BS0} <= 2'd0;S_A <= 12'd0;end else begindelay_cnt <= delay_cnt + 14'b1;endendST_REFRESH: begin if (delay_cnt >= WAIT_TRFC) begindelay_cnt <= 14'd0;state <= ST_IDLE; end else begindelay_cnt <= delay_cnt + 1'b1; end{SD_CS, SD_RAS, SD_CAS, SD_WE} <= CMD_NOP;S_A <= 12'd0;enddefault: beginstate <= ST_INIT_NOP;delay_cnt <= 14'd0;endendcase{SD_BS1, SD_BS0} <= addr[21:20];//??endendendmodule
Повторюсь про тактовый сигнал микросхемы памяти, необходим небольшой сдвиг по фазе, что бы сигнал на микросхему запаздывал. То есть в момент когда на микросхему памяти придёт нарастающий фронт синхросигнала, все сигналы управления должны уже установится. Обратите внимание каким образом, в коде, этот сигнал выдаётся наружу.
Не забываем, что в ПЛИС Altera есть специальные элементы быстрого вывода (могу немного не корректно в части терминологии говорить, надеюсь начинающим понятно). Что бы их задействовать нужно указать в настройках выводов опции “Fast Input Register On”, “Fast Output Enable Register On” (для двунаправленной шины данных) и “Fast Output Register On” (для выходов управления). Кроме этого, следите за сообщениями компилятора, он может игнорировать эти опции. Одна из причин, если «на входе сигнала» некая комбинационная схема.
Если в документации к вашей микросхеме нет временных диаграмм, посмотрите аналоги. Вот я нашёл у одного из аналогов хорошую диаграмму.
Надеюсь в этот раз обойдёмся без фиаско.
ссылка на оригинал статьи https://habr.com/ru/articles/1059342/