Showing posts with label 6502. Show all posts
Showing posts with label 6502. Show all posts

Sunday, 12 February 2023

SD Card Access for a Arty A7: Part 7

Foreword

In the previous post we managed to read a sector of data from an SD Card.

In this post w will continue our journey and see if we can read a file from a FAT32 formatted partition. To be able to read a file from a partition will form an important part of being able to boot an Amiga core on a Arty A7, and thus to load a Boot ROM and disk image from SD Card.

There is quite number of technical details involved to read a file from a FAT32 partition. Writing the functionality for this right from the start in 6502 Assembly language is quite a daunting task.

To make our lives easier we will start to write the functionality in a High Level language. As my current knowledge of FAT32 is rather limited, experimenting with such a partition in a High Level Language will get one quickly up to speed.

Once we know how to read a file from a FAT32 partition, we can write 6502 assembly for this in a future post.

About MBR and FAT32

Before we look in detail how to read a file from an SD Card, let us start with some basic terminology.

Firstly, the storage of any SD Card is divided into many blocks, where each block is 512 bytes in size. The choice of 512 bytes per block is actually rooted in the history of Personal computers where almost any Floppy Disk Drive or Hard Drive had a basic block size of 512 bytes.

To address blocks, all the blocks are numbered consecutively starting at block 0, and going up to the maximum number of blocks the device supports.

Block zero is called the MBR or master boot record. This is also where the history of the IBM PC kicks in again and I think is probably still relevant today. When an IBM PC boots up, it looks for machine language program at block zero, to start the booting of the system. Hence the name Master Boot Record.

The MBR has some other purpose as well, which is to store one or more partition entries, so you can be able to create more than one volume on the same device. We briefly encountered this in the previous post where we saw data at location 0x1be of block 0. This data was in actual fact a partition entry.

Also, from the previous post, you will remember that apart from the partition entry data, all the other bytes of that block were zero. So, although we call block zero the MBR, the SD Cards you use today most probably will not contain machine code in that block.

Now back to the partition entry. A partition entry gives us the block number of the sector of the partition in question. This first sector of the partition is called again, surprise, a boot record! The name is again because of the legacy of the IBM PC.

The boot record also contain some data to hold of the File allocation table and to be able to read the contents of a file.

We will have a look at a typical boot record in the next section.

Looking at the boot record

To look at the boot record, we first need find the block number of the it, via the MBR. In the previous post I made a screenshot of the MBR from the SD Card I was playing with, which contained the partition entry.

Here is the screenshot again, but with the bytes of the partition entry highlighted:


As you can see the partition entry starts at 0x1be and is 16 bytes in size. To get the meaning of the bytes, we look at the following link:

https://en.wikipedia.org/wiki/Master_boot_record#PTE

Two pieces of useful information for us, in the last 8 bytes of the entry:

  • offset 8: LBA of first absolute sector in the partition (4 bytes)
  • offset C: Number of sectors in partition.
For interest sake, let us start by checking if we can calculate the size of the partition. The bytes at offset set are as follows: EB 0B 76 00

These bytes are in little endian order, so we need to reverse them: 00 76 0B EB. This gives us the number of sectors in hexadecimal. In decimal this is 7,736,299. To verify if this number is correct, we need to convert number of sectors to bytes, that is multiplying by 512:

3960,985,088

With the thousand separators, we get more or less to 4GB, which is the size of the SD Card I am using.

Next, let us determine the staring block of the FAT32 partition. The data bytes for this 15 20 00 00. Swop this around because of little endianness:

00 00 20 15

So, the starting block is 2015 in hex, which is, 8213 in decimal. Now, let us write a program for reading this sector from the SD Card dump and display it.

The program language I am going to use is Java. This just the computer language I use every day in my work, so for this reason I am going to use it.

First we need to open the dump:

RandomAccessFile fis = RandomAccessFile("dump.sdcard","r");
With instances of RandomAccessFile, we can easily jump around within different positions in the file, which is what will need for this exercise of attempting to read a file from a FAT32 partition in a dump file.

Now, using instances of RandomAccessFile, as well as any other classes which allows you to read from a file in Java, to surround it with a try-catch block to handle IOExceptions. However, to keep the conversation focussed, I will not go into details of exception handling in Java.

Next, let us add some code for seeking to the boot sector and reading it:

        fis.seek(8213 * 512);
        byte[] buf = new byte[512];
        fis.read(buf);
        var dataString = new String(buf, StandardCharsets.US_ASCII);
        System.out.println(dataString);
Obviously we need to multiply 8213 by 512, because the seek method wants the position in bytes. I then reaad the sector into a byte buffer and then convert it to a String to see if there is any interesting human readable properties. When printing the String, we see the following:


Interesting snippets, we see like MSDOS5.0 and BLACKBERRY FAT32. This is more or less strings we expect from a Bootsector formatted with FAT32. 

Some of you might be confused with the word BLACBERRY in the output. Well, I used a Blackberry Phone about 10 years ago. When I received the phone it became bundled with an SD Card, which I am using for this exercise.

This confirms that we found the correct sector as the boot sector. In the next section we go further looking into the info stored in this sector.

A deeper look into the Boot sector

To make sense of the info stored in the boot sector, the following resources is of great help:


As you can see, there is quite a number of sectors stored in Boot sector. We will only be needing a handful of these:

        sectorsPerFat = FatBrowser.readFourBytes(buf, 36);
        numFat = buf[16];
        numReserved = buf[14];
        sectorsPerCluster = buf[13];
        rootCluster = buf[44];
        dataStart = numReserved + numFat * sectorsPerFat;
Let us dissect this snippet of code a bit. buf is the byte buffer we read in the previous section, containing the boot sector. FatBrowser.readFourBytes() is a pseudo function for taking four bytes starting at position 36 of buf, and forming a number.

Now, with FAT32, which we use here, the available storage is divided into multiple clusters. Usually each cluster is more than one sector in size. In fact, with the SD Card I use in this post, each cluster is 64 sectors in size, indicated by the variable sectorsPerCluster.

With a cluster size of 64 sectors, it means that the size allocated per file will be at least 64 sectors in size and multiples of it. The FAT keeps track where the different parts of a file are on a disk and also works with clusters.

Having talked a lot about FATs and Clusters, let us see how these are arranged on the partition:

For the numbers in the diagram, I have used my SD Card as an example. The number in your case might differ.

In my case the partition starts with 9 reserved sector, of which the boot record is the first reserved sector.

Following the Reserved sectors comes the actual FAT, with one or more copies. The total number of FATs is indicated by byte 16 of the Boot record, indicated by the variable name numFat in the code snippet above. The actual size of 945 sectors of the FAT I got from the variable sectorsPerFAT.

The clusters that the FAT refers to lives in the Data Area. In my case, the Data Area begins at sector 9 + 945 + 945 = 1899.

Now, one may be tempted to say the first cluster in the Data Area is cluster number zero. This is, however, not the case with FAT, where the first cluster is numbered 2. The reason for this is because in the FAT table entries zero and one are reserved. The significance of this 2 is that any cluster number you obtain from the system, you need to subtract two to get the real cluster slot number within the Data Area.

The rootCluster in the code snippet above is no exception to the rule. In my case rootCluster is 2, meaning you will find the root directory at Slot 0 in the Data Area, e.g. right at the beginning of the Data Area.

The root directory contains actual file entries which we are interested in, which we will try to read in the next section.

Looking into the root directory

Let us have look at the how the first sector of the root directory looks like. The following Java snippet will read this sector and display it:

        fis.seek((8213 + 9 + 945+ 945) * 512);
        byte[] buf = new byte[512];
        fis.read(buf);
        var dataString = new String(buf, StandardCharsets.US_ASCII);
        for (int i = 0; i < 16; i++) {
            System.out.println(dataString.substring(0, 32));
            dataString = dataString.substring(32);
        }
The number we use in the seek I used as derived from the previous section, with 8213 the start of my FAT32 partition.

From the link I presented earlier on from osdev.org, I know each file entry in the root directory is 32 bytes, so I only print 32 bytes per line. The output I got from this program, looks like this:


Each line starts with something that looks like a filename. Some lines have characters which are separated by some whitespace. These characters are in actual fact Unicode characters which have two bytes per character.

These filenames with Unicode characters are actually entries for long filenames. For what we want to do, it is best to ignore long file entries, and just focus on the non-Unicode lines, like RECORD~1, BLACKB~1, MUSIC and so on. I will illustrate in a moment how we can filter out the long file entries.

Reading a file

Being able to view root directory entries, let us see if we can the contents of file. To do this, we first need to find the file entry in the root directory.

To do this, let us start by writing a code snippet for listing the filenames in the root directory, excluding the long file entries. To determine if an entry is a long file entry, we need to look at byte 11 of the file entry. From the link from OSDev.org, the bits in the attribute byte has the following meaning:

  • READ_ONLY=0x01
  • HIDDEN=0x02
  • SYSTEM=0x04 
  • VOLUME_ID=0x08 
  • DIRECTORY=0x10 
  • ARCHIVE=0x20 
  • LFN=READ_ONLY|HIDDEN|SYSTEM|VOLUME_ID
For our purposes, we want to skip entries where the attribute is 15, resulting in the following code snippet:

        for (int i = 0; i < 16; i++) {
            int beginFileEntry = i * 32;
            if (buf[beginFileEntry + 11] == 15) {
                continue;
            }
            System.out.println(new String(buf, beginFileEntry, 11));
        }
This result in the following output:

This looks a lot cleaner than our previous attempt. One that looks strange in this output, however, is the entry preceded by the question mark. This is a deleted file, which we should also remove from our result. To skip past deleted files, we can just continue the loop as well if the first character of a filename is 0xE5.

Also, all the file entries we see here are directory entries. We need to search some more blocks in the is root directory cluster, to find some useful files to look at. So, we add an outer loop to our existing loop for reading the next block to look at:

        for (int j = 0; j < 63; j++) {
            fis.read(buf);
            for (int i = 0; i < 16; i++) {
                int beginFileEntry = i * 32;
                if (buf[beginFileEntry + 11] == 15) {
                    continue;
                }
                if ((buf[beginFileEntry] & 0xff) == 0xE5) {
                    continue;
                }
                System.out.println(new String(buf, beginFileEntry, 11));
            }
        }
This time we are seeing some more interesting stuff:

Here we see a couple of individual files, like WMPINFO.XML, CONTACTS.VCF, TEST.TXT and so on. Remember, these filenames are in 8.3 format. First 8 characters are the filename, followed by an extension of 3 characters. If the filename is less than 8 characters, you will space padding between filename and extension in output.

