Здравствуйте. Разбирался с SDRAM контроллером представленным здесь
http://fpga4fun.com/SDRAM2.html (http://fpga4fun.com/files/SDRAM_ctrl.zip) и возникли некоторые вопросы по коду.
Код на Verilog с моими комментариями.
Код
module sdram_controller(
input clk, /* Тактирование */
/* Чтение */
input RdReq, /* Запрос на чтение */
output RdGnt, /* Запрос на чтение одобрен */
input [19:0] RdAddr, /* Адрес. Шина на 20 сигналов */
output reg [15:0] RdData, /* Прочитанные данные. Шина 16 сигналов */
output RdDataValid, /* Достовреность прочитанных данных. Выставляется в 1, если достоверны */
/* Запись */
input WrReq, /* Запрос на запись */
output WrGnt, /* Запрос на запись одобрен */
input [19:0] WrAddr, /* Адрес. Шина на 20 сигналов */
input [15:0] WrData, /* Записываемые данные. Шина 16 сигналов */
/* Управление SDRAM */
output SDRAM_CKE, /* Clock. Тактирование */
output SDRAM_WEn, /* Enable. Включение */
output SDRAM_RASn, /* Row address strobe. */
output SDRAM_CASn, /* Column Access strobe */
output reg [10:0] SDRAM_A, /* Address */
output reg [0:0] SDRAM_BA, /* Memory Bank */
output reg [1:0] SDRAM_DQM = 2'b11, /* Data mask */
inout [15:0] SDRAM_DQ
);
/* На вход SDRAM.Enable всегда подаём лог. единицу, тем самым включая SDRAM */
assign SDRAM_CKE = 1'b1;
/* Команды управления SDRAM`ом */
localparam [2:0] SDRAM_CMD_LOADMODE = 3'b000; /* Загрузка регистра управления. Сама команда подаётся по шине адреса(A) */
localparam [2:0] SDRAM_CMD_REFRESH = 3'b001; /* Автоматическая регенерация. Для поддержания заряда конденсаторов */
localparam [2:0] SDRAM_CMD_PRECHARGE = 3'b010; /* Деактивация. Деактивация открытого ряда в банке */
localparam [2:0] SDRAM_CMD_ACTIVE = 3'b011; /* Активация. Активировать ряд в отдельном банке. По шине адреса(A) задаётся ряд, по шине банка(BA) банк */
localparam [2:0] SDRAM_CMD_WRITE = 3'b100; /* Запись. Записывает данные в активный ряд */
localparam [2:0] SDRAM_CMD_READ = 3'b101; /* Чтение. Читает данные из активного ряда */
localparam [2:0] SDRAM_CMD_NOP = 3'b111; /* Нет операции. Предохраняет от выполнения нежелательных команд во время ожидания */
/* По-умолчанию в регистре SDRAM_CMD будет висеть команда NOP */
reg [2:0] SDRAM_CMD = SDRAM_CMD_NOP;
/* Каждому управляещему выходу(пину) назначается свой сигнал из регистра SDRAM_CMD.
При изменении SDRAM_CMD соотествующие выхода будут меняться. */
assign {SDRAM_RASn, SDRAM_CASn, SDRAM_WEn} = SDRAM_CMD;
/* Установка приоритета командам чтение/запись */
wire read_now = RdReq; /* Приоритет у команды чтения */
wire write_now = ~RdReq & WrReq; /* Если нет команды чтения и есть запрос на запись, то разрешаем запись */
/* Регистр статуса. Для организации state-machine */
reg [1:0] state = 0;
/* ?? BEGIN */
reg ReadSelected = 0;
always @(posedge clk)
if (state == 2'h0)
ReadSelected <= read_now;
wire WriteSelected = ~ReadSelected;
wire ReadCycle = (state == 2'h0) ? read_now : ReadSelected;
wire [19:0] Addr = ReadCycle ? RdAddr : WrAddr;
reg [19:0] AddrR = 0;
always @(posedge clk)
AddrR <= Addr;
/* ?? END */
/* Сравниваем строку и банк текущего и предыдущего чтений на предмет изменений */
wire SameRowAndBank = (Addr[19:8] == AddrR[19:8]);
/* Выставляем флаг "Чтение разрешено" если
1) Статус "Ожидание" и есть запрос на чтение
2) Статус "Активирован" и есть выбранная строка блока, запрос на чтение, выборана та же строка что и на предыдущем чтении */
assign RdGnt = (state == 2'h0 & read_now) | (state == 2'h1 & ReadSelected & RdReq & SameRowAndBank);
/* Аналогично чтению */
assign WrGnt = (state == 2'h0 & write_now) | (state == 2'h1 & WriteSelected & WrReq & SameRowAndBank);
always @(posedge clk)
case(state)
/* Статус "Ожидание" */
2'h0: begin
/* Есть запрос на чтение/запись */
if(RdReq | WrReq) begin
SDRAM_CMD <= SDRAM_CMD_ACTIVE; /* Команда "Активирован" */
SDRAM_BA <= Addr[19]; /* Банк памяти */
SDRAM_A <= Addr[18:8]; /* Строка */
SDRAM_DQM <= 2'b11; /* Выставляем в 11, чтобы не мешало чтению/записи. */
/* Меняем статус на "Активирован" */
state <= 2'h1;
end
/* Иначе тупим дальше */
else
begin
SDRAM_CMD <= SDRAM_CMD_NOP;
SDRAM_BA <= 0;
SDRAM_A <= 0;
SDRAM_DQM <= 2'b11;
/* Остаёмся в режиме "Ожидание" */
state <= 2'h0;
end
end
/* Статус "Активирован" */
2'h1: begin
SDRAM_CMD <= ReadSelected ? SDRAM_CMD_READ : SDRAM_CMD_WRITE; /* Выставляем команду чтения или записи */
SDRAM_BA <= AddrR[19]; /* Банк памяти */
SDRAM_A[9:0] <= {2'b00, AddrR[7:0]}; /* Колонка */
SDRAM_A[10] <= 1'b0; /* Отключаем автоматическую деактивацию */
SDRAM_DQM <= 2'b00; /* */
/* Если есть запрос на чтение или запись, то остаёмся в режиме "Активирован", иначе переходим в "Деактивация" */
state <= (ReadSelected ? RdReq : WrReq) & SameRowAndBank ? 2'h1 : 2'h2;
end
/* Статус "Деактивация" */
2'h2: begin
SDRAM_CMD <= SDRAM_CMD_PRECHARGE; /* Команда деактивации */
SDRAM_BA <= 0;
SDRAM_A <= 11'b100_0000_0000; /* Деактивация всех банков */
SDRAM_DQM <= 2'b11;
state <= 2'h0;
end
/* Статус "Нет операции" */
2'h3: begin
SDRAM_CMD <= SDRAM_CMD_NOP;
SDRAM_BA <= 0;
SDRAM_A <= 0;
SDRAM_DQM <= 2'b11;
state <= 2'h0;
end
endcase
/* ?? BEGIN */
localparam trl = 4; // total read latency is the SDRAM CAS-latency (two) plus the SDRAM controller induced latency (two)
reg [trl-1:0] RdDataValidPipe;
always @(posedge clk)
RdDataValidPipe <= {RdDataValidPipe[trl-2:0], state == 2'h1 & ReadSelected};
assign RdDataValid = RdDataValidPipe[trl-1];
/* ?? END */
/* Выводим на шину RdData данные от SDRAM по фронту клока */
always @(posedge clk)
RdData <= SDRAM_DQ;
/* Сигнал(флаг) - команда записи
Определяется по состоянию "Активирован" и признаку "запрос на запись" */
reg SDRAM_DQ_OE = 1'b0;
always @(posedge clk)
SDRAM_DQ_OE <= (state == 2'h1) & WriteSelected;
/* Устранение метастабильности на входе WrData (?) */
reg [15:0] WrData1 = 0;
reg [15:0] WrData2 = 0;
always @(posedge clk)
WrData1 <= WrData;
always @(posedge clk)
WrData2 <= WrData1;
/* Если команда записи, то выставляем на шину SDRAM.DQ данные WrData2 */
assign SDRAM_DQ = SDRAM_DQ_OE ? WrData2 : 16'hZZZZ;
endmodule
Построчно я понимаю что происходит, но связать в общую схему некоторые куски кода у меня не получается.
Непонятые мною моменты(сначала как я это понял, затем вопросы):
1) Тут мы по фронту клока, находясь в состоянии "Ожидание", заводим сигнал "Запрос на чтение" на регистр ReadSelected. Так понимаю это нужно, чтобы зафиксировать вход, устранить "дрыганье"?
Код
reg ReadSelected = 0;
always @(posedge clk)
if (state == 2'h0)
ReadSelected <= read_now;
wire WriteSelected = ~ReadSelected;
2) Если мы в состоянии "Ожидание", то берём read_now, иначе ReadSelected
Почему мы в одном случае берём вход напрямую, а в другом из регистра ReadSelected?
Почему wire называется ReadCycle, т.е. какой цикл тут имеется ввиду?
Код
wire ReadCycle = (state == 2'h0) ? read_now : ReadSelected;
3) В state-machine используется и ReadSelected и RdReq одновременно и я не понимаю зачем. Т.е. для каких целей служит ReadSelected? Он сигнализирует о том, что команда чтения выполнялась перед этим?
4) Устанавливаем сигнал "Достоверность прочитанных данных" спустя 4 такта. Два такта на работу самой SDRAM и два на работу SDRAM контроллера. Можете объяснить как тут происходит отсчёт четырёх тактов за счёт объединения сигналов через {}?
Код
localparam trl = 4; // total read latency is the SDRAM CAS-latency (two) plus the SDRAM controller induced latency (two)
reg [trl-1:0] RdDataValidPipe;
always @(posedge clk)
RdDataValidPipe <= {RdDataValidPipe[trl-2:0], state == 2'h1 & ReadSelected};
assign RdDataValid = RdDataValidPipe[trl-1];
В RTL Viewer это разворачивается в
и тут уже понятно, что есть задержка в 4 такта.