Browse Source

Add sdcard unit; currently SPI based

H. Peter Anvin 3 years ago
parent
commit
65f412c5d0
9 changed files with 344 additions and 75 deletions
  1. 3 0
      .gitmodules
  2. 2 1
      fpga/max80.qsf
  3. 95 74
      fpga/max80.sv
  4. BIN
      fpga/output_files/max80.jbc
  5. BIN
      fpga/output_files/max80.jic
  6. BIN
      fpga/output_files/max80.pof
  7. BIN
      fpga/output_files/max80.sof
  8. 243 0
      fpga/sdcard.sv
  9. 1 0
      fw/fatfs

+ 3 - 0
.gitmodules

@@ -1,3 +1,6 @@
 [submodule "fw/tools/riscv-gnu-toolchain"]
 	path = tools/riscv-gnu-toolchain
 	url = https://github.com/riscv/riscv-gnu-toolchain
+[submodule "fw/fatfs"]
+	path = fw/fatfs
+	url = https://github.com/abbrev/fatfs.git

+ 2 - 1
fpga/max80.qsf

@@ -211,6 +211,7 @@ set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to altera_reserved_tdi
 set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to altera_reserved_tms
 
 set_global_assignment -name SYSTEMVERILOG_FILE tty.sv
+set_global_assignment -name SYSTEMVERILOG_FILE sdcard.sv
 set_global_assignment -name VERILOG_FILE ip/fastmem_ip.v
 set_global_assignment -name SYSTEMVERILOG_FILE fast_mem.sv
 set_global_assignment -name MIF_FILE ../fw/boot.mif
@@ -237,4 +238,4 @@ set_global_assignment -name VERILOG_FILE ip/ddufifo.v
 
 set_global_assignment -name OCP_HW_EVAL DISABLE
 set_global_assignment -name TIMING_ANALYZER_DO_REPORT_TIMING ON
-set_instance_assignment -name PARTITION_HIERARCHY root_partition -to | -section_id Top
+set_instance_assignment -name PARTITION_HIERARCHY root_partition -to | -section_id Top

+ 95 - 74
fpga/max80.sv