From these file entries, let us see if we can output the content of TEST.TXT. We modify our loops, so it will output the file entry slot within the current sector we are busy with:

        int foundSlot = -1;
        outerLoop:
        for (int j = 0; j < 63; j++) {
            fis.read(buf);
            for (int i = 0; i < 16; i++) {
                int beginFileEntry = i * 32;
                if (buf[beginFileEntry + 11] == 15) {
                    continue;
                }
                if ((buf[beginFileEntry] & 0xff) == 0xE5) {
                    continue;
                }
                if (new String(buf, beginFileEntry, 11).equalsIgnoreCase("TEST    TXT")) {
                    foundSlot = i * 32;
                    break outerLoop;
                }
            }
        }

Now, from the resulting file entry, we know the following:
  • bytes 20 + 21: High 16 bits of first cluster number for file
  • bytes 26 + 27: low 16 bits of first cluster number for file
To calculate the first cluster number of the file, we need to do some bit manipulation:

        int cluster = (buf[foundSlot + 21] & 0xff) >> 24 | (buf[foundSlot + 20] & 0xff) >> 16
                | (buf[foundSlot + 27] & 0xff) >> 8 | (buf[foundSlot + 26] & 0xff) >> 0;
Finally, we read the first sector of the file in question as follows:

        fis.seek((8213 + 9 + 945+ 945 + (cluster - 2) * 64) * 512);
        fis.read(buf);
        System.out.println(new String(buf));
In the seek command we basically start off again with calculation to find beginning of Data area, and adding to it the cluster number converted to a sector count. This is the output I get:


Ok, I admit I created this file beforehand and copied it to the SD Card, before making an image of it, which I used in this post😁

With all this done, I think we covered the basics of locating and reading a file from a FAT32 partition.

In Summary

In this post we continued our journey to find out how to read a file from an SD Card. We wrote some snippets of code to gradually explore how a FAT32 partition works and ended off by successfully reading file from such a partition.

In the next post we will redo the exercise, but by writing the code in 6502 Assembly language.

Until next time!


Sunday, 29 January 2023

SD Card Access for a Arty A7: Part 6

Foreword

In the previous post we managed to Initialise an SD Card from power up.

In this post we will try to read a sector from the SD Card.

We will continue to use the Gisselquist SD Card core for interfacing with the SD Card. 

Buffering a sector of data

When you read a sector of data from a SD Card, the SD Card will respond after a certain amount of time at which it will send the 512 bytes of data one after the other consecutively. If your CPU is busy during this point in time, we might miss a byte or two from the data.

Luckily the Gisselquist core provide us way out of this scenario by buffering 512 bytes of data for us when it becomes available. The CPU can then fetch the data at a later stage from the buffer when it is ready.

This buffer is in actual fact a FIFO (First In First Out) structure. This means that you only need a single address to map the contents of the FIFO into CPU memory space and not 512 addresses.

There is a couple of technicalities to remember when using the FIFO buffers in the Gisselquist core. The first thing is, when you issue a read command to the SDCard, you should also inform the Gisselquist core that this command will be utilising the FIFO buffer. To illustrate this in 6502 assembly language syntax:

...
     .BYTE $00, $00, $00, $00
     .BYTE $00, $00, $08, $51 ; CMD 51
...
The value $51 is the SD Card command for reading a sector.

Next to the value $51 we have the value $08. If you study the final assembly listing from the previous post, you will pick up that usually for a command row the first three bytes of 0's, followed by a command byte. If there is any bits set in the first three bytes, it provides the Gisselquist core with some additional info about the particular command.

In this case the $08 byte signal the Gisselquist core that we expect 512 bytes from the SD Card and this should be stored in the FIFO buffer for later access.

Like with the other commands we need to wait in a busy wait loop until the busy bit changes to zero.

The next question is, how do we read the FIFO buffer? The answer is to read register 2 of the Gisselquist core. Let us recap from previous posts the registers that the Gisselquist core contains. I have added register 2 to the list, just for completeness:

  • Register 0: Command register
  • Register 1: Data register
  • Register 2: FIFO buffer
These registers map into our 6502 address space starting at address FE00. It should be remembered that the Gisselquist core has 32 bit registers, whereas the 6502 works only with 8 bits at a time. So, Register 0 will map to address FE00, Register 1 will map to address FE04 and Register 2 will map to address FE08.

Now, back to the details of Register 2, the FIFO buffer register. In order to read the data you can continuously read register 2, which will bring you back 4 bytes at a time, with each read advancing to the next 4 bytes.

You will remember from my previous posts that in our 6502 design we trigger a register read by issuing an address that is a multiple of 4, like FE00, FE04 or FE08. The addresses FE01, FE02 and FE03 stores the remaining three bytes of the register which we couldn't accept, because the 6502 only works with a byte at a time.

There is one final technicality we need to look at. From the previous posts you will remember that we always use command $C0 for initialising the state of the Gisselquist core:

     .BYTE $55, $55, $55, $0B
     .BYTE $00, $00, $00, $C0 ; CMD C0
The value $0B set the value of the clock divider.

Now, the technicality I am referring to are bits 15-18, which specifies the limit of the FIFO buffer as a power of two. IF we use the command as above, the FIFO size limit value will be 5, which equals 32 bytes. The correct value to use for this is 9, which will yield the following $C0 command:
 
     .BYTE $55, $59, $55, $0B
     .BYTE $00, $00, $00, $C0 ; CMD C0

Verifying our design

When we run our design on a real FPGA, we will need a way to tell that the values are correctly read from the SD Card.

The easiest way for this verification is just to make an image dump of the SD Card we are using, and check some values with a Hex editor. The values we then get back from the FPGA need to match the values we saw in the Hex editor.

To take the SD Card I used as an example:


