litex-hub / litespi

Small footprint and configurable SPI core
BSD 2-Clause "Simplified" License
39 stars 24 forks source link

How to write to flash? #52

Open navaneeth-cirel opened 3 years ago

navaneeth-cirel commented 3 years ago

How to use litespi to perform flash write? I am trying to integrate litespi and use bios to download images to flash.

xobs commented 3 years ago

Currently, you should use the bitbang registers (e.g. https://rm.fomu.im/lxspi.html) to send raw SPI commands.

An example of how to do this can be found in Foboot. Use spiInit() https://github.com/im-tomu/foboot/blob/master/sw/src/spi.c#L186-L201 followed by spiBeginErase4(erase_addr) (https://github.com/im-tomu/foboot/blob/master/sw/src/spi.c#L101-L114), followed by waiting for spiIsBusy() to return false https://github.com/im-tomu/foboot/blob/master/sw/src/spi.c#L71-L73

Then call spiBeginWrite(addr, data, count) (https://github.com/im-tomu/foboot/blob/master/sw/src/spi.c#L146-L166) and wait for spiIsBusy() to return false again. Then call spiFree().

navaneeth-cirel commented 3 years ago

@xobs Thank you for the detailed usage example. I was using the bitbang SPI from litex/soc/cores/spi_flash.py along with the litex/soc/software/libbase/spiflash.c and it works, however the write speed is seriously limited and I am getting only about 2kB/s while downloading a bitstream using lxterm. If I understand correctly this is same method used in Fomu as well ?

zyp commented 3 years ago

LiteSPI doesn't have bitbang registers, @xobs code is for litex.soc.cores.spi_flash.

For LiteSPI you can use the registers provided by the LiteSPIMaster module for the same purpose. Here's an example of how to write flash from python over a wishbone bridge:

#!/usr/bin/env python3

import sys
import deps
from litex.tools.litex_client import RemoteClient

c = RemoteClient()
c.open()

PROGRAM_SIZE = 256
ERASE_SIZE = 4096

def transfer_byte(b):
    while not (c.regs.spiflash_mmap_master_status.read() & (1 << 0)):
        pass

    c.regs.spiflash_mmap_master_rxtx.write(b)

    while not (c.regs.spiflash_mmap_master_status.read() & (1 << 1)):
        pass

    return c.regs.spiflash_mmap_master_rxtx.read()

def transfer_cmd(bs):
    c.regs.spiflash_mmap_master_phyconfig.write((1 << 16) | (1 << 8) | (8 << 0))

    c.regs.spiflash_mmap_master_cs.write(1)

    r = [transfer_byte(b) for b in bs]

    c.regs.spiflash_mmap_master_cs.write(0)

    return bytes(r)

def read_status_register():
    return transfer_cmd(b'\x05\x00')[1]

def write_enable():
    transfer_cmd(b'\x06')

def page_program(addr, data):
    transfer_cmd(b'\x02' + addr.to_bytes(3, 'big') + data)

def sector_erase(addr):
    transfer_cmd(b'\x20' + addr.to_bytes(3, 'big'))

def write_stream(addr, stream):
    assert addr & (ERASE_SIZE - 1) == 0

    while True:
        data = stream.read(PROGRAM_SIZE)

        if not data:
            break

        if addr & (ERASE_SIZE - 1) == 0:
            write_enable()
            sector_erase(addr)

            while read_status_register() & 1:
                pass

            print(f'Erased addr {addr}.')

        write_enable()
        page_program(addr, data)

        while read_status_register() & 1:
            pass

        print(f'Wrote {len(data)} bytes.')

        addr += len(data)

def main():
    with open(sys.argv[1], 'rb') as f:
        write_stream(0, f)

if __name__ == '__main__':
    main()

If you want a faster option, I've written a flash writer module that just takes a data and address stream: https://github.com/orbcode/orbtrace/blob/main/orbtrace/flashwriter.py

navaneeth-cirel commented 3 years ago

@zyp Thank you for the code snippet. I have referred that implemented the same thing in the C code of bios and now able to write to flash using litespi. Looks like there were multiple factors affecting the serialboot method of downloading to spiflash but mainly I think it was the python serial interface which was the bottleneck, the serial read in python is supposedly slow (a lot of posts in stackoverflow regarding this) and also the CRC check for every frame. I have now added a complete image checksum instead of CRC and also increased the frame size to hold 2048 bytes of payload. After all these changes I am now getting a serialboot upload speed of ~60 kB/s.

mithro commented 3 years ago

FYI - @kgugala

norbertthiel commented 3 years ago

@navaneeth-cirel, @zyp I guess, initializing len, width and mask also would be needed?

also ported code snippet to C - but for some reason it is "not working" - see _transferbyte below

static uint32_t transfer_byte(uint8_t b)
{
    // wait for tx ready
    while(!spiflash_mmap_master_status_tx_ready_read())
    ;

    spiflash_mmap_master_rxtx_write((uint32_t)b);

    //wait for rx ready
    while(!spiflash_mmap_master_status_rx_ready_read())
    ;

    return spiflash_mmap_master_rxtx_read();
}

any idea?

zyp commented 3 years ago

What exactly is not working? Have you scoped the signals? Did you assert CS first?

norbertthiel commented 3 years ago

What exactly is not working? Have you scoped the signals? Did you assert CS first?

yepp, did assert CS; nope litescope causes build process (spartan 6) to crash: image

scoping these signals:

            analyzer_signals = [
                self.spiflash_mmap.master.cs,
                self.spiflash_mmap.master._rxtx.r,
                self.spiflash_mmap.master._rxtx.w,
            ]

Did scope pads - cs raises - no activity on miso/mosi lines

looking at code in generic.py I am wondering, what len, width and mask are supposed to contain? reading them before writing reveals 0 (zero) for all - I guess that is not right.

I can perfectly read from flash though ....

zyp commented 3 years ago

Oh, my bad, the line setting phyconfig got removed by accident when I cleaned up the code snippet above. I've added it back.

len, width and mask needs to be set to 8, 1 and 1 respectively.

norbertthiel commented 3 years ago

@zyp works like a charm - saved my day - thanks!

enjoy-digital commented 3 years ago

@zyp, @norbertthiel: Very interesting. @norbertthiel I'm currently improving LiteSPI integration with LiteX and LiteX-Boards and want to do an equivalent of https://github.com/enjoy-digital/litex/blob/master/litex/soc/software/libbase/spiflash.c for LiteSPI which is from what I understand what you just did :) Is is something you would like to contribute to project and so that we could use in in https://github.com/enjoy-digital/litex/tree/master/litex/soc/software/liblitespi and integrate it to the BIOS commands?

enjoy-digital commented 3 years ago

Hmm sorry, this is the code snippet you just posted before and it also seems to be part of https://github.com/enjoy-digital/litex/pull/979. I'm going to look at that, but if you want to share your code it can still be useful.

norbertthiel commented 3 years ago

porting above python code to C - allowing to _spiflash_sectorerase and _spiflash_writestream etc...

//IMPLEMENT writing to SPI Flash
static uint8_t w_buf[SPI_FLASH_BLOCK_SIZE + 4];
static uint8_t r_buf[SPI_FLASH_BLOCK_SIZE + 4];

static uint32_t transfer_byte(uint8_t b)
{
    // wait for tx ready
    while(!spiflash_core_master_status_tx_ready_read())
    ;

    spiflash_core_master_rxtx_write((uint32_t)b);

    //wait for rx ready
    while(!spiflash_core_master_status_rx_ready_read())
    ;

    return spiflash_core_master_rxtx_read();
}

static void transfer_cmd(uint8_t *bs, uint8_t *resp, int len)
{
    spiflash_core_master_phyconfig_len_write(8);
    spiflash_core_master_phyconfig_width_write(1);
    spiflash_core_master_phyconfig_mask_write(1);
    spiflash_core_master_cs_write(1);

    for(int i=0; i < len; i++)
        resp[i] = transfer_byte(bs[i]);

    spiflash_core_master_cs_write(0);
}

uint32_t spiflash_read_status_register(void)
{
    uint8_t buf[2];
    w_buf[0] = 0x05;
    w_buf[1] = 0x00;
        transfer_cmd(w_buf, buf, 2);
    return buf[1];
}

void spiflash_write_enable(void)
{
    uint8_t buf[1];
    w_buf[0] = 0x06;
        transfer_cmd(w_buf, buf, 1);
}

static void page_program(void *addr, uint8_t *data, int len)
{
    w_buf[0] = 0x02;
    w_buf[1] = ((uint32_t)addr)>>16;
    w_buf[2] = ((uint32_t)addr)>>8;
    w_buf[3] = ((uint32_t)addr)>>0;
    memcpy(w_buf+4, data, len);
        transfer_cmd(w_buf, r_buf, len+4);
}

void spiflash_sector_erase(void *addr)
{
    w_buf[0] = 0x20;
    w_buf[1] = ((uint32_t)addr)>>16;
    w_buf[2] = ((uint32_t)addr)>>8;
    w_buf[3] = ((uint32_t)addr)>>0;
        transfer_cmd(w_buf, r_buf, 4);
}

#define min(x, y) (((x) < (y)) ? (x) : (y))

int spiflash_write_stream(void *addr, uint8_t *stream, int len)
{
    int res = 0;
        if( ((uint32_t)addr & (SPI_FLASH_ERASE_SIZE - 1)) == 0)
    {
        int w_len = min(len, SPI_FLASH_BLOCK_SIZE);
        int offset = 0;
        while(w_len)
        {
            if(((uint32_t)addr+offset) & (SPI_FLASH_ERASE_SIZE - 1) == 0)
            {
                spiflash_write_enable();
                sector_erase(addr+offset);

                while (spiflash_read_status_register() & 1)
                ;
            }

            spiflash_write_enable();
            page_program(addr+offset, stream+offset, w_len);

            while(spiflash_read_status_register() & 1)
                ;

            offset += w_len;
            w_len = min(len-offset,SPI_FLASH_BLOCK_SIZE);
            res = offset;
        }
    }
    return res;
}