@@ -10,93 +10,93 @@
 
 module max80 (
 	      // Clock oscillator
-	      input 	    clock_48, // 48 MHz
+	      input	    clock_48, // 48 MHz
 
 	      // ABC-bus
-	      input 	    abc_clk, // ABC-bus 3 MHz clock
+	      input	    abc_clk, // ABC-bus 3 MHz clock
 	      input [15:0]  abc_a, // ABC address bus
 	      inout [7:0]   abc_d, // ABC data bus
-	      output 	    abc_d_oe, // Data bus output enable
-	      input 	    abc_rst_n, // ABC bus reset strobe
-	      input 	    abc_cs_n, // ABC card select strobe
+	      output	    abc_d_oe, // Data bus output enable
+	      input	    abc_rst_n, // ABC bus reset strobe
+	      input	    abc_cs_n, // ABC card select strobe
 	      input [4:0]   abc_out_n, // OUT, C1-C4 strobe
 	      input [1:0]   abc_inp_n, // INP, STATUS strobe
-	      input 	    abc_xmemfl_n, // Memory read strobe
-	      input 	    abc_xmemw800_n, // Memory write strobe (ABC800)
-	      input 	    abc_xmemw80_n, // Memory write strobe (ABC80)
-	      input 	    abc_xinpstb_n, // I/O read strobe (ABC800)
-	      input 	    abc_xoutpstb_n, // I/O write strobe (ABC80)
+	      input	    abc_xmemfl_n, // Memory read strobe
+	      input	    abc_xmemw800_n, // Memory write strobe (ABC800)
+	      input	    abc_xmemw80_n, // Memory write strobe (ABC80)
+	      input	    abc_xinpstb_n, // I/O read strobe (ABC800)
+	      input	    abc_xoutpstb_n, // I/O write strobe (ABC80)
 	      // The following are inverted versus the bus IF
 	      // the corresponding MOSFETs are installed
-	      output 	    abc_rdy_x, // RDY = WAIT#
-	      output 	    abc_resin_x, // System reset request
-	      output 	    abc_int80_x, // System INT request (ABC80)
-	      output 	    abc_int800_x, // System INT request (ABC800)
-	      output 	    abc_nmi_x, // System NMI request (ABC800)
-	      output 	    abc_xm_x, // System memory override (ABC800)
+	      output	    abc_rdy_x, // RDY = WAIT#
+	      output	    abc_resin_x, // System reset request
+	      output	    abc_int80_x, // System INT request (ABC80)
+	      output	    abc_int800_x, // System INT request (ABC800)
+	      output	    abc_nmi_x, // System NMI request (ABC800)
+	      output	    abc_xm_x, // System memory override (ABC800)
 	      // Master/slave control
-	      output 	    abc_master, // 1 = master, 0 = slave
-	      output 	    abc_a_oe,
+	      output	    abc_master, // 1 = master, 0 = slave
+	      output	    abc_a_oe,
 	      // Bus isolation
-	      output 	    abc_d_ce_n,
+	      output	    abc_d_ce_n,
 
 	      // ABC-bus extension header
 	      // (Note: cannot use an array here because HC and HH are
 	      // input only.)
-	      inout 	    exth_ha,
-	      inout 	    exth_hb,
-	      input 	    exth_hc,
-	      inout 	    exth_hd,
-	      inout 	    exth_he,
-	      inout 	    exth_hf,
-	      inout 	    exth_hg,
-	      input 	    exth_hh,
+	      inout	    exth_ha,
+	      inout	    exth_hb,
+	      input	    exth_hc,
+	      inout	    exth_hd,
+	      inout	    exth_he,
+	      inout	    exth_hf,
+	      inout	    exth_hg,
+	      input	    exth_hh,
 
 	      // SDRAM bus
-	      output 	    sr_clk,
-	      output 	    sr_cke,
+	      output	    sr_clk,
+	      output	    sr_cke,
 	      output [1:0]  sr_ba, // Bank address
 	      output [12:0] sr_a, // Address within bank
 	      inout [15:0]  sr_dq, // Also known as D or IO
 	      output [1:0]  sr_dqm, // DQML and DQMH
-	      output 	    sr_cs_n,
-	      output 	    sr_we_n,
-	      output 	    sr_cas_n,
-	      output 	    sr_ras_n,
+	      output	    sr_cs_n,
+	      output	    sr_we_n,
+	      output	    sr_cas_n,
+	      output	    sr_ras_n,
 
 	      // SD card
-	      output 	    sd_clk,
-	      output 	    sd_cmd,
+	      output	    sd_clk,
+	      output	    sd_cmd,
 	      inout [3:0]   sd_dat,
 
 	      // USB serial (naming is FPGA as DCE)
-	      input 	    tty_txd,
-	      output 	    tty_rxd,
-	      input 	    tty_rts,
-	      output 	    tty_cts,
-	      input 	    tty_dtr,
+	      input	    tty_txd,
+	      output	    tty_rxd,
+	      input	    tty_rts,
+	      output	    tty_cts,
+	      input	    tty_dtr,
 
 	      // SPI flash memory (also configuration)
-	      output 	    flash_cs_n,
-	      output 	    flash_sck,
+	      output	    flash_cs_n,
+	      output	    flash_sck,
 	      inout [1:0]   flash_io,
 
 	      // SPI bus (connected to ESP32 so can be bidirectional)
-	      inout 	    spi_clk,
-	      inout 	    spi_miso,
-	      inout 	    spi_mosi,
-	      inout 	    spi_cs_esp_n, // ESP32 IO10
-	      inout 	    spi_cs_flash_n, // ESP32 IO01
+	      inout	    spi_clk,
+	      inout	    spi_miso,
+	      inout	    spi_mosi,
+	      inout	    spi_cs_esp_n, // ESP32 IO10
+	      inout	    spi_cs_flash_n, // ESP32 IO01
 
 	      // Other ESP32 connections
-	      inout 	    esp_io0, // ESP32 IO00
-	      inout 	    esp_int, // ESP32 IO09
+	      inout	    esp_io0, // ESP32 IO00
+	      inout	    esp_int, // ESP32 IO09
 
 	      // I2C bus (RTC and external)
-	      inout 	    i2c_scl,
-	      inout 	    i2c_sda,
-	      input 	    rtc_32khz,
-	      input 	    rtc_int_n,
+	      inout	    i2c_scl,
+	      inout	    i2c_sda,
+	      input	    rtc_32khz,
+	      input	    rtc_int_n,
 
 	      // LED (2 = D23/G, 1 = D22/R, 0 = D17/B)
 	      output [2:0]  led,
@@ -106,10 +106,10 @@ module max80 (
 
 	      // HDMI
 	      output [2:0]  hdmi_d,
-	      output 	    hdmi_clk,
-	      inout 	    hdmi_scl,
-	      inout 	    hdmi_sda,
-	      inout 	    hdmi_hpd
+	      output	    hdmi_clk,
+	      inout	    hdmi_scl,
+	      inout	    hdmi_sda,
+	      inout	    hdmi_hpd
 	      );
 
    // Set if MOSFETs Q1-Q6 are installed rather than the corresponding
@@ -130,7 +130,7 @@ module max80 (
    wire	    vid_hdmiclk;	// D:o in the HDMI clock domain
    wire     flash_clk;		// Serial flash ROM clock
 
-   reg 	    reset_cmd_q = 1'b0;
+   reg	    reset_cmd_q = 1'b0;
    wire     reset_cmd;
 
    pll pll (
@@ -458,11 +458,6 @@ module max80 (
 		.wacc2    ( sdram_rom_wacc )
 		);
 
-   // SD card
-   assign sd_clk    = 1'b1;
-   assign sd_cmd    = 1'b1;
-   assign sd_dat    = 4'hz;
-
    // SPI bus (free for ESP32)
    assign spi_clk        = 1'bz;
    assign spi_miso       = 1'bz;
@@ -545,13 +540,15 @@ module max80 (
 	sdram_rdata <= sdram_rd;
      end
 
+   tri1 [15:0] iodev_wait_n;
+
    always @(*)
      case ( cpu_mem_quad )
        4'b0000: cpu_mem_ready = 1'b0;
        4'b0001: cpu_mem_ready = 1'b1;
        4'b0010: cpu_mem_ready = sdram_mem_ready;
        4'b0100: cpu_mem_ready = 1'b1;
-       4'b1000: cpu_mem_ready = 1'b1;
+       4'b1000: cpu_mem_ready = &iodev_wait_n;
        default: cpu_mem_ready = 1'bx;
      endcase // case ( mem_quad )
 
@@ -574,7 +571,7 @@ module max80 (
 	    .rdata ( fast_mem_rdata )
 	    );
 
-       
+
    // Input data MUX
    wire [31:0] iodev_rdata;
 
@@ -599,14 +596,14 @@ module max80 (
 	 led_q <= cpu_mem_wdata[2:0];
 
    assign led = led_q;
-   
+
    //
    // Serial ROM (also configuration ROM.) Fast hardwired data download
    // unit to SDRAM.
    //
    wire        rom_done;
-   reg 	       rom_done_q;
-   
+   reg	       rom_done_q;
+
    spirom ddu (
 	       .rst_n    ( rst_n ),
 	       .rom_clk  ( flash_clk ),
@@ -625,7 +622,7 @@ module max80 (
 
    always @(posedge sys_clk)
      rom_done_q <= rom_done;
-   
+
    //
    // Serial port. Direct to the CP2102N for reworked
    // boards or to GPIO for non-reworked boards, depending on
@@ -683,15 +680,39 @@ module max80 (
    assign tty_cts = tty_dtr_q[0] ? 1'bz : tty_cts_out;
    assign gpio[3] = tty_dtr_q[1] ? 1'bz : tty_cts_out;
 
+
+   // SD card
+   wire [31:0] sdcard_rdata;
+
+   sdcard sdcard (
+		  .rst_n   ( rst_n ),
+		  .clk     ( sys_clk ),
+		  .sd_cs_n ( sd_dat[3] ),
+		  .sd_di   ( sd_cmd ),
+		  .sd_sclk ( sd_clk ),
+		  .sd_do   ( sd_dat[0] ),
+		  .sd_cd_n ( 1'b0 ),
+		  .sd_we_n ( 1'b0 ),
+
+		  .wdata   ( cpu_mem_wdata ),
+		  .rdata   ( sdcard_rdata ),
+		  .valid   ( iodev[4] ),
+		  .wstrb   ( cpu_mem_wstrb ),
+		  .addr    ( cpu_mem_addr[3:2] ),
+		  .wait_n  ( iodev_wait_n[4] )
+		  );
+   assign sd_dat[2:1] = 2'bzz;
+
    //
    // I/O device input data MUX
    //
    always @(*)
      case ( cpu_mem_addr[9:6] )
-       4'h0:    iodev_rdata = { 29'b0, led_q };
-       4'h2:    iodev_rdata = { 31'b0, rom_done_q };
-       4'h3:	iodev_rdata = tty_rdata;
-       default: iodev_rdata = 32'hffff_ffff;
+       4'h0:    iodev_rdata  = { 29'b0, led_q };
+       4'h2:    iodev_rdata  = { 31'b0, rom_done_q };
+       4'h3:	iodev_rdata  = tty_rdata;
+       4'h4:    iodev_rdata  = sdcard_rdata;
+       default: iodev_rdata  = 32'h0;
      endcase
-   
+
 endmodule

BIN
fpga/output_files/max80.jbc


BIN
fpga/output_files/max80.jic


BIN
fpga/output_files/max80.pof


BIN
fpga/output_files/max80.sof


+ 243 - 0
fpga/sdcard.sv

@@ -0,0 +1,243 @@
+// -----------------------------------------------------------------------
+//
+//   Copyright 2003-2021 H. Peter Anvin - All Rights Reserved
+//
+//   This program is free software; you can redistribute it and/or modify
+//   it under the terms of the GNU General Public License as published by
+//   the Free Software Foundation, Inc., 53 Temple Place Ste 330,
+//   Bostom MA 02111-1307, USA; either version 2 of the License, or
+//   (at your option) any later version; incorporated herein by reference.
+//
+// -----------------------------------------------------------------------
+
+//
+// MMC/SD controller for MAX80
+//
+// This runs the SD card in SPI mode. In the future, consider improving
+// performance by switching to quad SD mode.
+//
+
+module sdcard (
+	       input		 rst_n, // Global reset
+	       input		 clk, // System clock (84 MHz)
+
+	       inout		 sd_cs_n, // SD card CS# (CD, DAT3)
+	       inout		 sd_di, // SD card DI (MOSI, CMD)
+	       inout		 sd_sclk, // SD card CLK (SCLK)
+	       inout		 sd_do, // SD card SO (MISO, DAT0)
+
+	       input		 sd_cd_n, // SD socket CD# (Card Detect) switch
+	       input		 sd_we_n, // SD socket WE# (Write Enable) switch
+
+	       input [31:0]	 wdata, // CPU data out (CPU->controller)
+	       output reg [31:0] rdata, // CPU data in (controller->CPU)
+	       input		 valid, // Memory valid
+	       input [3:0]	 wstrb, // Write strobes
+	       input [1:0]	 addr, // Address bits
+	       output		 wait_n	// Hold mem_ready
+	       );
+
+   // ------------------------------------------------------------------------
+   //  SD card interface
+   //
+   //  This drives the SD card in SPI mode.  We support two speeds:
+   //  84 MHz/4 = 21 MHz for normal operation, and 84 MHz/256 = 328 kHz
+   //  during initialization.
+   //
+   //  It exports the following I/O ports, address bits can be combined.
+   //  The actual connection to the CPU bus shifts the addresses left
+   //  by two so that dword accesses can be done.
+   //
+   //  Write, A[1:0]:
+   //  00	- control register:
+   //             [6:0] - speed divider (CPU_HZ/(2*(divider+1)))
+   //                 7 - CS# active
+   //                 8 - clear CRC registers
+   //                 9 - select CRC register inputs (0 = input, 1 = output)
+   //  01	- load output shift register from the CPU
+   //  10       - start transaction without loading
+   //  11       - load output shift register and start transaction
+   //
+   //  Note: the triggered bus transaction size is set by byte enables.
+   //  The output latch should be written left-aligned (most significant
+   //  bytes within a dword); the input latch right-aligned.
+   //
+   //  On read, A[1:0]:
+   //  00       - control register
+   //  01       - read input latch
+   //  10       - read CRC7 (in D[7:1], D0 = 1)
+   //  11       - read CRC16
+   // ------------------------------------------------------------------------
+
+   reg [31:0] sd_shr_out;
+   reg [31:0] sd_shr_in;
+   reg [4:0]  sd_out_ctr;	// Output bit counter
+   reg	      sd_active;	// Transfer in progress
+   reg	      sd_active_neg;	// Transfer in progress, first pos clock seen
+   reg        sd_crcsrc;	// CRC generator input
+   reg	      sd_crcstb;	// Strobe for CRC generator
+   reg	      sd_cs_reg;	// CS# active (positive logic, so inverted)
+   reg [6:0]  sd_crc7;		// CRC-7 generator
+   reg [15:0] sd_crc16;		// CRC-16 generator
+   wire       sd_cmd = valid & ~sd_active; // CPU command we can act on
+   reg	      sd_cmd_ok;	// Valid CPU command received
+   wire       sd_data_out = sd_shr_out[31];
+   reg	      sd_clk_out;	// Output clock signal
+
+   // Output pins - tristate if card not present
+   assign     sd_di   = ~sd_cd_n ? sd_data_out : 1'bz;
+   assign     sd_sclk = ~sd_cd_n ? sd_clk_out  : 1'bz;
+   assign     sd_cs_n = ~sd_cd_n ? ~sd_cs_reg  : 1'bz;
+   assign     sd_do   = 1'bz;	// Always an input
+
+   // If we try an action while a bus transaction is in progress,
+   // wait.  The register sd_cmd_ok is used to prevent WAIT# from
+   // being asserted when we already started a transaction on *this*
+   // I/O operation.
+   //
+   // valid:      0 0 0 0 1 1 1 0 0 0 0 0 0 1 1 1 1 1 0 0 0
+   // sd_active:  0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
+   // sd_cmd:     0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
+   // sd_cmd_ok:  0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 1 1 0 0
+   // cpu_wait_n: 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1
+
+   always @(negedge rst_n or posedge clk)
+     if (~rst_n)
+       sd_cmd_ok <= 1'b0;
+     else
+       sd_cmd_ok <= valid & (~sd_active | sd_cmd_ok);
+
+   assign wait_n = ~(valid & sd_active) | sd_cmd_ok;
+
+   // SD clock generator; this counter is used to generate the slow clock.
+   reg [6:0]  sd_clk_div;
+   reg [6:0]  sd_clk_ctr;
+   reg	      sd_clk_stb;	// Clock strobe (clock flips next cycle)
+   reg	      sd_clk_pol;	// Clock polarity
+   wire       sd_clk_pos;	// SD clock positive strobe
+   wire       sd_clk_neg;	// SD clock negative strobe
+
+   always @(posedge clk)
+     begin
+	if (|sd_clk_ctr)
+	  begin
+	     sd_clk_stb <= 1'b0;
+	     sd_clk_ctr <= sd_clk_ctr - 1'b1;
+	  end
+	else
+	  begin
+	     sd_clk_stb <= 1'b1;
+	     sd_clk_pol <= ~sd_clk_pol; // Polarity of clock (one cycle early)
+	     sd_clk_ctr <= sd_clk_div;
+	  end
+     end // always @ (posedge clk)
+
+   // Generate strobes from the sd_clk_ctr; this is defined to be 1
+   assign sd_clk_pos = sd_active     & sd_clk_stb &  sd_clk_pol;
+   assign sd_clk_neg = sd_active_neg & sd_clk_stb & ~sd_clk_pol;
+
+   always @(negedge rst_n or posedge clk)
+     if (~rst_n)
+       sd_clk_out <= 1'b0;
+     else
+       sd_clk_out <= (sd_clk_out | sd_clk_pos) & ~sd_clk_neg;
+
+   wire [2:0] nwrite = wstrb[3] + wstrb[2] + wstrb[1] + wstrb[0];
+   wire       rstrb = valid & ~|wstrb;
+
+   always @(negedge rst_n or posedge clk)
+     if (~rst_n)
+       begin
+	  sd_shr_out    <= 32'hffff_ffff;
+	  sd_cs_reg     <= 1'b0;
+	  sd_clk_div    <= 7'h7f;
+	  sd_active     <= 1'b0;
+	  sd_active_neg <= 1'b0;
+	  sd_out_ctr    <= 5'h0;
+	  sd_crcstb     <= 1'b0;
+	  sd_shr_in     <= 32'hffff_ffff;
+	  sd_crcsrc     <= 1'b0;
+       end
+     else
+       begin
+	  if (sd_clk_pos)
+	    begin
+	       sd_shr_in     <= {sd_shr_in[30:0], sd_do};
+	       sd_out_ctr    <= sd_out_ctr + 1'b1;
+	       sd_active_neg <= 1'b1;
+	    end
+	  if (sd_clk_neg)
+	    begin
+	       sd_shr_out    <= {sd_shr_out[30:0], 1'b1};
+	       sd_active     <= |sd_out_ctr;
+	       sd_active_neg <= |sd_out_ctr;
+	    end
+	  sd_crcstb <= sd_clk_pos; // CRCs are computed one cycle after posedge
+
+	  if (sd_cmd)
+	    begin
+	       if (addr[1:0] == 2'b00)
+		 begin
+		    if (wstrb[0]) {sd_cs_reg, sd_clk_div} <= wdata[7:0];
+		    if (wstrb[1]) sd_crcsrc <= wdata[9];
+		 end
+
+	       if (addr[0])
+		 begin
+		    if (wstrb[3]) sd_shr_out[31:24] <= wdata[31:24];
+		    if (wstrb[2]) sd_shr_out[23:16] <= wdata[23:16];
+		    if (wstrb[1]) sd_shr_out[15: 8] <= wdata[15: 8];
+		    if (wstrb[0]) sd_shr_out[ 7: 0] <= wdata[ 7: 0];
+		 end
+
+	       if (addr[1])
+		 begin
+		    sd_active  <= |wstrb;
+		    sd_out_ctr <= { nwrite[1:0], 3'b000 };
+		 end
+	    end // if (sd_cmd)
+       end // else: !if(~rst_n)
+
+   wire clear_crc = ~rst_n |
+	(sd_cmd & (addr[1:0] == 2'b00) & wstrb[1] & wdata[8]);
+
+   // CRC generators: we have one 7-bit and one 16-bit, shared between
+   // input and output.  The controller CPU has to specify where it wants
+   // the input from by setting A3 properly when starting a bus
+   // transaction (A4 = 1).
+   //
+   // The CRC generators run one cycle behind the positive sd_clk strobe.
+
+   wire sd_crcbit = sd_crcsrc ? sd_data_out : sd_shr_in[0];
+   wire sd_crc7in = sd_crcbit ^ sd_crc7[6];
+
+   always @(posedge clk)
+     if (clear_crc)
+       sd_crc7 <= 7'h00;
+     else if (sd_crcstb)
+       sd_crc7 <= {sd_crc7[5:3], sd_crc7[2]^sd_crc7in,
+		   sd_crc7[1:0], sd_crc7in};
+
+   wire sd_crc16in = sd_crcbit ^ sd_crc16[15];
+
+   always @(posedge clk)
+     if (clear_crc)
+       sd_crc16 <= 16'h0000;
+     else if (sd_crcstb)
+       sd_crc16 <= {sd_crc16[14:12], sd_crc16[11]^sd_crc16in,
+		    sd_crc16[10:5], sd_crc16[4]^sd_crc16in,
+		    sd_crc16[3:0], sd_crc16in};
+
+   // Data out MUX
+   // Currently no registers with read side effects
+   always @(*)
+     begin
+	case (addr)
+	  2'b00: rdata = { 22'b0, sd_crcsrc, 1'b0, sd_cs_reg, sd_clk_div };
+	  2'b01: rdata = sd_shr_in;
+	  2'b10: rdata = { 24'b0, sd_crc7, 1'b1 };
+	  2'b11: rdata = { 16'b0, sd_crc16 };
+	endcase // case (addr)
+     end
+
+endmodule // sdcard

+ 1 - 0
fw/fatfs

@@ -0,0 +1 @@
+Subproject commit 074e3603efad8eca429ada5b693ab906090554d1