When I opened my dump, the beginning was filled with zeros. Not very useful for a test. However, Scrolling further down, eventually yielded some data:


So, if we run a test on the Arty A7, we should look at around byte 0x1bf of the sector data returned to verify if our implementation works correctly.

If you are doing a test yourself, you can also expect data in more or less the same spot. Any properly formatted SD Card will have a master boot record (MBR) located in sector zero. According to Wikipedia, bytes 0x1be to 0x1ce contains the first partition entry in a MBR.

Let us write some Assembly for reading sector zero from the SD Card:

       LDA #6
       JSR CMD
       LDA #0
       STA $FE0B
       LDX #$74
LOOPLD
       LDA $FE08
       DEX
       BNE LOOPLD
       LDA #2
       STA $FE0B
We start by issuing a sector read command. In our CMD table this is contained in slot 6, which we load into the Accumulator and we Jump to the CMD routine, which issues the command to the SD Card.

This routine only returns once we received the full response. Once the full response is loaded into the FIFO, our CPU needs to read it out, by reading address $FE08 multiple times.

We will again use an ILA (Integrated Logic Analyzer) block for examining the read data returned by the Gisselquist core to the CPU.

Unfortunately the data we are looking for is quite deep in the FIFO, and the Arty A7 doesn't provide enough block RAM to capture all read data returned. So, we need some trigger that we can only capture the segment we are interested in.

For this reason why are executing the loop in the above assembly $74 times. It should be noted that each read returns 4 bytes, so we need to multiply this number by 4 to get the real byte number where the loop will stop. In this case the byte number is $1d0, which gives us a big enough window for our ILA to capture all bytes in question.

Once the loop has completed, we store the value 2 into address $FE0B. This will set a bit triggering a capture on the ILA block. 

Let us have a look at what the ILA capture looks like:



As with a lot of ILA captures, they are simply too wide to present within a blog. With this ILA screen capture I tried to illustrate that I cut out a portion of the screenshot so we can view the important parts together. In the first part of signal we can see the assertion of the capture signal by the CPU at sector sample 0x1d0.

In the second signal we can see that the value 76000000. This is the value captured before we get to sample 0x1d0 and corresponds to the hex dump I presented earlier.

This proves that our design is more or less correct.

In Summary

In this post we attempted to read a sector of data from an SD Card and proved via a dump made from the SD Card via another system that our design read the data correctly.

In the next post we will attempt to read a file from an SD Card using its FAT table. This will be a big milestone in getting an Amiga core to boot up on an Arty A7, which will require to load an Amiga ROM and disk images from an SD Card.

Until next time!

Thursday, 5 January 2023

SD Card Access for a Arty A7: Part 5

Foreword

In the previous post we replaced our state machine with a 6502 CPU + machine code program for issuing commands to the Gisselquist SD Card core. We managed to issue an IDLE to command to an SD Card with the 6502 core.

In this post we will continue our endeavour of trying to access an SD Card by means of the Gisselquist SD Card core and a 6502 CPU.

I mentioned in the previous post that in this post  I want to finish off with the process of powering up and initialising the SD Card, followed by reading some data from it. However, I found that the process of reading data from the SD Card is quite involved, so to keep things simple I will just be covering the process of initialising the SD Card in this post.

Revisiting 6502 Assemblers

In the previous post I wrote 6502 machine code manually. The required machine code was fairly straightforward, so doing the process manually wasn't that much of a deal.

However, from this point onwards, the complexities of machine code will only increase, so it make sense to rather use an Assembler.

Using an assembler the code will remain readable and self documenting.

The Assembler I have chosen for this purpose is the following online one:

https://www.masswerk.at/6502/assembler.html

During the course of this post, I will give a gradual introduction this assembler. Let us start with a quick outline:

.ORG $FF00
     ; Assembly language instructions
ENDROM = $FFFF-*-3
.FILL ENDROM 00
.BYTE 0, $FF, 00, 00
We start with the directive .ORG, specifying the start address of our program. The assembler needs this info to calculate various things, like if you jump to a label, to calculate the absolute address of that label.

Next we declare a symbol ENDROM, where we actually work with an address at the end of our assembly language program, donated by *.  At any point in time within the assembly listing you can get the current address via this asterisk. In the case of ENDROM, the expression will return the number of bytes remaining to get to a total ROM size of 256 bytes. From this number we subtract three, so we can leave a gap at the end for our reset vector.

With the .FILL directive, we add a number of padding bytes. As mentioned previously, ENDROM is the calculated number of bytes that needs to be added to get to 256 bytes, and the .FILL makes it happen.

The .BYTE directive allows us to emit one or more bytes of data. In this case it is the Reset vector, as well as the IRQ vector.

To get an idea into what this outline program will assemble as, let us enter the program into the above mentioned assembler:


As can be seen from the picture, we have a set of zero's starting at address $FF00. If you scroll down, you will see the zeros stop address $FFFF:


Thus, the resulting binary is exactly 256 bytes, which is what we want.

As also can be seen from these screenshots, there is a Show Address checkbox. Unchecking this checkbox, will remove the address from each line, which will make it easy to create a Hex file which is required by Vivado to populate a ROM.

Reducing repetition

In software development we have a very common term called DRY: Don't Repeat yourself.

Well, in the previous post I wrote some 6502 machine code where I repeated the same set of instructions for different pieces of data. We can do better and see if we can encapsulate the code into loops and Subroutines. Also, perhaps store the data into lookup tables.

Let us start with the command for setting the clock speed of sclk, and express it in a lookup table:

DATA: 
     .BYTE $55, $55, $55, $0B
     .BYTE $00, $00, $00, $C0 ; CMD C0
So, here we first present the data for setting the data register in the SDSPI core, and then the actual command.

Let us add one more command and see if we can start to spot some patterns:

DATA: 
     .BYTE $55, $55, $55, $0B
     .BYTE $00, $00, $00, $C0 ; CMD C0
     .BYTE $FF, $FF, $FF, $FF
     .BYTE $00, $00, $00, $40 ; CMD 40
We see that each command has a size of 8 bytes. We can use a zero based index for accessing the bytes for a particular command from the lookup table. For example, for Command $C0 we will use index 0 and for command $40 we will use index 1.

To deal with lookups from a table, the 6502 provide us with the Indirect Indexed addressing mode. Let us start with a basic loop for sending a command:

LOOP:
     LDA ($A0),Y
     STA $FE00,X
     INY
     DEX
     BPL LOOP
From this we can see that the address A0 should contain the base address of the lookup table, which we should initialise in the beginning like this:

.ORG $FF00
     LDX #$FF
     TXS
     LDA #<DATA
     STA $A0
     LDA #>DATA
     STA $A1
A couple of initialisation steps are happening. First we should init the stackpointer with the value $FF. The 6502 doesn't do this at startup and forgetting this initialisation will give you an XX during simulation of the Arlet core in the Stackpointer.

Both "<" and ">" are Assembler directives yielding the low and high address respectively of a label.

Let us focus at the loop code again. The Y register points to a specific entry into the lookup table, incrementing it to the next byte with each iteration of the loop. 

The X register starts with a value of 7 and goes to zero. This will transfer the data of a lookup entry to addresses FE07 to FE00. As from the previous post these addresses maps to the Gisselquist SD Card core.

One question that remains is how Y is initialised. The journey starts with the command index stored in the Accumulator, after which we do the following:

     ASL
     ASL
     ASL
     TAY
This is equivalent to multiplying the command index by 8.

This covers more or less what is required to issue a command to the SD Card. There is, however, one caveat we haven't dealt with in the code, and that is that we should wait for the SD Card to complete a command before issuing the next one.

The way to check this is to continuously poll address 0 of the Gisselquist core and see if the busy bit, which is bit 14, is cleared.

Having considered all this, we end off with the following subroutine for issuing a command to the SD Card:
 
CMD:
     ASL
     ASL
     ASL
     TAY
     LDX #$07
LOOP:
     LDA ($A0),Y
     STA $FE00,X
     INY
     DEX
     BPL LOOP
     AND #$80
     BMI END
BUSY
     LDA $FE00
     BIT $FE01
     BVS BUSY
END
     RTS
One thing that might look a bit strange is that we and the command byte, which is always the last byte in a command entry of the lookup table, with $80. Here we basically want to test if the command is a true SD Card command (which always starts with 01) and not a command dedicated to the Gisselquist core. There is always a wait associated with a SD Card command, but not with a Gisselquist core command.

Verilog issues

While I was testing the 6502 machine code I developed in this post, I discovered a couple of flaws with my existing FPGA design.

The first issue is when executing the command STA $FE00,X with X 0 or 4 which triggers a wishbone bus operation.

With this instruction the complete address is basically asserted for two consecutive clock cycles on the address bus and the write line is asserted only at the second consecutive cycle.

Now when the full address is asserted during the first clock cycle, the system assumes a memory read because the write signal is not asserted. With normal block RAM this is not an issue and will just result in a redundant read.

However, with addresses FE00 and FE04 things get a bit more complicated since these ones trigger a wishbone read transaction. As we know at this point in time wishbone reads asserts the RDY signal on the 6502 during some clock cycles.

All in all things just gets more complicated when you trigger a read on the wishbone bus on one clock cycle and a write on the bus the next clock cycle. These action makes the 6502 and the Gisselquist core out of sync with each other and the wrong values gets written.

There is probably a number of ways to solve this, but the easiest way I could come up with was just add a register to our FPGA design instructing the system to ignore all reads to the Wishbone bus. When are at a point in our program where we will do a couple of writes via an Absolute,X instructions we just need to set this register so reads to wishbone bus can be ignored.

Let us implement this register:

