В прошлой статье, к моему сожалению, код оказался не рабочий. Из-за недостатка опыта, я читал свои же данные на шине и воспринял это как работоспособность памяти. Исправляю это недоразумение в этой статье. Опытным разработчикам читать только из спортивного интереса :-).
Немного изменим аппаратную часть. Итак постановка задачи. Будем передавать по последовательному каналу (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; // Это общий сброс. Кроме кнопки, влияет состояние 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” (для выходов управления). Кроме этого, следите за сообщениями компилятора, он может игнорировать эти опции. Одна из причин, если "на входе сигнала" некая комбинационная схема.
Если в документации к вашей микросхеме нет временных диаграмм, посмотрите аналоги. Вот я нашёл у одного из аналогов хорошую диаграмму.

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