Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to write to flash? #52

Open
navaneeth-cirel opened this issue Jul 20, 2021 · 13 comments
Open

How to write to flash? #52

navaneeth-cirel opened this issue Jul 20, 2021 · 13 comments
Labels

Comments

@navaneeth-cirel
Copy link

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

@xobs
Copy link
Member

xobs commented Jul 20, 2021

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
Copy link
Author

@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
Copy link
Collaborator

zyp commented Jul 20, 2021

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
Copy link
Author

@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
Copy link
Contributor

mithro commented Jul 23, 2021

FYI - @kgugala

@norbertthiel
Copy link

@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 transfer_byte 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
Copy link
Collaborator

zyp commented Jul 29, 2021

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

@norbertthiel
Copy link

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
Copy link
Collaborator

zyp commented Jul 29, 2021

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
Copy link

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

@enjoy-digital
Copy link
Member

@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
Copy link
Member

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

@enjoy-digital enjoy-digital changed the title How to write to flash How to write to flash? Jul 30, 2021
@norbertthiel
Copy link

porting above python code to C - allowing to spiflash_sector_erase and spiflash_write_stream 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;
}

mntmn pushed a commit to mntmn/litex that referenced this issue Apr 25, 2023
The code is based on norbert thiel's comment litex-hub/litespi#52
But edited to work with W25Q128JVS flash used in MNT RKX7.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants