gateware

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 69e1beb55107e6a3733c77b3f40ea8b00bc01720
parent eb5ec9b9fcacde2638a58fc8f094a10abae850c2
Author: Brian Swetland <swetland@frotz.net>
Date:   Thu, 30 Jan 2020 22:31:39 -0800

sdram: more work in progress

- stop auto-precharging after read/write
- track which rows of which banks are open
- auto-precharge-all before refresh
- read/write immediately if bank is open to correct row
- start wiring up burst read/write ops
- spent a lot of time trying to simplify to meet timing

Diffstat:
Mhdl/sdram/sdram.sv | 333+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mhdl/sdram/sdram_glue_ecp5.sv | 2+-
Mhdl/sdram/testbench.sv | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mproject/colorlight-sdram.def | 1+
Mproject/test-sdram.def | 2+-
5 files changed, 311 insertions(+), 147 deletions(-)

diff --git a/hdl/sdram/sdram.sv b/hdl/sdram/sdram.sv @@ -4,18 +4,18 @@ `default_nettype none // tRCD (RAS# to CAS# Delay Time) -// Min clocks betwen ACTIVATE of a bank and a READ or WRITE of that bank +// Min clocks betwen ACTIVE 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 +// Min clocks between ACTIVE of one row in a bank and ACTIVE 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 +// Min time between ACTIVE of a row in one bank and the ACTIVE 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 +// Min time between PREFRESH of a bank and REFRESH or ACTIVE of it // // tWR (Write Recovery Time) // Min clocks between the last word of a write and PRECHARGE of that bank @@ -51,14 +51,18 @@ module sdram #( output wire [AWIDTH-1:0]pin_addr, input wire [XWIDTH-1:0]rd_addr, - input wire rd_ready, + input wire [3:0]rd_len, + input wire rd_req, + output reg rd_ack = 0, + output reg [DWIDTH-1:0]rd_data, - output reg rd_valid = 0, + output reg rd_rdy = 0, input wire [XWIDTH-1:0]wr_addr, input wire [DWIDTH-1:0]wr_data, - input wire wr_valid, - output reg wr_ready = 0 + input wire [3:0]wr_len, + input wire wr_req, + output reg wr_ack = 0 ); // sdram addr is wide enough for row + bank @@ -67,184 +71,295 @@ localparam AWIDTH = (ROWBITS + BANKBITS); // full addr is rowbits + bankbits + colbits wide localparam XWIDTH = (ROWBITS + BANKBITS + COLBITS); -wire [COLBITS-1:0]wr_col; +localparam BANKCOUNT = (1 << BANKBITS); + +integer i; // used by various for loops + +// split input address into bank, row, col wire [BANKBITS-1:0]wr_bank; wire [ROWBITS-1:0]wr_row; +wire [COLBITS-1:0]wr_col; assign {wr_row, wr_bank, wr_col} = wr_addr; -wire [COLBITS-1:0]rd_col; +// split input address into bank, row, col wire [BANKBITS-1:0]rd_bank; wire [ROWBITS-1:0]rd_row; +wire [COLBITS-1:0]rd_col; 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 } }; +// sdram io address management +reg [XWIDTH-1:0]io_addr = 0; +reg [XWIDTH-1:0]io_addr_next; + +wire [COLBITS-1:0]io_col; +wire [BANKBITS-1:0]io_bank; +wire [ROWBITS-1:0]io_row; +assign {io_row, io_bank, io_col} = io_addr; +wire [COLBITS-1:0]io_col_add1 = io_col + {{COLBITS-1{1'b0}},1'b1}; reg [DWIDTH-1:0]rd_data_next; -reg rd_valid_next; -reg wr_ready_next; +reg rd_ack_next; +reg rd_rdy_next; +reg wr_ack_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; +// signals to sdram_glue module +wire ras_n; +wire cas_n; +wire we_n; +wire [AWIDTH-1:0]addr; wire [DWIDTH-1:0]data_i; +reg [DWIDTH-1:0]data_o = 0; reg data_oe = 0; +reg [DWIDTH-1:0]data_o_next; +reg data_oe_next; + +reg [2:0]cmd = 3'b111; +reg [2:0]cmd_next; + // 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; +reg [15:0]refresh = T_PWR_UP; +reg [15:0]refresh_next; +wire [15:0]refresh_sub1 = refresh - 16'd1; +wire refresh_done = refresh[15]; // 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 [4:0]count = 0; +reg [4:0]count_next; +wire [4:0]count_sub1 = count - 5'd1; +wire count_done = count[4]; -reg [2:0]cmd_next; -reg [AWIDTH-1:0]addr_next; -reg [DWIDTH-1:0]data_o_next; -reg data_oe_next; +// sdram bank state +reg bank_active[0:BANKCOUNT-1]; +reg bank_active_next[0:BANKCOUNT-1]; +reg [ROWBITS-1:0]bank_row[0:BANKCOUNT-1]; +reg [ROWBITS-1:0]bank_row_next[0:BANKCOUNT-1]; +// state machine state 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 IDLE = 4'd1; +localparam INIT0 = 4'd2; +localparam INIT1 = 4'd3; +localparam INIT2 = 4'd4; +localparam REFRESH = 4'd5; +localparam ACTIVE = 4'd6; +localparam READ = 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; +// sdram commands 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_PRECHARGE = 3'b010; // BA*=bankno, A10=ALL +localparam CMD_ACTIVE = 3'b011; // BA*=bankno, A*=ROW +localparam CMD_WRITE = 3'b100; // BA*=bankno, A10=AP, COLADDR +localparam CMD_READ = 3'b101; // BA*=bankno, A10=AP, COLADDR localparam CMD_STOP = 3'b110; localparam CMD_NOP = 3'b111; +// TODO CL2 vs CL3 configurability here and elsewhere + +reg [3:0]rd_pipe_rdy = 0; +reg [3:0]rd_pipe_bsy = 0; +reg [3:0]rd_pipe_rdy_next; +reg [3:0]rd_pipe_bsy_next; + +reg [3:0]burst = 0; +reg [3:0]burst_next; +wire [3:0]burst_sub1; +wire burst_done; +assign { burst_done, burst_sub1 } = { 1'b0, burst } - 5'd1; + +reg io_sel_a10 = 0; +reg io_sel_a10_next; +reg io_sel_row = 0; +reg io_sel_row_next; + always_comb begin state_next = state; count_next = count_done ? count : count_sub1; - refresh_next = refresh_now ? refresh : refresh_sub1; + refresh_next = refresh_done ? 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; + data_oe_next = 0; + wr_ack_next = 0; + rd_rdy_next = 0; + rd_ack_next = 0; rd_data_next = rd_data; + burst_next = burst; + io_addr_next = io_addr; + io_sel_a10_next = io_sel_a10; + io_sel_row_next = io_sel_row; + + for (i = 0; i < BANKCOUNT; i++) begin + bank_active_next[i] = bank_active[i]; + bank_row_next[i] = bank_row[i]; + end + // read pipe regs track inbound read data (rdy) + // and hold off writes (bsy) to avoid bus conflict + rd_pipe_rdy_next = { 1'b0, rd_pipe_rdy[3:1] }; + rd_pipe_bsy_next = { 1'b0, rd_pipe_bsy[3:1] }; + + if (rd_pipe_rdy[0]) begin + rd_rdy_next = 1; + rd_data_next = data_i; + end + + if (count_done) // state can only advance if counter is 0 case (state) START: begin + refresh_next = T_PWR_UP; state_next = INIT0; - count_next = T_PWR_UP - 1; end - IDLE: if (count_done) begin + IDLE: 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; + if (refresh_done) begin + // refresh counter has expired, precharge all and refresh + state_next = REFRESH; + cmd_next = CMD_PRECHARGE; + io_sel_row_next = 0; + io_sel_a10_next = 1; // ALL BANKS + count_next = T_RP - 2; + end else if (rd_req && !rd_ack) begin + io_addr_next = rd_addr; + if (!bank_active[rd_bank] || (bank_row[rd_bank] != rd_row)) begin + state_next = ACTIVE; + cmd_next = CMD_PRECHARGE; + io_sel_row_next = 0; // column addr + io_sel_a10_next = 0; // one bank only + count_next = T_RP - 2; + end else begin + cmd_next = CMD_READ; + io_sel_row_next = 0; // column addr + io_sel_a10_next = 0; // no auto precharge + rd_pipe_rdy_next = { 1'b1, rd_pipe_rdy[3:1] }; + rd_pipe_bsy_next = 4'b1111; + rd_ack_next = 1; + if (rd_len != 4'd0) begin + state_next = READ; + burst_next = rd_len; + end + end + end else if (wr_req && !rd_pipe_bsy[0] && !wr_ack) begin + io_addr_next = wr_addr; + if (!bank_active[wr_bank] || (bank_row[wr_bank] != wr_row)) begin + state_next = ACTIVE; + // precharge one bank (a10=0) + cmd_next = CMD_PRECHARGE; + io_sel_row_next = 0; // column addr + io_sel_a10_next = 0; // one bank only + count_next = T_RP - 2; + end else begin + cmd_next = CMD_WRITE; + io_sel_row_next = 0; // column addr + io_sel_a10_next = 0; // no auto precharge + data_o_next = wr_data; + data_oe_next = 1; + wr_ack_next = 1; // 1cyc delay eww + if (wr_len != 4'd0) begin + burst_next = wr_len; + state_next = WRITE; + end + end end end - WACTIVE: if (count_done) begin + ACTIVE: 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; + count_next = T_RCD - 2; + cmd_next = CMD_ACTIVE; + io_sel_row_next = 1; // row address + bank_active_next[io_bank] = 1; + bank_row_next[io_bank] = io_row; end - RACTIVE: if (count_done) begin - state_next = READ; + READ: begin + if (burst_done) begin + state_next = IDLE; + end else begin + burst_next = burst_sub1; + end + // column addressing pre-selected from initial read + io_addr_next[COLBITS-1:0] = io_col_add1; cmd_next = CMD_READ; - count_next = T_RCD ; - addr_next = { rd_bank, x1_col, rd_col }; + rd_pipe_rdy_next = { 1'b1, rd_pipe_rdy[3:1] }; + rd_pipe_bsy_next = 4'b1111; end - READ: if (count_done) begin - state_next = IDLE; - count_next = 2; // ?? - rd_data_next = data_i; - rd_valid_next = 1; + WRITE: begin + if (burst_done) begin + state_next = IDLE; + end else begin + burst_next = burst_sub1; + end + // column addressing pre-selected from initial write + io_addr_next[COLBITS-1:0] = io_col_add1; + cmd_next = CMD_WRITE; + data_oe_next = 1; end - INIT0: if (count_done) begin + INIT0: if (refresh_done) begin state_next = INIT1; count_next = 2; cmd_next = CMD_PRECHARGE; - addr_next[10] = 1; // ALL + io_sel_row_next = 0; // column addressing + io_sel_a10_next = 1; // ALL BANKS end - INIT1: if (count_done) begin + INIT1: begin state_next = INIT2; cmd_next = CMD_SET_MODE; - count_next = T_MRD - 1; + count_next = T_MRD - 2; // r/w burst off, cas lat 3, sequential addr - addr_next = { {(AWIDTH - 10){1'b0}}, 10'b0000110000}; + io_addr_next[XWIDTH-1:COLBITS] = { {(AWIDTH - 10){1'b0}}, 10'b0000110000}; + io_sel_row_next = 1; // row addressing end - INIT2: if (count_done) begin - state_next = INIT3; + INIT2: begin + state_next = REFRESH; cmd_next = CMD_REFRESH; - count_next = T_RC - 1; + count_next = T_RC - 2; end - INIT3: if (count_done) begin - state_next = INIT4; - cmd_next = CMD_REFRESH; - count_next = T_RC - 1; - end - INIT4: if (count_done) begin + REFRESH: begin state_next = IDLE; + cmd_next = CMD_REFRESH; + count_next = T_RC - 2; refresh_next = T_RI - 1; + + // we got here after a precharge all + for (i = 0; i < BANKCOUNT; i++) + bank_active_next[i] = 0; end default: begin - state_next = START; + //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; + io_sel_a10 <= io_sel_a10_next; + io_sel_row <= io_sel_row_next; + io_addr <= io_addr_next; data_o <= data_o_next; data_oe <= data_oe_next; - wr_ready <= wr_ready_next; - rd_valid <= rd_valid_next; + wr_ack <= wr_ack_next; + rd_ack <= rd_ack_next; + rd_rdy <= rd_rdy_next; rd_data <= rd_data_next; + for (i = 0; i < BANKCOUNT; i++) begin + bank_active[i] <= bank_active_next[i]; + bank_row[i] <= bank_row_next[i]; + end + rd_pipe_rdy <= rd_pipe_rdy_next; + rd_pipe_bsy <= rd_pipe_bsy_next; end +assign { ras_n, cas_n, we_n } = cmd; + +wire [(ROWBITS-COLBITS)-1:0]io_misc = {{(ROWBITS-COLBITS)-1{1'b0}}, io_sel_a10 } << (10 - COLBITS); +wire [ROWBITS-1:0]io_low = io_sel_row ? io_row : { io_misc, io_col }; +assign addr = { io_bank, io_low }; `ifdef verilator assign pin_clk = clk; diff --git a/hdl/sdram/sdram_glue_ecp5.sv b/hdl/sdram/sdram_glue_ecp5.sv @@ -41,7 +41,7 @@ generate for (n = 0; n < DWIDTH; n++) begin BB iobuf ( .I(data_o[n]), - .T(~data_oe[n]), + .T(~data_oe), .O(data_i[n]), .B(pin_data[n]) ); diff --git a/hdl/sdram/testbench.sv b/hdl/sdram/testbench.sv @@ -31,20 +31,32 @@ reg info_e_next; reg [19:0]rd_addr = 0; wire [15:0]rd_data; -reg rd_ready = 0; -wire rd_valid; +reg rd_req = 0; +reg [3:0]rd_len = 0; +wire rd_ack; +wire rd_rdy; reg [19:0]wr_addr = 0; reg [15:0]wr_data = 0; -reg wr_valid = 0; -wire wr_ready; +reg wr_req = 0; +wire wr_ack; -reg [31:0]count = T_PWR_UP + 32; -reg [31:0]count_next; -wire [31:0]count_sub1; +reg rd_req_next; +reg wr_req_next; +reg [3:0]rd_len_next; +reg [19:0]wr_addr_next; +reg [19:0]rd_addr_next; +reg [15:0]wr_data_next; + +reg done_next; +reg error_next; + +reg [15:0]count = T_PWR_UP + 32; +reg [15:0]count_next; +wire [15:0]count_sub1; wire count_done; -assign { count_done, count_sub1 } = { 1'b0, count } - 32'd1; +assign { count_done, count_sub1 } = { 1'b0, count } - 16'd1; localparam INIT = 4'd0; localparam WRITES = 4'd1; @@ -54,32 +66,49 @@ 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; +reg number_next; +reg number_reset; +wire [31:0]number; + +xorshift32 xs( + .clk(clk), + .next(number_next), + .reset(number_reset), + .data(number) +); + +reg [15:0]cycles = 0; +reg [15:0]cycles_next; always_comb begin + number_reset = 0; + number_next = 0; state_next = state; count_next = count; - rd_ready_next = rd_ready; - wr_valid_next = wr_valid; + rd_req_next = rd_req; + wr_req_next = wr_req; wr_addr_next = wr_addr; rd_addr_next = rd_addr; + rd_len_next = rd_len; wr_data_next = wr_data; info_next = info; info_e_next = 0; done_next = 0; + error_next = 0; + cycles_next = cycles + 16'd1; + + if (cycles == 16'd5000) + error_next = 1; 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; + count_next = 32; //1000; //32; + wr_addr_next = 20'hF0; + //wr_data_next = 0; + wr_data_next= number[15:0]; + number_next = 1; + wr_req_next = 1; info_next = 16'h10FF; info_e_next = 1; end else begin @@ -87,15 +116,18 @@ always_comb begin end WRITES: if (count_done) begin state_next = READS; - count_next = 32; - rd_addr_next = 0; - rd_ready_next = 1; - wr_valid_next = 0; + number_reset = 1; + count_next = 32; //1000; //32; + rd_addr_next = 20'hF0; + rd_req_next = 1; + wr_req_next = 0; info_next = 16'h20EE; info_e_next = 1; end else begin - if (wr_ready) begin - wr_data_next = wr_data + 1; + if (wr_ack) begin + //wr_data_next = wr_data + 1; + wr_data_next = number[15:0]; + number_next = 1; wr_addr_next = wr_addr + 1; count_next = count_sub1; end @@ -103,14 +135,23 @@ always_comb begin READS: if (count_done) begin state_next = STOP; done_next = 1; - rd_ready_next = 0; info_next = 16'h20DD; info_e_next = 1; + rd_req_next = 0; end else begin - if (rd_valid) begin + if (rd_ack) begin + rd_req_next = 0; + end + if (rd_rdy) begin + rd_req_next = 1; rd_addr_next = rd_addr + 1; count_next = count_sub1; - info_next = { 8'h40, rd_data[7:0] }; + if (rd_data == number[15:0]) + info_next = { 16'h7011 }; + else + info_next = { 16'h40FF }; + //info_next = { 8'h40, rd_data[7:0] }; + number_next = 1; info_e_next = 1; end end @@ -121,15 +162,19 @@ end always_ff @(posedge clk) begin state <= state_next; - rd_ready <= rd_ready_next; - wr_valid <= wr_valid_next; + rd_req <= rd_req_next; + wr_req <= wr_req_next; rd_addr <= rd_addr_next; wr_addr <= wr_addr_next; wr_data <= wr_data_next; + rd_len <= rd_len_next; count <= count_next; - done <= done_next; info <= info_next; info_e <= info_e_next; + + cycles <= cycles_next; + done <= done_next; + error <= error_next; end sdram #( @@ -150,14 +195,17 @@ sdram #( .pin_data(sdram_data), `endif .rd_addr(rd_addr), + .rd_len(rd_len), + .rd_req(rd_req), + .rd_ack(rd_ack), .rd_data(rd_data), - .rd_ready(rd_ready), - .rd_valid(rd_valid), + .rd_rdy(rd_rdy), .wr_addr(wr_addr), .wr_data(wr_data), - .wr_valid(wr_valid), - .wr_ready(wr_ready) + .wr_len(0), + .wr_req(wr_req), + .wr_ack(wr_ack) ); endmodule diff --git a/project/colorlight-sdram.def b/project/colorlight-sdram.def @@ -5,5 +5,6 @@ PROJECT_SRCS += hdl/lattice/ecp5_pll_25_125_250.v PROJECT_SRCS += hdl/display/display.sv hdl/display/display_timing.sv PROJECT_SRCS += hdl/sdram/testbench.sv PROJECT_SRCS += hdl/sdram/sdram.sv hdl/sdram/sdram_glue_ecp5.sv +PROJECT_SRCS += hdl/xorshift.sv PROJECT_NEXTPNR_OPTS := --25k --package CABGA381 --speed 6 diff --git a/project/test-sdram.def b/project/test-sdram.def @@ -2,6 +2,6 @@ PROJECT_TYPE := verilator-sim PROJECT_SRCS := hdl/sdram/testbench.sv -PROJECT_SRCS += hdl/sdram/sdram.sv +PROJECT_SRCS += hdl/sdram/sdram.sv hdl/xorshift.sv PROJECT_VOPTS := -CFLAGS -DSDRAM