commit 98ef000c19752e52cd3742a280c5286478a024e5
parent 77a57a23f8f258c7e8096ff22403c4fded280ee8
Author: Brian Swetland <swetland@frotz.net>
Date: Tue, 28 Jan 2020 07:15:41 -0800
sdram: wip mvp sdram controller
- only operates in burst 1 mode
- is horribly inefficient
- has successfully read/written from real hardware
Diffstat:
4 files changed, 503 insertions(+), 0 deletions(-)
diff --git a/hdl/sdram/sdram.sv b/hdl/sdram/sdram.sv
@@ -0,0 +1,281 @@
+// Copyright 2020, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+`default_nettype none
+
+// tRCD (RAS# to CAS# Delay Time)
+// Min clocks betwen ACTIVATE of a bank and a READ or WRITE of that bank
+//
+// tRC (RAS# Cycle Time)
+// Min clocks between ACTIVATE of one row in a bank and ACTIVATE of a
+// different row in the /same/ bank. PRECHARGE must happen in between.
+//
+// tRRD (Row Activate to Row Activate Delay)
+// Min time between ACTIVATE of a row in one bank and the ACTIVATE of
+// a row in a /different/ bank.
+//
+// tRP (Precharge to Refresh/Activate)
+// Min time between PREFRESH of a bank and REFRESH or ACTIVATE of it
+//
+// tWR (Write Recovery Time)
+// Min clocks between the last word of a write and PRECHARGE of that bank
+
+module sdram #(
+ // Memory Geometry
+ parameter BANKBITS = 1, // 2^BANKBITS banks of
+ parameter ROWBITS = 11, // 2^ROWBITS rows by
+ parameter COLBITS = 8, // 2^COLBITS columns of
+ parameter DWIDTH = 16, // DWIDTH bit wide words
+
+ // Memory Timing
+ parameter T_RI = 1900, // Refresh Interval
+ parameter T_RC = 8, // RAS# Cycle Time
+ parameter T_RCD = 3, // RAS# CAS# Delay
+ parameter T_RRD = 3, // Row Row Delay
+ parameter T_RP = 3, // Precharge to Refresh/Activate
+ parameter T_WR = 2, // Write Recovery TimeA
+ parameter T_MRD = 3, // Mode Register Delay
+ parameter T_PWR_UP = 25000 // Power on delay
+ ) (
+ input wire clk,
+ output wire pin_clk,
+ output wire pin_ras_n,
+ output wire pin_cas_n,
+ output wire pin_we_n,
+`ifdef verilator
+ input wire [DWIDTH-1:0]pin_data_i,
+ output wire [DWIDTH-1:0]pin_data_o,
+`else
+ inout wire [DWIDTH-1:0]pin_data,
+`endif
+ output wire [AWIDTH-1:0]pin_addr,
+
+ input wire [XWIDTH-1:0]rd_addr,
+ input wire rd_ready,
+ output reg [DWIDTH-1:0]rd_data,
+ output reg rd_valid = 0,
+
+ input wire [XWIDTH-1:0]wr_addr,
+ input wire [DWIDTH-1:0]wr_data,
+ input wire wr_valid,
+ output reg wr_ready = 0
+ );
+
+// sdram addr is wide enough for row + bank
+localparam AWIDTH = (ROWBITS + BANKBITS);
+
+// full addr is rowbits + bankbits + colbits wide
+localparam XWIDTH = (ROWBITS + BANKBITS + COLBITS);
+
+wire [COLBITS-1:0]wr_col;
+wire [BANKBITS-1:0]wr_bank;
+wire [ROWBITS-1:0]wr_row;
+assign {wr_row, wr_bank, wr_col} = wr_addr;
+
+wire [COLBITS-1:0]rd_col;
+wire [BANKBITS-1:0]rd_bank;
+wire [ROWBITS-1:0]rd_row;
+assign {rd_row, rd_bank, rd_col} = rd_addr;
+
+// high bits for read/write command addresses
+localparam x1_col = { ROWBITS - COLBITS { 1'b1 } };
+localparam x0_col = { ROWBITS - COLBITS { 1'b0 } };
+
+reg [DWIDTH-1:0]rd_data_next;
+reg rd_valid_next;
+reg wr_ready_next;
+
+reg ras_n = 1;
+reg cas_n = 1;
+reg we_n = 1;
+reg [AWIDTH-1:0]addr = 0;
+reg [DWIDTH-1:0]data_o = 0;
+wire [DWIDTH-1:0]data_i;
+reg data_oe = 0;
+
+// next refresh down counter
+reg [11:0]refresh = 0;
+reg [11:0]refresh_next;
+wire [11:0]refresh_sub1;
+wire refresh_now;
+assign { refresh_now, refresh_sub1 } = { 1'b0, refresh } - 13'd1;
+
+// general purpose down counter
+reg [15:0]count = 0;
+reg [15:0]count_next;
+wire [15:0]count_sub1;
+wire count_done;
+assign { count_done, count_sub1 } = { 1'b0, count } - 17'd1;
+
+reg [2:0]cmd_next;
+reg [AWIDTH-1:0]addr_next;
+reg [DWIDTH-1:0]data_o_next;
+reg data_oe_next;
+
+localparam START = 4'd0;
+localparam INIT0 = 4'd1;
+localparam INIT1 = 4'd2;
+localparam INIT2 = 4'd3;
+localparam INIT3 = 4'd4;
+localparam INIT4 = 4'd5;
+localparam IDLE = 4'd6;
+localparam WACTIVE = 4'd7;
+localparam WRITE = 4'd8;
+localparam RACTIVE = 4'd9;
+localparam READ = 4'd10;
+localparam RCAP = 4'd11;
+
+reg [3:0]state = START;
+reg [3:0]state_next;
+
+localparam CMD_SET_MODE = 3'b000; // A0-9 mode, A10+ SBZ
+localparam CMD_REFRESH = 3'b001;
+localparam CMD_PRECHARGE = 3'b010; // A10=all, BA*=bankno
+localparam CMD_ACTIVATE = 3'b011; // BA*=bankno
+localparam CMD_WRITE = 3'b100;
+localparam CMD_READ = 3'b101;
+localparam CMD_STOP = 3'b110;
+localparam CMD_NOP = 3'b111;
+
+always_comb begin
+ state_next = state;
+ count_next = count_done ? count : count_sub1;
+ refresh_next = refresh_now ? refresh : refresh_sub1;
+ cmd_next = CMD_NOP;
+ addr_next = addr;
+ data_o_next = data_o;
+ data_oe_next = data_oe;
+ wr_ready_next = 0;
+ rd_valid_next = 0;
+ rd_data_next = rd_data;
+
+ case (state)
+ START: begin
+ state_next = INIT0;
+ count_next = T_PWR_UP - 1;
+ end
+ IDLE: if (count_done) begin
+ data_oe_next = 0;
+ if (refresh_now) begin
+ cmd_next = CMD_REFRESH;
+ refresh_next = T_RI - 1;
+ count_next = T_RC - 1;
+ end else if (rd_ready) begin
+ state_next = RACTIVE;
+ count_next = T_RCD - 1;
+ cmd_next = CMD_ACTIVATE;
+ addr_next = { rd_bank, rd_row };
+ end else if (wr_valid) begin
+ state_next = WACTIVE;
+ count_next = T_RCD - 1;
+ cmd_next = CMD_ACTIVATE;
+ addr_next = { wr_bank, wr_row };
+ data_oe_next = 1;
+ end
+ end
+ WACTIVE: if (count_done) begin
+ state_next = IDLE;
+ cmd_next = CMD_WRITE;
+ count_next = T_WR + T_RP - 1;
+ addr_next = { wr_bank, x1_col, wr_col };
+ data_o_next = wr_data;
+ wr_ready_next = 1;
+ end
+ RACTIVE: if (count_done) begin
+ state_next = READ;
+ cmd_next = CMD_READ;
+ count_next = T_RCD ;
+ addr_next = { rd_bank, x1_col, rd_col };
+ end
+ READ: if (count_done) begin
+ state_next = IDLE;
+ count_next = 2; // ??
+ rd_data_next = data_i;
+ rd_valid_next = 1;
+ end
+ INIT0: if (count_done) begin
+ state_next = INIT1;
+ count_next = 2;
+ cmd_next = CMD_PRECHARGE;
+ addr_next[10] = 1; // ALL
+ end
+ INIT1: if (count_done) begin
+ state_next = INIT2;
+ cmd_next = CMD_SET_MODE;
+ count_next = T_MRD - 1;
+ // r/w burst off, cas lat 3, sequential addr
+ addr_next = { {(AWIDTH - 10){1'b0}}, 10'b0000110000};
+ end
+ INIT2: if (count_done) begin
+ state_next = INIT3;
+ cmd_next = CMD_REFRESH;
+ count_next = T_RC - 1;
+ end
+ INIT3: if (count_done) begin
+ state_next = INIT4;
+ cmd_next = CMD_REFRESH;
+ count_next = T_RC - 1;
+ end
+ INIT4: if (count_done) begin
+ state_next = IDLE;
+ refresh_next = T_RI - 1;
+ end
+ default: begin
+ state_next = START;
+ end
+ endcase
+end
+
+// debug
+reg [2:0]cmd = 3'b111;
+
+always_ff @(posedge clk) begin
+ state <= state_next;
+ count <= count_next;
+ refresh <= refresh_next;
+ ras_n <= cmd_next[2];
+ cas_n <= cmd_next[1];
+ we_n <= cmd_next[0];
+ cmd <= cmd_next;
+ addr <= addr_next;
+ data_o <= data_o_next;
+ data_oe <= data_oe_next;
+ wr_ready <= wr_ready_next;
+ rd_valid <= rd_valid_next;
+ rd_data <= rd_data_next;
+end
+
+
+`ifdef verilator
+assign pin_clk = clk;
+assign pin_ras_n = ras_n;
+assign pin_cas_n = cas_n;
+assign pin_we_n = we_n;
+assign pin_addr = addr;
+assign pin_data_o = data_o;
+assign data_i = pin_data_i;
+
+`else
+sdram_glue #(
+ .AWIDTH(AWIDTH),
+ .DWIDTH(DWIDTH)
+ ) glue (
+ .clk(clk),
+ .pin_clk(pin_clk),
+ .pin_ras_n(pin_ras_n),
+ .pin_cas_n(pin_cas_n),
+ .pin_we_n(pin_we_n),
+ .pin_addr(pin_addr),
+ .pin_data(pin_data),
+ .ras_n(ras_n),
+ .cas_n(cas_n),
+ .we_n(we_n),
+ .addr(addr),
+ .data_i(data_i),
+ .data_o(data_o),
+ .data_oe(data_oe)
+);
+`endif
+
+endmodule
+
diff --git a/hdl/sdram/sdram_glue_ecp5.sv b/hdl/sdram/sdram_glue_ecp5.sv
@@ -0,0 +1,52 @@
+// Copyright 2020, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+`default_nettype none
+
+module sdram_glue #(
+ parameter AWIDTH = 12,
+ parameter DWIDTH = 16
+ ) (
+ input wire clk,
+ output wire pin_clk,
+ output wire pin_ras_n,
+ output wire pin_cas_n,
+ output wire pin_we_n,
+ output wire [AWIDTH-1:0]pin_addr,
+ inout wire [DWIDTH-1:0]pin_data,
+ input wire ras_n,
+ input wire cas_n,
+ input wire we_n,
+ input wire [AWIDTH-1:0]addr,
+ input wire [DWIDTH-1:0]data_i,
+ output wire [DWIDTH-1:0]data_o,
+ output wire data_oe
+);
+
+assign pin_ras_n = ras_n;
+assign pin_cas_n = cas_n;
+assign pin_we_n = we_n;
+assign pin_addr = addr;
+
+ODDRX1F clock_ddr (
+ .Q(pin_clk),
+ .SCLK(clk),
+ .RST(0),
+ .D0(0),
+ .D1(1)
+);
+
+genvar n;
+generate
+for (n = 0; n < DWIDTH; n++) begin
+ BB iobuf (
+ .I(data_o[n]),
+ .T(~data_oe[n]),
+ .O(data_i[n]),
+ .B(pin_data[n])
+ );
+end
+endgenerate
+
+endmodule
+
diff --git a/hdl/sdram/testbench.sv b/hdl/sdram/testbench.sv
@@ -0,0 +1,163 @@
+// Copyright 2020, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+`default_nettype none
+
+module testbench #(
+ parameter T_PWR_UP = 3,
+ parameter T_RI = 32
+ ) (
+ input clk,
+ output reg error = 0,
+ output reg done = 0,
+
+ output wire sdram_clk,
+ output wire sdram_ras_n,
+ output wire sdram_cas_n,
+ output wire sdram_we_n,
+ output wire [11:0]sdram_addr,
+`ifdef verilator
+ input wire [15:0]sdram_data_i,
+ output wire [15:0]sdram_data_o,
+`else
+ inout wire [15:0]sdram_data,
+`endif
+ output reg [15:0]info = 0,
+ output reg info_e = 0
+);
+
+reg [15:0]info_next;
+reg info_e_next;
+
+reg [19:0]rd_addr = 0;
+wire [15:0]rd_data;
+reg rd_ready = 0;
+wire rd_valid;
+
+reg [19:0]wr_addr = 0;
+reg [15:0]wr_data = 0;
+reg wr_valid = 0;
+wire wr_ready;
+
+reg [31:0]count = T_PWR_UP + 32;
+reg [31:0]count_next;
+wire [31:0]count_sub1;
+wire count_done;
+
+assign { count_done, count_sub1 } = { 1'b0, count } - 32'd1;
+
+localparam INIT = 4'd0;
+localparam WRITES = 4'd1;
+localparam READS = 4'd2;
+localparam STOP = 4'd3;
+
+reg [3:0]state = INIT;
+reg [3:0]state_next;
+
+reg rd_ready_next;
+reg wr_valid_next;
+reg [19:0]wr_addr_next;
+reg [19:0]rd_addr_next;
+reg [15:0]wr_data_next;
+reg done_next;
+
+always_comb begin
+ state_next = state;
+ count_next = count;
+ rd_ready_next = rd_ready;
+ wr_valid_next = wr_valid;
+ wr_addr_next = wr_addr;
+ rd_addr_next = rd_addr;
+ wr_data_next = wr_data;
+ info_next = info;
+ info_e_next = 0;
+ done_next = 0;
+
+ case (state)
+ INIT: if (count_done) begin
+ state_next = WRITES;
+ count_next = 32;
+ wr_addr_next = 0;
+ wr_data_next = 0;
+ wr_valid_next = 1;
+ info_next = 16'h10FF;
+ info_e_next = 1;
+ end else begin
+ count_next = count_sub1;
+ end
+ WRITES: if (count_done) begin
+ state_next = READS;
+ count_next = 32;
+ rd_addr_next = 0;
+ rd_ready_next = 1;
+ wr_valid_next = 0;
+ info_next = 16'h20EE;
+ info_e_next = 1;
+ end else begin
+ if (wr_ready) begin
+ wr_data_next = wr_data + 1;
+ wr_addr_next = wr_addr + 1;
+ count_next = count_sub1;
+ end
+ end
+ READS: if (count_done) begin
+ state_next = STOP;
+ done_next = 1;
+ rd_ready_next = 0;
+ info_next = 16'h20DD;
+ info_e_next = 1;
+ end else begin
+ if (rd_valid) begin
+ rd_addr_next = rd_addr + 1;
+ count_next = count_sub1;
+ info_next = { 8'h40, rd_data[7:0] };
+ info_e_next = 1;
+ end
+ end
+ STOP: state_next = STOP;
+ default: state_next = INIT;
+ endcase
+end
+
+always_ff @(posedge clk) begin
+ state <= state_next;
+ rd_ready <= rd_ready_next;
+ wr_valid <= wr_valid_next;
+ rd_addr <= rd_addr_next;
+ wr_addr <= wr_addr_next;
+ wr_data <= wr_data_next;
+ count <= count_next;
+ done <= done_next;
+ info <= info_next;
+ info_e <= info_e_next;
+end
+
+sdram #(
+ .T_PWR_UP(T_PWR_UP),
+ .T_RI(T_RI)
+ ) sdram0 (
+ .clk(clk),
+
+ .pin_clk(sdram_clk),
+ .pin_ras_n(sdram_ras_n),
+ .pin_cas_n(sdram_cas_n),
+ .pin_we_n(sdram_we_n),
+ .pin_addr(sdram_addr),
+`ifdef verilator
+ .pin_data_i(sdram_data_i),
+ .pin_data_o(sdram_data_o),
+`else
+ .pin_data(sdram_data),
+`endif
+ .rd_addr(rd_addr),
+ .rd_data(rd_data),
+ .rd_ready(rd_ready),
+ .rd_valid(rd_valid),
+
+ .wr_addr(wr_addr),
+ .wr_data(wr_data),
+ .wr_valid(wr_valid),
+ .wr_ready(wr_ready)
+);
+
+endmodule
diff --git a/project/test-sdram.def b/project/test-sdram.def
@@ -0,0 +1,7 @@
+
+PROJECT_TYPE := verilator-sim
+
+PROJECT_SRCS := hdl/sdram/testbench.sv
+PROJECT_SRCS += hdl/sdram/sdram.sv
+
+PROJECT_VOPTS := -CFLAGS -DSDRAM