Sunday 4 December 2022

SD Card Access for a Arty A7: Part 3

Foreword

This is the third part in the series where we try to get a SD Card reader to work on an Arty A7 board.

In the previous post we had a look at the FPGA core by Dan Gisselquist that can interface with an SD Card Reader.

A nice feature of Dan Gisselquist's core is the test bench that can simulate responses from an SD Card. Out the box this Test Bench works within the Verilator eco system. However, in the previous post we managed to use Gisselquist's SD Card response module with simulation in Vivado.

We concluded the previous post been able to issue an IDLE command to the SD Card response module, and got a response back.

In this post we will do the same exercise on the physical Arty A7 board, issuing an IDLE command to the SD Card and checking if we can also get a response back from an SD Card.

Attaching the SD Card module to the Arty A7

In the first part of this series I briefly shown a pic of a PMOD SD Card reader still in its packaging.

To be honest, this module was in its packaging up to now 😁 Well, I thought just to share a picture of the Sd Card module attached to the Arty A7 board:


There was one caveat I discovered straight away when inserting the SD Card module, which I didn't thought of before hand: This module occupies some space in front of the PMOD header next to it.

This might pose an issue when we want to use a VGA PMOD module later in the project, which uses both PMOD headers JB and JC.

To get around this issue, we might need to make use of a PMOD extension cord for inserting the SD Card module, freeing some space in front of header JB. But, we will tackle this issue when we get there.

Creating the constraints

We need to create some constraints to ensure the ports from the top module are mapped to the correct pins on the PMOD header. We start by looking at the general xdc file for the Arty A7 on Github. In particular, we are interested in PMOD section JA, which we use for our SD Card Module:

We adjust the port names as follows:

## Pmod Header JA
set_property -dict {PACKAGE_PIN G13 IOSTANDARD LVCMOS33} [get_ports cs]
set_property -dict {PACKAGE_PIN B11 IOSTANDARD LVCMOS33} [get_ports mosi]
set_property -dict {PACKAGE_PIN A11 IOSTANDARD LVCMOS33} [get_ports miso]
set_property -dict {PACKAGE_PIN D12 IOSTANDARD LVCMOS33} [get_ports sclk]
set_property -dict { PACKAGE_PIN D13   IOSTANDARD LVCMOS33 } [get_ports dat1]; #IO_L6N_T0_VREF_15 Sch=ja[7]
set_property -dict { PACKAGE_PIN B18   IOSTANDARD LVCMOS33 } [get_ports dat2]; #IO_L10P_T1_AD11P_15 Sch=ja[8]
set_property -dict {PACKAGE_PIN A18 IOSTANDARD LVCMOS33} [get_ports cd]
set_property -dict {PACKAGE_PIN K16 IOSTANDARD LVCMOS33} [get_ports wp]
Next, we should ensure that our top level module use the same port names:

module top(
    input wire CLK100MHZ,
    output wire cs,
    output wire mosi,
    input wire miso,
    output wire sclk,
    input wire cd,
    input wire wp
    );
...
endmodule
Inside this top level module an instance will live of the Gisselquist SD Card core. We will also be implementing a state machine in this module for instruction the Gisselquist core for sending an IDLE command to the SD Card.

We will cover the state machine in the next section.

The State Machine

Let us create a state machine for issuing a IDLE command to the Gisselquist core.

For starters, one need to look at clock speed. The input clock to the Arty A7 is always 100MHz. This is perhaps a bit too fast for our purposes, so one can create a slower clock with the help of a MMCME2_ADV block. I have covered the use of such a block in in one of previous posts some time ago, so I am not going to cover the process of instantiating one here.

Preferably our state machine should only start once the generated clock is stable. For this purpose we will use the .LOCKED signal of the MMCME2_ADV instance. With this in mind, let us start with an outline of our state machine:

always @(posedge gen_clk)
begin
    if (clk_locked)
    begin
      case (state)
      ...
      endcase
    end
end
So, the state machine will only start changing states once the clock is locked. We start with a number of dummy states to give the Gisselquist core a chance to initialise, after which we de-assert the reset signal to the core:

...
          0: state <= 1;
          1: state <= 2;
          2: state <= 3;
          3: state <= 4;
          4: begin
               state <= 5;
               reset_sd <= 0;
             end
