Open navaneeth-cirel opened 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()
.
@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 ?
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
@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.
FYI - @kgugala
@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?
What exactly is not working? Have you scoped the signals? Did you assert CS first?
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:
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 ....
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.
@zyp works like a charm - saved my day - thanks!
@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?
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.
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;
}
How to use litespi to perform flash write? I am trying to integrate litespi and use bios to download images to flash.