Initial OTP programming of FE310-g002 chips

I have some bare FE310-g002 from crowdsupply which I have placed on a custom PCB. Code does not seem to run from QSPI flash after hardware reset/POR. If I halt the core and resume at 0x2000000 via JTAG, the code written to QSPI flash executes as expected.

Reading out 0x00001000 via JTAG indicates that MSEL is 0x03 which causes execution to start at mask rom which jumps to OTP according to the manual. Readouts of the 0x00020000-0x00021FFF region via JTAG seem to indicate that OTP is all zeros.

https://forums.sifive.com/t/initial-otp-programming-of-fe310-chips/1131 indicated that the FE310-g000 chips were shipped with OTP programmed. Is OTP pre-programmed in the bare FE310-g002 chips or do I need to program it myself?

hello @camdenmil your findings are very interesting. some time ago @tincman and I were discussing a similar question, here
msel-pins-on-fe310

it would really be nice to find where is the external hardwired control of MSEL; my best guess is in the discussion at the link above.

also, note the minor difference between -g000 and -g002:
Chapter 5 of the User Manual states the four values of MSEL[1:0] are
MSEL[1:0]
0 0 trap loop
0 1 jump to QSPI (0x2000 0000)
1 0 jump to OTP (0x0002 0000)
1 1 jump to ROM (0x0001 0000) (on -G000 it’s 0x00001000, not a typo)

I just messed around with the chip a bit more and really just confirmed what the manual says. On reset, pc starts at 0x1004 in gate ROM. The gate ROM jumps to mask ROM at 0x00010000 since MSEL is 0x03. Mask ROM then jumps to OTP at 0x00020000. After the jump, 0x00000000 is fetched from OTP which triggers an illegal instruction trap. Since the POR state of mtvec is zero, pc ends up at 0x00000000 and stays there until I JTAG the core to somewhere else (or poke mtvec via JTAG)

Unless I’m missing something here (I’m new to RISC-V), seems like I either need to program a jump into the beginning of OTP or set MSEL to 0x01 if that’s possible.

I think that programming OTP is the only solution here. These MSEL lines exist inside the chip, but they do not have any connections to the outside world.

I attempted to program the OTP and I can’t seem to set any bits when following the procedure in the manual. I wrote a barebones C program to write to OTP since metal doesn’t implement an OTP control interface. I’m sort of at a loss for what to do here since the disassembly and stepping through the program seems to indicate that it’s correctly following the manual’s procedure.

I’ve included my code below. It’s called from a _start that simply initializes the stack and jumps to main. I’ve been running this code out of DTIM at 0x80000000.

OTP Programming Code
#include "stdint.h"

#define IO(mem_addr) (*(volatile uint32_t *)(mem_addr))

#define otp_lock   IO(0x10010000)
#define otp_ck     IO(0x10010004)
#define otp_oe     IO(0x10010008)
#define otp_sel    IO(0x1001000C)
#define otp_we     IO(0x10010010)
#define otp_mr     IO(0x10010014)
#define otp_mrr    IO(0x10010018)
#define otp_mpp    IO(0x1001001C)
#define otp_vrren  IO(0x10010020)
#define otp_vppen  IO(0x10010024)
#define otp_a      IO(0x10010028)
#define otp_d      IO(0x1001002C)
#define otp_q      IO(0x10010030)
#define otp_rsctrl IO(0x10010034)

#define pllcfg     IO(0x10008008)

void wait(int us){
    int j = us * 8; //2 instructions per loop iteration
    for (int i = 0; i<j; i++){
        asm("");
    }
}

void get_otp_lock(){
    while(otp_lock == 0){
        otp_lock = 1;
    }
}

void release_otp_lock(){
    otp_lock = 0;
}

int write_otp_voltages(){
    otp_mrr = 0x4;
    otp_mpp = 0x0;
    otp_vppen = 0x0;
    wait(40);
    if((otp_mrr != 0x4) || (otp_mpp != 0x0) || (otp_vppen != 0x0)){
        return(1);
    }
    return(0);
}

void write_word_otp(uint32_t addr, uint32_t value){
    otp_a = addr;
    wait(5);

    for (int i = 0; i < 32; i++){
        if (value & (1<<i)){
            //Write
            otp_d = (1<<i);
            otp_ck = 1;
            wait(50);
            otp_ck = 0;
            //Verify
            otp_mrr = 0x9;
            for (int j = 0; j < 10; j++){
                if(j > 0){
                    otp_mrr = 0xF;
                }
                wait(10);
                if (otp_q & (1<<i)){
                    break;
                } else {
                    //Check failed, rewrite the bit
                    otp_d = (1<<i);
                    otp_ck = 1;
                    wait(400);
                    otp_ck=0;
                }
            }
            otp_mrr = 0x4;
        }
    }
}

int main (void) {
    pllcfg = 0x00070DF1; //Use 16Mhz crystal for hfclk
    get_otp_lock();
    if (write_otp_voltages() != 0){
        return(0);
    }
    write_word_otp(2039, 0x0000000F); //Throw a fence somewhere inconsequential
    release_otp_lock();
    return(0);
}

I’m not sure if this warrants a documentation bug/issue. The FE310-g002 datasheet says that the beginning and end of OTP come programmed but all of OTP is zeros when read via the memory mapped region at 0x00020000 and when read using the OTP control registers.

Solved it. I checked out all of the pads on the chip and found that pin 12, OTP_AIVDD, had not made a connection to the PCB. I got some solder under that pad and everything works as expected now.

When I was poking around on my original HiFive1 several years ago I found that the OTP didn’t read correctly if the CPU clock was 256 MHz but it worked fine and I saw the documented values when I ran at 16 MHz.