...
reg [7:0] ignore_reads = 0;
...
assign wb_stb = cpu_address[15:8] == 8'hfe && on_word_boundary && !(ignore_reads[0] && !we_6502);
...
always @(posedge gen_clk)
begin
  if (we_6502 && cpu_address[15:8] == 8'hfe)
  begin
    if (cpu_address[1:0] == 2'h1 && !cpu_address[3])
    begin
        reg_1 <= cpu_data_out;
    end else if (cpu_address[1:0] == 2'h2 && !cpu_address[3])
    begin
        reg_2 <= cpu_data_out;
    end else if (cpu_address[1:0] == 2'h3 && !cpu_address[3])
    begin
        reg_3 <= cpu_data_out;
    end else if (cpu_address[3:0] == 11)
    begin
       ignore_reads <= cpu_data_out;
    end
  end
end
...
I have highlighted the changes in build. I have made ignore_reads 8 bits wide, in case we need additional signals later on.

With these changes our memory map in the FE00 range is like this:
  • FE00-FE07: SD Card core registers
  • FE0B: Ignore reads
I will present a full Assembly listing at the end of this post to show how FE0B should be used.

Another thing we need to implement in Verilog is to map block RAM for Zero Page and the Stack. This is very similar to what we did in the previous post where we mapped ROM in the space FF00-FFFF, so I will not be covering it here.

Looking Deeper into SD Card commands

Up to this point we have only used the SD Card IDLE command. Let us have a look at some other commands with the focus of initialising an SD Card.

I will try and be brief about these commands. If you want more detail on these commands, you can consult the following sources:


Let us start by looking at the command CMD8, which tells us if the card is indeed an SD Card or an MMC card. In my case I will only call this command for own curiosity to confirm that this card is indeed an SD Card. At this point I will not expect the 6502 program to make any decision based on whether the card is an SDCard or MMC.

The Byte definition in 6502 assembly for this command is as follows:

     .BYTE $00, $00, $01, $AA
     .BYTE $00, $00, $02, $48 ; CMD 48
From this we can see that CMD8 starts with the byte $48, followed by four bytes which end with the bytes $01 and $AA.

You will notice that next the command byte, there is byte of value with 2. This value informs the Gisselquist core on what type of response we are expecting, which in this case is a response byte followed by 4 bytes. This info is important so that we read the correct number of bits from the serial line.

The next command of interest is CMD58. This basically tells us the voltages that the Card supports. The definition in Assembly code is the following:

     .BYTE $FF, $FF, $FF, $FF
     .BYTE $00, $00, $02, $7A ; CMD 7A
This is also a command where we get one response byte back followed by four bytes. The format of the trailing four bytes are as follows:

Here I am using a diagram from http://www.rjhcoding.com. The most interesting bits are bits 15-23, indicating the voltages the SD Card can handle. According to the SD Card spec the general excepted voltage is 3.3V. However, the SD Card I am testing with have all the bits 15-23 set to one, meaning that it can work with the voltage range 2.7V-3.6V. Not sure how much other SD Cards will differ.

Another interesting bit is bit 31. While the card is powering up, this bit will be 1, and will change to zero once power up is completed. 

Let us move onto the command that performs the actual initialisation. This actually involves two separate commands, CMD55 and ACMD41. The first command signals that the next command will be a application specific command, which is ACMD41.

The assembly byte definition for these commands are as follows:

     .BYTE $00, $00, $00, $00
     .BYTE $00, $00, $00, $77 ; CMD 55
     .BYTE $40, $00, $00, $00
     .BYTE $00, $00, $00, $69 ; CMD 41
You will notice that CMD 41 contains a command byte $40. This is because bit 31 of the command data is reserved and should be set to one.

The CMD55 and ACMD41 you need to call continuously in a loop and during each iteration you need to check the response byte of the ACMD41 command. When the response byte has transitioned from a 0 (e.g. BUSY), to 1 (initialised), the SD Card initialisation has completed and it is ready to accept read/write commands.

The full program

Here is the full program listing:

.ORG $FF00
     LDX #$FF
     TXS
     LDA #<DATA
     STA $A0
     LDA #>DATA
     STA $A1
     LDX #1
     STX $FE0B
START:
       LDA #0
       JSR CMD
       LDA #1
       JSR CMD
       LDA #2
       JSR CMD
       LDA #3
       JSR CMD
INIT
       LDA #4
       JSR CMD
       LDA #5
       JSR CMD
       ROR A
       BCS INIT 
       LDA #3
       JSR CMD
       LDA #2
       STA $FE0B
       LDA $FE04
DONE
       JMP DONE
CMD:
     ASL
     ASL
     ASL
     TAY
     LDX #$07
LOOP:
     LDA ($A0),Y
     STA $FE00,X
     INY
     DEX
     BPL LOOP
     AND #$80
     BMI END
     LDX #0
     STX $FE0B
BUSY
     LDA $FE00
     BIT $FE01
     BVS BUSY
END
     LDX #1
     STX $FE0B
     RTS
.ALIGN $8
DATA: 
     .BYTE $55, $55, $55, $0B
     .BYTE $00, $00, $00, $C0 ; CMD C0
     .BYTE $FF, $FF, $FF, $FF
     .BYTE $00, $00, $00, $40 ; CMD 40
     .BYTE $00, $00, $01, $AA
     .BYTE $00, $00, $02, $48 ; CMD 48
     .BYTE $FF, $FF, $FF, $FF
     .BYTE $00, $00, $02, $7A ; CMD 7A
     .BYTE $00, $00, $00, $00
     .BYTE $00, $00, $00, $77 ; CMD 55
     .BYTE $40, $00, $00, $00
     .BYTE $00, $00, $00, $69 ; CMD 41
ENDROM = $FFFF-*-3
.FILL ENDROM 00
.BYTE 0, $FF, 00, 00
As you can see there is a loop at the INIT label where we continuously CMD55 and ACMD41 until the card is initialised.

You will also notice that we use the address $FE0B as mentioned previously to disable the creation of wishbone read commands if required.

In this code I have also purposed bit 1 of $FE0B for something else. I am using this bit as trigger for a Xilinx ILA debug core for capturing data. In the code I am setting this bit when invoking command index 3 (e.g. CMD58 or command byte $7A) for a second time.

By triggering the ILA core at this point we can inspect the OCR after initialisation to see if bit 31 has changes to a zero, indicating that the initialisation was indeed successful. The signal I am inspecting with the ILA for this is the miso signal, from which we get the serial data from the SD Card.

To get a better overview of what is going on, I have included a screenshot of a ILA capture on machine for the above scenario:


The key signal here is miso. The location where the logic level initially drops from a 1 to 0 is the start of the response from the SD Card for the CMD58 command. Use the rising edge of each o_sclk as reference for each bit of data.

The first byte of data has every bit zero. This is our response byte and indicate that the SD Card is not in IDLE mode anymore. Should this byte had a value of 1, this would have indicated that the SD Card was in IDLE mode.

The following two bits are one, meaning that both bit 31 and bit 30 are one. This indicates that the power up routine is completed and the SD Card is ready to accept read/write commands.

From the rest of the bits we can deduce that bits 15-23 are all ones, meaning that my SD Card support all mentioned voltage levels.

In Summary

In this post we wrote a 6502 assembly program for initialising an SD Card. We also issued some other SD Card commands to confirm that the Card has properly powered up.

In the next post we will attempt to read from the SD Card.

Until next time!

Friday, 23 December 2022

SD Card Access for a Arty A7: Part 4

Foreword

In the previous post we managed to issue an IDLE command to SD Card via an SD Card reader, attached to the Arty A7 board. We also confirmed that SD Card send a response back for the command.

Up to this point in time we made use of a state machine for issuing command sequences to the Gisselquist SD core. There is quite a number of commands one needs to issue to an SD Card, in order to do reading/writing of data stored on the SD Card. Using a state machine for this exercise can become quite unpleasant in the long run.

Thus, in this post we will look at using a CPU core, on which we can run a stored program for issuing the SD Card command sequences. The CPU core I will using for this purpose will be Arlet Ottens' 6502 core. 

I am sure there will be very frowns out there on using an 8-bit CPU, working with a 32-bit Wishbone device like the Gisselquist SD Card core. However, the 6502 is fairly light on FPGA resources and I think it is worthwhile to see how far this core can help us out.

The Memory Map

Let us have a look at the memory map for our 6502 system:

  • FFFF - FF00: ROM. For starters we will have a 256 byte ROM, but might grow beyond this size over time. As with many 6502 systems, the startup ROM needs to live in the top part of RAM, because the reset vector is at addresses FFFC-FFFD.
  • FEFF - FE00: Interface to the registers of Gisselquist SD Card Core. As we have seen in the previous post, we have access to 2 32-bit registers via the Wishbone bus of the Gisselquist core.
Let us zoom a bit into the Interface to the Gisselquist core. The wishbone interface works with 32 bits of data, whereas the 6502 works with 8 bits of data at a time. How does one deal with these differences in data widths?

To explain a possible solution to the problem, let us start by arranging the memory locations starting at FE00 like this:

So, the addresses FE00-FE003 maps to register 0 of Gisselquist core, and the addresses FE04-FE07 maps to Register 1 of the Gisselquist core.

Now, read/writes to the lowest byte of each register (e.g. marked in red), will trigger transactions on the wishbone bus. The byte addresses in black, maps to temporary registers.

Suppose we want our 6502 to write a value to Register 1. We will start by writing the top three bytes of the 32 bit word to memory locations FE07, FE06 and FE05. Writing to these addresses will set the values of temporary registers and will not trigger any wishbone transaction. Witing to FE04, however, will a wishbone write transaction.

With this wishbone write transaction, we will concatenate the values stored in temporary registers FE07, FE06 and FE05, together with the value currently been written by the 6502 to address FE04.

I wishbone read works in a very similar way, triggered by reading either FE04 and FE00. The top three bytes returned by the wishbone read will be stored in another set of temporary registers, which afterwards can also be read by the 6502 at addresses FE07/FE06/FE05 or FE03/FE02/FE01. 

I will give more detail on implementing this in coming sections.

Wiring up the 6502

Let us start Wiring up the 6502 core.

For starters, we need a ROM for feeding the 6502 with a program to execute. For this we go on a trip in memory lane, where in 2017 we created a ROM module for our C64 core, here. You can find the full source for this module with this link on Github: https://github.com/ovalcode/c64fpga/blob/master/ip/bblock/src/rom.v 

An instance of the ROM module looks like this:

   rom#(
    .ADDR_WIDTH(8),
    .ROM_FILE("romsdspi.bin")
)   rom (
      .clk(gen_clk),
      .addr(cpu_address[7:0]),
      .rom_out(rom_out)
    );
As mentioned earlier, we will start off with only a 256 byte ROM. For this reason ADD_WIDTH is set to 8. We also only use the lower 8 bits of the address from the CPU.

The parameter, .ROM_FILE, is the path to a file on the file system containing a ROM image. This is a text file, one byte per line and in Hex. So, our 256 byte ROM, will result in a file containing 256 lines. As mentioned previously, the rest vector is at address FFFC-FFFD, so the last four lines of our ROM file will look like this:

00
FF
00
00
Here we see the 6502 will start executing at address FF00, the beginning of the last 256 page in the 64K address space. We will cover the assembly code a bit later.

Let us now have a quick look at an instance of the Arlet Ottens core:

cpu cpu( .clk(gen_clk), .reset(...), .AB(cpu_address), .DI(rom_out), .DO(cpu_data_out), .WE(we_6502), .IRQ(0), .NMI(0), .RDY(1) );

Writing from 6502 to SDSPI Core

Let us now focus on the functionality for writing from the 6502 core to a SDSPI Core register.

Firstly, because the 6502 can only deal with 8-bits at a time, we need to add 3 temp registers so we can have 32-bits available that the SDSPI require for a write:

always @(posedge gen_clk)
begin
  if (we_6502)
  begin
    if (cpu_address == 16'hfe01)
    begin
        reg_1 <= cpu_data_out;
    end else if (cpu_address == 16'hfe02)
    begin
        reg_2 <= cpu_data_out;
    end else if (cpu_address == 16'hfe03)
    begin
        reg_3 <= cpu_data_out;
    end
  end
end
Next, let us generate a strobe signal for the SDSPI Core:

always @(posedge gen_clk)
assign on_word_boundary = cpu_address[1:0] == 0;

assign wb_stb = cpu_address[15:8] == 8'hfe && on_word_boundary;
So, we only strobe on a word boundary, e.g. addresses like FE00 and FE04. The applicable signals on the SDSPI core looks like this:

sdspi  sdspi (
...
		// Wishbone interface
		// {{{
		.i_wb_cyc(1), .i_wb_stb(wb_stb), .i_wb_we(we_6502),
		.i_wb_addr({1'b0,cpu_address[2]}),
		.i_wb_data({reg_3, reg_2, reg_1, cpu_data_out}),
...
	);

Reading with the 6502

Now, let us look into reading with the 6502. Reading is a bit more complex than writing, because we can read from potentially two sources: ROM and registers of the SDSPI core.

To cater for the two possible read sources, let us create the following outline:

...
cpu cpu( ... .DO(cpu_data_out), ... );
...
always @(posedge gen_clk)
begin
  addr_delayed <= cpu_address;
end
...
always @*
begin
    casex (addr_delayed)
        ...
        default: combined_data = rom_out;
    endcase 
end
...
The casex is the main part for selecting the correct source. We use a casex instead of a usual case because we use a subset of the bits to decide which source to select. We will add more selectors to our casex in a bit.

One thing you will also notice, is that we are using a delayed version of the address for selection. This is just to cater for the way Block RAMs work, which always has the data ready for given address at the next clock cycle. At the next clock cycle the 6502 core can potentially assert a different address, which can cause data from the wrong source to be selected and presented to the CPU.

Now, let us extend our outline so that we make our 6502 read registers from the SDSPI core:

...
always @(posedge gen_clk)
begin
    wb_data_store <= (wb_stb && !we_6502) ? o_data_sdspi[31:8] : wb_data_store;  
end
...
always @*
begin
    casex (addr_delayed)
        16'b1111_1110_xxxx_xx00: combined_data = o_data_sdspi[7:0];
        16'b1111_1110_xxxx_xx01: combined_data = wb_data_store[7:0];
        16'b1111_1110_xxxx_xx10: combined_data = wb_data_store[15:8];
        16'b1111_1110_xxxx_xx11: combined_data = wb_data_store[23:16];

        default: combined_data = rom_out;
    endcase 
end
...
So, when we read from a SDPSI core regitser we store the top three in temporary register called wb_data_store, which the 6502 can read at later stage if so desired.

At this point we have a small caveat, since the SDSPI Core will not have register data ready at the next clock cycle, but require one additional clock cycle before the data is ready. This behaviour breaks all the assumptions the 6502 core make.

Luckily, the 6502 core does provides an RDY input signal, with which we can effectively pause the 6502 on read for as many clock cycles as we want to, until the data we want is ready in the data bus. With this in mind, we need to change the code above to the following:

...
always @(posedge gen_clk)
begin
    wait_read <= wait_read ? 0 : (wb_stb && !we_6502);
end

always @(posedge gen_clk)
begin
    capture_data <= wait_read;
end

always @(posedge gen_clk)
begin
    wb_data_store <= capture_data ? o_data_sdspi[31:8] : wb_data_store;  
end

cpu cpu(... .RDY(!wait_read) );

always @(posedge gen_clk)
begin
  addr_delayed <= wait_read ? addr_delayed : cpu_address;
end
...
As seen from this code, we also need to wait before we capture a value for wb_data_store, as well as delaying addr_delayed even further is required.

The 6502 Assembly Program

Let us have a look at a 6502 Assembly program for for accessing the SDSPI core, which will ultimately put an SD Card into IDLE mode:

FF00   A9 55     LDA #$55
FF02   8D 01 FE  STA $FE01
FF05   8D 02 FE  STA $FE02
FF08   8D 03 FE  STA $FE03
FF0B   A9 0B     LDA #$0B
FF0D   8D 04 FE  STA $FE04 ; Store the value $5555550B into Data Register
FF10   A9 C0     LDA #$C0  
FF12   8D 00 FE  STA $FE00 ; Init Config registers with value stored in Data Register
FF15   A9 FF     LDA #$FF
FF17   8D 03 FE  STA $FE03
FF1A   8D 02 FE  STA $FE02
FF1D   8D 01 FE  STA $FE01
FF20   8D 04 FE  STA $FE04 ; Load Data register with $FFFFFFFF
FF23   A9 40     LDA #$40
FF25   8D 00 FE  STA $FE00 ; Give Idle command ($40) followed by $FFFFFFFF (e.g. Data Register)
Just to give some context again. Data Register mentioned in the comments is register 1 of the SDSPI core.

The actual command byte is issued via address $FE00. The command byte value $C0 instructs the SDSPI core to load the config registers with values stored in the Data Register. Command byte value $40 instructs the SD Card to go into IDLE mode.

One part I haven't shown in this program is a required endless loop.

The waveforms produced by this Assembly program is the same as in the previous post where we issued an IDLE command by means of a state machine, so I will not present the waveforms in this post.

In Summary

In this post we added added Arlet Otten's 6502 core to our design, so that we can programmatically initialise an SD Card. Doing SD Card initialisation with a state machine will just become too cumbersome on the long run.

In the next post we will try and fully initialise the SDCard and see if we can read a sector of data from the Card.

Until next time!