...
So, what next? We could go straight ahead and issue the IDLE command, but preferably we set the signal which we clock the SD Card at the desired initial frequency, which is 400KHz. I am clocking the Sd Card core at a frequency of 10MHZ, so we will need to bring it down by means of the internal clock divider provided by this core. To get to 400KHz we need to use a divider value of 11, which is 0b in hexadecimal. We set the value with the state machine as follows:

          5: begin
                 state <= 6;
                 stb <= 1;
                 wb_sel <= 4'hf;
                 addr <= 1;
                 we <= 1;
                 data <= 32'h5556550b;
             end
          6: begin
                 state <= 7;
                 stb <= 0;
             end
          7: begin
                 state <= 8;
                 stb <= 1;
                 wb_sel <= 4'hf;
                 addr <= 0;
                 we <= 1;
                 data <= 32'hc0;
             end
To understand this snippet, we need to quickly look at the internal registers of the Gisselquist core:
  • Register 0: Used for sending command bytes. SD Cards always expect a command command that starts with 01. For all the other bit combinations of the two significant bits of the command byte, we are free to use as command bytes operating on the Gisselquist core itself.
  • Register 1: Data register containing four extra bytes of data associated with the command byte. If you want to issue a command byte containing multiple bytes, you need to set this register first before issuing the command.
So, from the above snippet we are issuing the command byte c0, which sets some internal configuration registers of the Gisselquist core. The value to write to the configuration registers should be stored in the data register beforehand, which in this case is 32'h5556550b. The lower eight bits of the this value is the value for the divider, which is 0b.

Once we have set the the internal configuration registers, we are free to issue the idle command to the Sd Card:
 
          8: begin
                 state <= 9;
                 stb <= 1;
                 wb_sel <= 4'hf;
                 addr <= 1;
                 we <= 1;
                 data <= 32'hffffffff;
                  
              end
          9: begin
                 state <= 10;
                 stb <= 1;
                 wb_sel <= 4'hf;
                 addr <= 0;
                 we <= 1;
                 data <= 32'h40;
                  
              end
          10: begin 
                 stb <= 0;
             end
So, we issue the command 40 to the SD Card, followed by 4 ff bytes.

Clocking the Sd Card

The Gisselquist core generates a clock that we can clock the SD Card. The temptation is great just to connect this signal directly to the outside world, as we do with the other signals to the SD Card.

However, everything always gets more complex with clocks, whether passing it around within the FPGA or passing it externally. In my previous project where I implemented a C64 core on a Zybo board, I had some fun at one stage dealing with an external clock, here.

I wanted make use of the the onboard Audio Codec on the Zybo board and with my first attempt I wanted to clock this device directly. The Audio Codec simply refused to work. After some digging on the Internet, I discovered that you should always pass a clock to the outside world with an ODDR block.

The clock signal to the SD Card is no exception, so let us define an ODDR instance:

   ODDR #(
      .DDR_CLK_EDGE("OPPOSITE_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE" 
      .INIT(1'b0),    // Initial value of Q: 1'b0 or 1'b1
      .SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC" 
   ) ODDR_inst (
      .Q(sclk),   // 1-bit DDR output
      .C(o_sclk),   // 1-bit clock input
      .CE(1), // 1-bit clock enable input
      .D1(1), // 1-bit data input (positive edge)
      .D2(0), // 1-bit data input (negative edge)
      .R(0),   // 1-bit reset
      .S(0)    // 1-bit set
With all this in place, we are now ready to do a test run on the Arty A7 board

Test Results

Let us have a look at the Test Results, when running the core on the Arty A7:


I tried to cram quite a lot of info into this picture and the names of the signals is perhaps not so readable, so I repeat the signal names:
  • cs (Chip Select)
  • miso
  • mosi
  • o_sclk
Looking at the signals, we can see that we issue the IDLE command (0x40) on the mosi signal, and eventually we get get a response back from the SD Card (e.g. 0x01)via the miso signal, which is what we expect.

In Summary

In this we gave the Gisselquist core a test run on an Arty A7 with a SD Card module attached and issued an IDLE command. With the test, the SD Card responded correctly, confirming that we are on track at the moment with out setup.

There are quite a number of commands one needs to issue to an SD Card to read the data stored on it and this can be very cumbersome to implement via a state machine.

It will be far easier to write a program executed by a CPU for issuing the SD Card commands. We will look into this with the next post.

The CPU I have in mind for this exercise is the 6502. Granted, this is an 8-bit CPU and one will probably not get the best performance given the 32-bit data that needs to be passed quite often to the Gisselquist core, but I think it is a good start.

The 6502 doesn't need so much resources of the FPGA, which will leave us more room for the Amiga core to be used later in the Blog series.

If required, we can always help the 6502 out with some hardware acceleration.

Until next time!

No comments:

Post a Comment