Skip to content
VLSI Mentor

SPI · Module 11

Boot from SPI Flash

The first-fetch sequence a boot ROM issues: waking a part previous software may have left asleep, verifying the device before trusting it, and why an image length must never be used before the header carrying it has been verified.

Every chapter so far assumed software was running. This one is about the transaction that happens when none is.

A boot ROM reads an image header, takes the length from it, and copies that many bytes into RAM. What is wrong with that sentence?

The length came from flash that has not been checked. Using it to size a memory copy lets whatever is in flash — corrupted, erased, or deliberately altered — decide how much memory to overwrite. The order of operations in a boot sequence is not a style question.

1. Why Boot Is Different

Three things make the first fetch unlike every other flash transaction.

There is nothing to debug with. No console, no operating system, often no JTAG until later in the sequence. A boot failure presents as a device that does nothing.

The device may not be in a known state. Previous software may have left the flash in a low-power mode, in 4-byte addressing mode (Chapter 10.5), or with a partially completed erase. A warm reset resets the processor and not the flash.

Nothing downstream can validate anything. Later stages check signatures and checksums, but they only run if the boot ROM hands control to them — which it does based on the very data it is meant to be validating.

So a boot sequence is not a read. It is a chain of checks, each of which must pass before the next field may be trusted.

2. The Sequence, and Why the Order Is the Design

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   1. release from power-down    the part may be asleep
   2. read and verify the ID     is this the device we were built for
   3. read the header magic      is there an image here at all
   4. verify the header checksum is the header itself intact
   5. bound the length           only NOW may the length be used
   6. fetch                      and only if everything above passed

Each step exists because the step after it depends on something the step before cannot guarantee.

Step 1 first, because a sleeping part answers nothing. A boot ROM that skips it reads zeros and concludes the flash is dead — or worse, concludes there is no image and enters a recovery mode that also needs the flash.

Step 2 before anything is read, because the ID is the only value whose correct answer is known in advance (Chapter 10.1). A wrong ID means every subsequent read is meaningless, and the failure is informative: all-ones means no device responding, a shifted value means the link works and the latency is wrong.

Step 4 before step 5. This is the one that matters most and the one most often inverted. A length read from a header whose integrity has not been checked is an arbitrary number. Using it to size a copy means a single corrupted byte in flash can specify a copy of four gigabytes into a 256 KB RAM.

3. What the Checks Actually Catch

Each check has a characteristic failure it exists for, and knowing them is what makes the sequence memorable rather than arbitrary.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   ID mismatch, all ones      no device responding, or CS not reaching it
   ID mismatch, all zeros     device unpowered, or asleep (step 1 skipped)
   ID mismatch, a real value  the WRONG part -- a second-source substitution
   ID shifted                 the link works, the latency is wrong

   magic all ones             an erased device -- never programmed
   magic all zeros            a wiped device
   magic one bit wrong        corruption, or a partial write

   checksum wrong             the header is damaged; the length is garbage
   checksum right, length 0   a corrupt header that happens to be consistent
   checksum right, too large  an image that does not fit the RAM

The last two are worth dwelling on because they demolish a comfortable assumption: a corrupt image can be internally consistent. A header whose checksum matches is not a header whose contents are sensible. So the bound on the length is a separate check from the checksum, and both are needed.

4. The Boot Path

A boot path. The processor's boot ROM drives an SPI controller which reads a serial flash. The flash holds a header containing a magic value, a length and a checksum, followed by the image. The boot ROM verifies the device identity, then the header magic, then the header checksum, then bounds the length, and only then copies the image into on-chip RAM before jumping to it. Any failed check goes to a fail-safe state instead.Boot ROMmasked, cannot be updatedSPI controllerslowest divisor, plain readOn-chip RAMthe only bound that mattersSerial flashmay be asleep, may be blankImage headermagic, length, checksumImagecopied only after everycheckCheck chainID, magic, checksum, thenlengthFail-safenever jump — report andstopissuesRDP, RDIDholdsfields checked inorderlength trusted onlyhereany failurecopiedprecedes12
Figure 1 — what the boot ROM touches. Each stage gates the next: the ID gates the header read, the header checksum gates the use of the length, and the length gates the copy into RAM. A failure at any stage stops before the copy.

5. Reading the Boot Capture

The boot sequence, frame by frame

6 cycles
Six successive boot steps shown as a bus lane: release from power-down, read identity, read the header magic, read the header length, read the header checksum, then fetch the image. A second lane shows what has been established after each step, progressing from nothing to the device, to the header, to everything.device identifieddevice identifiedheader verified — length usableheader verified — lengthusableboot stepRDPRDIDMAGICLENSUMFETCHtrusted--deviceimage?--headerallt0t1t2t3t4t5
Figure 2 — the six frames of a successful boot. Each frame's verdict gates the next, and the fetch is the only frame that touches the image.

The second lane is the point. Note that after reading the length at cell 3, nothing new is trusted — the value has been captured but not validated. Trust arrives at cell 4, when the checksum confirms the header, and only then does the length become a number the fetch may use.

6. The Sequence in Hardware — Three HDLs

The circuit

Circuit. A nine-state machine over an abstract read channel.

State. The captured magic and length, and the verdict.

Datapath. Three comparisons and one addition. The checksum is magic + length, which is weak by design and sufficient for its purpose.

Control. The six steps of §2, with every failure path leading to a state from which the fetch is unreachable. That is the structural guarantee, and it is stronger than a flag: there is no transition from a failure state into the fetch.

Clock and reset. System clock; asynchronous active-low reset.

Enables. fail_reason names which check failed, in the order the checks occur, so a boot failure indicated on a pin or an LED blink code says where rather than only that.

Timing. One read per handshake. No timeouts here, because Chapter 11.4's sequencer owns that and boot issues only reads, which do not set WIP.

Synthesis. A state machine, two 32-bit registers and three comparators. Small enough to sit in a masked boot ROM's hardware assist.

Limitations. No cryptography, and the header checksum is trivially forgeable. That is an honest scope statement rather than a defect: this block establishes that the header's fields are usable, which is a prerequisite for a signature check rather than a substitute for one.

Azvya Education Pvt. Ltd.VLSI Mentor
flash_boot_seq.sv — a chain of checks, each gating the next
// flash_boot_seq.sv
//
// Chapter 11.5 -- the boot ROM's first-fetch sequence.
//
// Booting from serial flash is the one SPI transaction that must work
// before any software exists to debug it. So the sequence is not "read the
// image" -- it is a chain of checks, each of which must pass before the
// next field may be TRUSTED:
//
//   1. release from power-down    the part may be asleep
//   2. read and verify the ID     is this the device we were built for
//   3. read the header magic      is there an image here at all
//   4. verify the header checksum is the header itself intact
//   5. bound the length           only NOW may the length be used
//   6. fetch                      and only if everything above passed
//
// The ORDER is the design. Step 5 comes after step 4 because a length read
// from a header whose checksum has not been verified is an arbitrary
// number, and using it to size a fetch means letting corrupt flash contents
// decide how much memory to overwrite. Validating the container before
// trusting its contents is the whole discipline of secure boot, and this is
// its smallest honest form.
//
// The header checksum here covers the header only -- magic plus length --
// which is deliberately weak as an integrity check and exactly strong
// enough for its job: it lets the length be trusted before it is used. A
// real design adds an image-wide hash, but that cannot be checked until
// after the fetch, so it does not remove the need for this one.
//
// On ANY failure the fetch is never issued and a reason code is reported.
// The testbench checks that by recording every opcode.

module flash_boot_seq #(
    parameter int ADDR_W = 24,
    parameter int LEN_W  = 24,
    parameter logic [7:0]  OP_RDP  = 8'hAB,   // release from power-down
    parameter logic [7:0]  OP_RDID = 8'h9F,   // read JEDEC ID
    parameter logic [7:0]  OP_READ = 8'h03,   // plain read
    parameter logic [23:0] EXPECT_ID = 24'hEF4018,
    parameter logic [31:0] MAGIC     = 32'h5A5AC0DE,
    parameter int HDR_BASE  = 24'h000000,
    parameter int IMAGE_BASE = 24'h001000,
    parameter int MAX_LEN   = 24'h040000     // 256 KB of on-chip RAM
) (
    input  logic              clk,
    input  logic              rst_n,

    input  logic              start,

    // Abstract read channel. op_data carries up to four bytes with the ack.
    output logic              op_req,
    output logic [7:0]        op_code,
    output logic [ADDR_W-1:0] op_addr,
    output logic [LEN_W-1:0]  op_len,
    input  logic              op_ack,
    input  logic [31:0]       op_data,

    output logic              busy,
    output logic              boot_ok,
    output logic              boot_fail,
    output logic [2:0]        fail_reason,
    output logic [ADDR_W-1:0] image_base,
    output logic [LEN_W-1:0]  image_len
);

    // Failure reasons, in the order they can be detected.
    localparam logic [2:0] F_NONE  = 3'd0;
    localparam logic [2:0] F_ID    = 3'd1;
    localparam logic [2:0] F_MAGIC = 3'd2;
    localparam logic [2:0] F_SUM   = 3'd3;
    localparam logic [2:0] F_LEN   = 3'd4;

    localparam logic [3:0] S_IDLE  = 4'd0;
    localparam logic [3:0] S_RDP   = 4'd1;
    localparam logic [3:0] S_ID    = 4'd2;
    localparam logic [3:0] S_MAGIC = 4'd3;
    localparam logic [3:0] S_LEN   = 4'd4;
    localparam logic [3:0] S_SUM   = 4'd5;
    localparam logic [3:0] S_FETCH = 4'd6;
    localparam logic [3:0] S_OK    = 4'd7;
    localparam logic [3:0] S_FAIL  = 4'd8;

    logic [3:0]  state;
    logic [31:0] hdr_magic;
    logic [31:0] hdr_len;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state       <= S_IDLE;
            op_req      <= 1'b0;
            op_code     <= 8'h00;
            op_addr     <= {ADDR_W{1'b0}};
            op_len      <= {LEN_W{1'b0}};
            busy        <= 1'b0;
            boot_ok     <= 1'b0;
            boot_fail   <= 1'b0;
            fail_reason <= F_NONE;
            image_base  <= ADDR_W'(IMAGE_BASE);
            image_len   <= {LEN_W{1'b0}};
            hdr_magic   <= 32'h0;
            hdr_len     <= 32'h0;
        end else begin
            op_req <= 1'b0;

            case (state)
                S_IDLE, S_OK, S_FAIL: begin
                    if (start) begin
                        // Release from power-down first. A part left asleep
                        // by previous software answers nothing at all, and a
                        // boot ROM that skips this reads zeros and blames
                        // the flash.
                        op_code     <= OP_RDP;
                        op_addr     <= {ADDR_W{1'b0}};
                        op_len      <= {LEN_W{1'b0}};
                        op_req      <= 1'b1;
                        busy        <= 1'b1;
                        boot_ok     <= 1'b0;
                        boot_fail   <= 1'b0;
                        fail_reason <= F_NONE;
                        image_len   <= {LEN_W{1'b0}};
                        state       <= S_RDP;
                    end
                end

                S_RDP: begin
                    if (op_ack) begin
                        op_code <= OP_RDID;
                        op_len  <= LEN_W'(3);
                        op_req  <= 1'b1;
                        state   <= S_ID;
                    end
                end

                S_ID: begin
                    if (op_ack) begin
                        if (op_data[23:0] == EXPECT_ID) begin
                            op_code <= OP_READ;
                            op_addr <= ADDR_W'(HDR_BASE);
                            op_len  <= LEN_W'(4);
                            op_req  <= 1'b1;
                            state   <= S_MAGIC;
                        end else begin
                            // Wrong device, or no device. Either way nothing
                            // further can be trusted.
                            fail_reason <= F_ID;
                            busy        <= 1'b0;
                            boot_fail   <= 1'b1;
                            state       <= S_FAIL;
                        end
                    end
                end

                S_MAGIC: begin
                    if (op_ack) begin
                        hdr_magic <= op_data;
                        if (op_data == MAGIC) begin
                            op_addr <= ADDR_W'(HDR_BASE + 4);
                            op_len  <= LEN_W'(4);
                            op_req  <= 1'b1;
                            state   <= S_LEN;
                        end else begin
                            // No image here. A blank device reads all-ones
                            // and a wiped one all-zeros; neither matches.
                            fail_reason <= F_MAGIC;
                            busy        <= 1'b0;
                            boot_fail   <= 1'b1;
                            state       <= S_FAIL;
                        end
                    end
                end

                S_LEN: begin
                    if (op_ack) begin
                        // The length is CAPTURED here and validated later.
                        // It is not trusted yet -- the header checksum has
                        // not been checked.
                        hdr_len <= op_data;
                        op_addr <= ADDR_W'(HDR_BASE + 8);
                        op_len  <= LEN_W'(4);
                        op_req  <= 1'b1;
                        state   <= S_SUM;
                    end
                end

                S_SUM: begin
                    if (op_ack) begin
                        if (op_data != (hdr_magic + hdr_len)) begin
                            // The header is damaged. Note what this
                            // protects: without it, the length below would
                            // be an arbitrary number from corrupt flash,
                            // deciding how much memory to overwrite.
                            fail_reason <= F_SUM;
                            busy        <= 1'b0;
                            boot_fail   <= 1'b1;
                            state       <= S_FAIL;
                        end else if (hdr_len == 32'h0 ||
                                     hdr_len > 32'(MAX_LEN)) begin
                            // Only NOW is the length trustworthy enough to
                            // bound. Zero is a corrupt header that happens
                            // to checksum; too large is an image that does
                            // not fit the RAM it would be copied into.
                            fail_reason <= F_LEN;
                            busy        <= 1'b0;
                            boot_fail   <= 1'b1;
                            state       <= S_FAIL;
                        end else begin
                            image_len <= LEN_W'(hdr_len);
                            op_code   <= OP_READ;
                            op_addr   <= ADDR_W'(IMAGE_BASE);
                            op_len    <= LEN_W'(hdr_len);
                            op_req    <= 1'b1;
                            state     <= S_FETCH;
                        end
                    end
                end

                S_FETCH: begin
                    if (op_ack) begin
                        busy    <= 1'b0;
                        boot_ok <= 1'b1;
                        state   <= S_OK;
                    end
                end

                default: ;   // unreachable
            endcase
        end
    end

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
flash_boot_seq_tb.sv — every failure injected independently
// flash_boot_seq_tb.sv
//
// The testbench models a flash whose ID, magic, length and header checksum
// are all settable, so each failure can be injected independently. The
// property that matters most is recorded, not inspected: on ANY failure,
// the image fetch must never be issued.

`timescale 1ns/1ps

module flash_boot_seq_tb;

    localparam int ADDR_W = 24;
    localparam int LEN_W  = 24;
    localparam logic [7:0]  OP_RDP  = 8'hAB;
    localparam logic [7:0]  OP_RDID = 8'h9F;
    localparam logic [7:0]  OP_READ = 8'h03;
    localparam logic [23:0] GOOD_ID = 24'hEF4018;
    localparam logic [31:0] MAGIC   = 32'h5A5AC0DE;
    localparam int HDR_BASE   = 24'h000000;
    localparam int IMAGE_BASE = 24'h001000;
    localparam int MAX_LEN    = 24'h040000;

    localparam logic [2:0] F_NONE  = 3'd0;
    localparam logic [2:0] F_ID    = 3'd1;
    localparam logic [2:0] F_MAGIC = 3'd2;
    localparam logic [2:0] F_SUM   = 3'd3;
    localparam logic [2:0] F_LEN   = 3'd4;

    logic clk = 1'b0;
    logic rst_n = 1'b0;
    always #5 clk = ~clk;

    logic              start = 1'b0;
    logic              op_req;
    logic [7:0]        op_code;
    logic [ADDR_W-1:0] op_addr;
    logic [LEN_W-1:0]  op_len;
    logic              op_ack;
    logic [31:0]       op_data;

    logic              busy, boot_ok, boot_fail;
    logic [2:0]        fail_reason;
    logic [ADDR_W-1:0] image_base;
    logic [LEN_W-1:0]  image_len;

    int errors = 0;

    // ---- settable flash contents ----------------------------------------
    logic [23:0] f_id    = GOOD_ID;
    logic [31:0] f_magic = MAGIC;
    logic [31:0] f_len   = 32'h00002000;      // 8 KB image
    logic [31:0] f_sum   = MAGIC + 32'h00002000;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            op_ack  <= 1'b0;
            op_data <= 32'h0;
        end else begin
            op_ack <= op_req;
            if (op_req) begin
                case (op_code)
                    OP_RDP:  op_data <= 32'h0;
                    OP_RDID: op_data <= {8'h00, f_id};
                    OP_READ: begin
                        case (op_addr)
                            ADDR_W'(HDR_BASE):     op_data <= f_magic;
                            ADDR_W'(HDR_BASE + 4): op_data <= f_len;
                            ADDR_W'(HDR_BASE + 8): op_data <= f_sum;
                            default:               op_data <= 32'hDEADBEEF;
                        endcase
                    end
                    default: op_data <= 32'h0;
                endcase
            end
        end
    end

    flash_boot_seq #(
        .ADDR_W(ADDR_W), .LEN_W(LEN_W),
        .OP_RDP(OP_RDP), .OP_RDID(OP_RDID), .OP_READ(OP_READ),
        .EXPECT_ID(GOOD_ID), .MAGIC(MAGIC),
        .HDR_BASE(HDR_BASE), .IMAGE_BASE(IMAGE_BASE), .MAX_LEN(MAX_LEN)
    ) dut (
        .clk(clk), .rst_n(rst_n), .start(start),
        .op_req(op_req), .op_code(op_code), .op_addr(op_addr),
        .op_len(op_len), .op_ack(op_ack), .op_data(op_data),
        .busy(busy), .boot_ok(boot_ok), .boot_fail(boot_fail),
        .fail_reason(fail_reason),
        .image_base(image_base), .image_len(image_len)
    );

    // ---- recorder: did a FETCH ever happen? ------------------------------
    // A fetch is a read of the image base. Recording it is the only way to
    // check the safety property, since a fetch that happened and was then
    // discarded is indistinguishable from one that never happened by
    // looking at outputs alone.
    bit        saw_fetch = 1'b0;
    bit        saw_rdp   = 1'b0;
    int        n_ops     = 0;
    logic [LEN_W-1:0] fetch_len = {LEN_W{1'b0}};

    always_ff @(posedge clk) begin
        if (rst_n && op_req) begin
            n_ops <= n_ops + 1;
            if (op_code == OP_RDP) saw_rdp <= 1'b1;
            if (op_code == OP_READ && op_addr == ADDR_W'(IMAGE_BASE)) begin
                saw_fetch <= 1'b1;
                fetch_len <= op_len;
            end
        end
    end

    task automatic boot(input int limit);
        int guard;
        begin
            @(negedge clk);
            saw_fetch = 1'b0; saw_rdp = 1'b0; n_ops = 0;
            fetch_len = {LEN_W{1'b0}};
            start = 1'b1;
            @(negedge clk);
            start = 1'b0;
            guard = 0;
            while (busy && guard < limit) begin
                @(negedge clk);
                guard++;
            end
            if (guard >= limit) begin
                $display("  FAIL: boot never finished within %0d cycles", limit);
                errors++;
            end
            @(negedge clk);
        end
    endtask

    task automatic expect_fail(input string what, input logic [2:0] why);
        begin
            boot(500);
            if (!boot_fail || boot_ok) begin
                $display("  FAIL: %s did not fail the boot (fail=%0b ok=%0b)",
                         what, boot_fail, boot_ok);
                errors++;
            end
            if (fail_reason !== why) begin
                $display("  FAIL: %s reported reason %0d, expected %0d",
                         what, fail_reason, why);
                errors++;
            end
            // THE SAFETY PROPERTY.
            if (saw_fetch) begin
                $display("  FAIL: %s still fetched the image", what);
                errors++;
            end
            $display("  %-26s reason=%0d, %0d opcodes, image never fetched",
                     what, fail_reason, n_ops);
        end
    endtask

    initial begin
        repeat (3) @(negedge clk);
        rst_n = 1'b1;
        @(negedge clk);

        // 1. The happy path. Note what must be true: the part is woken
        //    first, the ID is checked, the header is validated, and only
        //    then is the image fetched -- with the length from the header.
        boot(500);
        if (!boot_ok || boot_fail) begin
            $display("  FAIL: a good device did not boot (ok=%0b fail=%0b reason=%0d)",
                     boot_ok, boot_fail, fail_reason);
            errors++;
        end
        if (!saw_rdp) begin
            $display("  FAIL: the part was never released from power-down");
            errors++;
        end
        if (!saw_fetch) begin
            $display("  FAIL: a good boot never fetched the image"); errors++;
        end
        if (image_len !== LEN_W'(24'h002000)) begin
            $display("  FAIL: image_len is 0x%06h, expected 0x002000", image_len);
            errors++;
        end
        if (fetch_len !== LEN_W'(24'h002000)) begin
            $display("  FAIL: the fetch requested 0x%06h bytes, header said 0x002000",
                     fetch_len);
            errors++;
        end
        if (image_base !== ADDR_W'(IMAGE_BASE)) begin
            $display("  FAIL: image_base is 0x%06h", image_base); errors++;
        end
        $display("  good device: booted, %0d opcodes, fetched 0x%06h bytes from 0x%06h",
                 n_ops, fetch_len, image_base);

        // 2. A wrong or absent device. A blank bus reads all-ones; an
        //    unpowered one all-zeros. Neither matches the expected ID.
        f_id = 24'hFFFFFF;
        expect_fail("wrong ID (all ones)", F_ID);
        f_id = 24'h000000;
        expect_fail("no device (all zeros)", F_ID);
        f_id = 24'hEF4017;                 // a real but DIFFERENT part
        expect_fail("different part", F_ID);
        f_id = GOOD_ID;

        // 3. A device with no image. An erased flash reads all-ones.
        f_magic = 32'hFFFFFFFF;
        expect_fail("erased flash (no magic)", F_MAGIC);
        f_magic = 32'h5A5AC0DF;            // one bit wrong
        expect_fail("magic off by one bit", F_MAGIC);
        f_magic = MAGIC;

        // 4. A damaged header. THIS is the check that protects the length:
        //    without it the number below would size a memory copy.
        f_len = 32'h00002000; f_sum = 32'h00000000;
        expect_fail("header checksum wrong", F_SUM);

        // 5. A header that checksums but carries an impossible length. Both
        //    ends of the bound, and note that the checksum is CONSISTENT in
        //    both cases -- a corrupt image can be internally consistent.
        f_len = 32'h00000000; f_sum = MAGIC + 32'h00000000;
        expect_fail("zero-length image", F_LEN);
        f_len = 32'h00040001; f_sum = MAGIC + 32'h00040001;
        expect_fail("image larger than RAM", F_LEN);

        // 6. The exact boundary. MAX_LEN must be accepted and MAX_LEN+1
        //    rejected -- one apart, opposite outcomes.
        f_len = 32'(MAX_LEN); f_sum = MAGIC + 32'(MAX_LEN);
        boot(500);
        if (!boot_ok) begin
            $display("  FAIL: an image of exactly MAX_LEN was rejected"); errors++;
        end
        $display("  exactly MAX_LEN (0x%06h): accepted", MAX_LEN);
        f_len = 32'(MAX_LEN) + 32'h1; f_sum = MAGIC + 32'(MAX_LEN) + 32'h1;
        expect_fail("one byte over MAX_LEN", F_LEN);

        // 7. Recovery. A good device after every kind of failure must boot
        //    -- the verdict must not be sticky across a new attempt.
        f_len = 32'h00001000; f_sum = MAGIC + 32'h00001000;
        boot(500);
        if (!boot_ok || boot_fail || fail_reason !== F_NONE) begin
            $display("  FAIL: a good device after failures did not boot cleanly");
            errors++;
        end
        $display("  recovery after six failures: booted, fetched 0x%06h bytes",
                 fetch_len);

        if (errors == 0)
            $display("PASS: the part is released from power-down before anything else, a wrong or absent device is rejected on its ID, an erased or damaged header is rejected on its magic, the header checksum is verified BEFORE the length is trusted, the length bound is exact at both ends, the image is never fetched after any failure, and a good device boots cleanly afterwards");
        else
            $display("FAIL: %0d error(s)", errors);
        $finish;
    end

endmodule

The testbench models a flash whose ID, magic, length and checksum are each independently settable, so every check can be failed on its own — and it records whether a fetch was ever issued, because that is the only way to verify the safety property. An output-only check cannot distinguish a fetch that happened and was discarded from one that never happened.

The output is worth reading as a table, because the opcode counts tell the story:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   good device                6 opcodes, image fetched
   wrong ID (all ones)        2 opcodes, never fetched
   no device (all zeros)      2 opcodes, never fetched
   different part             2 opcodes, never fetched
   erased flash (no magic)    3 opcodes, never fetched
   magic off by one bit       3 opcodes, never fetched
   header checksum wrong      5 opcodes, never fetched
   zero-length image          5 opcodes, never fetched
   image larger than RAM      5 opcodes, never fetched

Each failure stops at exactly the step that detects it — two opcodes for an ID failure, three for a magic failure, five for a header or length failure — and none reaches the sixth. The counts are evidence that the checks are ordered as claimed rather than all performed at the end.

Two boundary cases complete it. An image of exactly MAX_LEN is accepted and one byte more is rejected — one apart, opposite outcomes, which is the pair a comparison written with the wrong relational operator gets wrong in exactly one direction. And a good device after all nine failures boots cleanly, proving the verdict is not sticky.

Azvya Education Pvt. Ltd.VLSI Mentor
flash_boot_seq.v — the same sequence in Verilog-2001
// flash_boot_seq.v
//
// Chapter 11.5 -- the boot ROM's first-fetch sequence, in Verilog-2001.
//
// Booting from serial flash must work before any software exists to debug
// it, so the sequence is a chain of checks, each of which must pass before
// the next field may be TRUSTED:
//
//   1. release from power-down    the part may be asleep
//   2. read and verify the ID     is this the device we were built for
//   3. read the header magic      is there an image here at all
//   4. verify the header checksum is the header itself intact
//   5. bound the length           only NOW may the length be used
//   6. fetch                      and only if everything above passed
//
// The ORDER is the design. Step 5 follows step 4 because a length read from
// an unverified header is an arbitrary number, and using it to size a fetch
// lets corrupt flash decide how much memory to overwrite.
//
// On ANY failure the fetch is never issued and a reason code is reported.

module flash_boot_seq #(
    parameter ADDR_W = 24,
    parameter LEN_W  = 24,
    parameter [7:0]  OP_RDP  = 8'hAB,   // release from power-down
    parameter [7:0]  OP_RDID = 8'h9F,   // read JEDEC ID
    parameter [7:0]  OP_READ = 8'h03,   // plain read
    parameter [23:0] EXPECT_ID = 24'hEF4018,
    parameter [31:0] MAGIC     = 32'h5A5AC0DE,
    parameter HDR_BASE   = 24'h000000,
    parameter IMAGE_BASE = 24'h001000,
    parameter MAX_LEN    = 24'h040000   // 256 KB of on-chip RAM
) (
    input  wire              clk,
    input  wire              rst_n,

    input  wire              start,

    // Abstract read channel. op_data carries up to four bytes with the ack.
    output reg               op_req,
    output reg  [7:0]        op_code,
    output reg  [ADDR_W-1:0] op_addr,
    output reg  [LEN_W-1:0]  op_len,
    input  wire              op_ack,
    input  wire [31:0]       op_data,

    output reg               busy,
    output reg               boot_ok,
    output reg               boot_fail,
    output reg  [2:0]        fail_reason,
    output reg  [ADDR_W-1:0] image_base,
    output reg  [LEN_W-1:0]  image_len
);

    // Failure reasons, in the order they can be detected.
    localparam [2:0] F_NONE  = 3'd0;
    localparam [2:0] F_ID    = 3'd1;
    localparam [2:0] F_MAGIC = 3'd2;
    localparam [2:0] F_SUM   = 3'd3;
    localparam [2:0] F_LEN   = 3'd4;

    localparam [3:0] S_IDLE  = 4'd0;
    localparam [3:0] S_RDP   = 4'd1;
    localparam [3:0] S_ID    = 4'd2;
    localparam [3:0] S_MAGIC = 4'd3;
    localparam [3:0] S_LEN   = 4'd4;
    localparam [3:0] S_SUM   = 4'd5;
    localparam [3:0] S_FETCH = 4'd6;
    localparam [3:0] S_OK    = 4'd7;
    localparam [3:0] S_FAIL  = 4'd8;

    reg [3:0]  state;
    reg [31:0] hdr_magic;
    reg [31:0] hdr_len;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state       <= S_IDLE;
            op_req      <= 1'b0;
            op_code     <= 8'h00;
            op_addr     <= {ADDR_W{1'b0}};
            op_len      <= {LEN_W{1'b0}};
            busy        <= 1'b0;
            boot_ok     <= 1'b0;
            boot_fail   <= 1'b0;
            fail_reason <= F_NONE;
            image_base  <= IMAGE_BASE;
            image_len   <= {LEN_W{1'b0}};
            hdr_magic   <= 32'h0;
            hdr_len     <= 32'h0;
        end else begin
            op_req <= 1'b0;

            case (state)
                S_IDLE, S_OK, S_FAIL: begin
                    if (start) begin
                        // Release from power-down first. A part left asleep
                        // by previous software answers nothing at all, and a
                        // boot ROM that skips this reads zeros and blames
                        // the flash.
                        op_code     <= OP_RDP;
                        op_addr     <= {ADDR_W{1'b0}};
                        op_len      <= {LEN_W{1'b0}};
                        op_req      <= 1'b1;
                        busy        <= 1'b1;
                        boot_ok     <= 1'b0;
                        boot_fail   <= 1'b0;
                        fail_reason <= F_NONE;
                        image_len   <= {LEN_W{1'b0}};
                        state       <= S_RDP;
                    end
                end

                S_RDP: begin
                    if (op_ack) begin
                        op_code <= OP_RDID;
                        op_len  <= 3;
                        op_req  <= 1'b1;
                        state   <= S_ID;
                    end
                end

                S_ID: begin
                    if (op_ack) begin
                        if (op_data[23:0] == EXPECT_ID) begin
                            op_code <= OP_READ;
                            op_addr <= HDR_BASE;
                            op_len  <= 4;
                            op_req  <= 1'b1;
                            state   <= S_MAGIC;
                        end else begin
                            // Wrong device, or no device. Either way nothing
                            // further can be trusted.
                            fail_reason <= F_ID;
                            busy        <= 1'b0;
                            boot_fail   <= 1'b1;
                            state       <= S_FAIL;
                        end
                    end
                end

                S_MAGIC: begin
                    if (op_ack) begin
                        hdr_magic <= op_data;
                        if (op_data == MAGIC) begin
                            op_addr <= HDR_BASE + 4;
                            op_len  <= 4;
                            op_req  <= 1'b1;
                            state   <= S_LEN;
                        end else begin
                            // No image here. A blank device reads all-ones
                            // and a wiped one all-zeros; neither matches.
                            fail_reason <= F_MAGIC;
                            busy        <= 1'b0;
                            boot_fail   <= 1'b1;
                            state       <= S_FAIL;
                        end
                    end
                end

                S_LEN: begin
                    if (op_ack) begin
                        // The length is CAPTURED here and validated later.
                        // It is not trusted yet -- the header checksum has
                        // not been checked.
                        hdr_len <= op_data;
                        op_addr <= HDR_BASE + 8;
                        op_len  <= 4;
                        op_req  <= 1'b1;
                        state   <= S_SUM;
                    end
                end

                S_SUM: begin
                    if (op_ack) begin
                        if (op_data != (hdr_magic + hdr_len)) begin
                            // The header is damaged. Without this check the
                            // length below would be an arbitrary number
                            // from corrupt flash, deciding how much memory
                            // to overwrite.
                            fail_reason <= F_SUM;
                            busy        <= 1'b0;
                            boot_fail   <= 1'b1;
                            state       <= S_FAIL;
                        end else if (hdr_len == 32'h0 || hdr_len > MAX_LEN) begin
                            // Only NOW is the length trustworthy enough to
                            // bound. Zero is a corrupt header that happens
                            // to checksum; too large does not fit the RAM.
                            fail_reason <= F_LEN;
                            busy        <= 1'b0;
                            boot_fail   <= 1'b1;
                            state       <= S_FAIL;
                        end else begin
                            image_len <= hdr_len[LEN_W-1:0];
                            op_code   <= OP_READ;
                            op_addr   <= IMAGE_BASE;
                            op_len    <= hdr_len[LEN_W-1:0];
                            op_req    <= 1'b1;
                            state     <= S_FETCH;
                        end
                    end
                end

                S_FETCH: begin
                    if (op_ack) begin
                        busy    <= 1'b0;
                        boot_ok <= 1'b1;
                        state   <= S_OK;
                    end
                end

                default: ;   // unreachable
            endcase
        end
    end

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
flash_boot_seq_tb.v — the same injected failures in Verilog-2001
// flash_boot_seq_tb.v
//
// The same checks as the SystemVerilog testbench: a flash whose ID, magic,
// length and header checksum are all settable so each failure can be
// injected independently, and a recorded fetch so the safety property can
// be checked rather than assumed.

`timescale 1ns/1ps

module flash_boot_seq_tb;

    parameter ADDR_W = 24;
    parameter LEN_W  = 24;
    localparam [7:0]  OP_RDP  = 8'hAB;
    localparam [7:0]  OP_RDID = 8'h9F;
    localparam [7:0]  OP_READ = 8'h03;
    localparam [23:0] GOOD_ID = 24'hEF4018;
    localparam [31:0] MAGIC   = 32'h5A5AC0DE;
    localparam HDR_BASE   = 24'h000000;
    localparam IMAGE_BASE = 24'h001000;
    localparam MAX_LEN    = 24'h040000;

    localparam [2:0] F_NONE  = 3'd0;
    localparam [2:0] F_ID    = 3'd1;
    localparam [2:0] F_MAGIC = 3'd2;
    localparam [2:0] F_SUM   = 3'd3;
    localparam [2:0] F_LEN   = 3'd4;

    reg clk;
    reg rst_n;
    reg start;

    wire              op_req;
    wire [7:0]        op_code;
    wire [ADDR_W-1:0] op_addr;
    wire [LEN_W-1:0]  op_len;
    reg               op_ack;
    reg  [31:0]       op_data;

    wire              busy, boot_ok, boot_fail;
    wire [2:0]        fail_reason;
    wire [ADDR_W-1:0] image_base;
    wire [LEN_W-1:0]  image_len;

    integer errors;
    integer guard;

    // ---- settable flash contents ----------------------------------------
    reg [23:0] f_id;
    reg [31:0] f_magic;
    reg [31:0] f_len;
    reg [31:0] f_sum;

    initial begin
        clk = 1'b0; rst_n = 1'b0; start = 1'b0;
        op_ack = 1'b0; op_data = 32'h0;
        f_id = GOOD_ID; f_magic = MAGIC;
        f_len = 32'h00002000; f_sum = MAGIC + 32'h00002000;
        errors = 0;
    end
    always #5 clk = ~clk;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            op_ack  <= 1'b0;
            op_data <= 32'h0;
        end else begin
            op_ack <= op_req;
            if (op_req) begin
                case (op_code)
                    OP_RDP:  op_data <= 32'h0;
                    OP_RDID: op_data <= {8'h00, f_id};
                    OP_READ: begin
                        case (op_addr)
                            HDR_BASE:     op_data <= f_magic;
                            HDR_BASE + 4: op_data <= f_len;
                            HDR_BASE + 8: op_data <= f_sum;
                            default:      op_data <= 32'hDEADBEEF;
                        endcase
                    end
                    default: op_data <= 32'h0;
                endcase
            end
        end
    end

    flash_boot_seq #(
        .ADDR_W(ADDR_W), .LEN_W(LEN_W),
        .OP_RDP(OP_RDP), .OP_RDID(OP_RDID), .OP_READ(OP_READ),
        .EXPECT_ID(GOOD_ID), .MAGIC(MAGIC),
        .HDR_BASE(HDR_BASE), .IMAGE_BASE(IMAGE_BASE), .MAX_LEN(MAX_LEN)
    ) dut (
        .clk(clk), .rst_n(rst_n), .start(start),
        .op_req(op_req), .op_code(op_code), .op_addr(op_addr),
        .op_len(op_len), .op_ack(op_ack), .op_data(op_data),
        .busy(busy), .boot_ok(boot_ok), .boot_fail(boot_fail),
        .fail_reason(fail_reason),
        .image_base(image_base), .image_len(image_len)
    );

    // ---- recorder: did a FETCH ever happen? ------------------------------
    reg              saw_fetch;
    reg              saw_rdp;
    integer          n_ops;
    reg [LEN_W-1:0]  fetch_len;

    initial begin
        saw_fetch = 1'b0; saw_rdp = 1'b0; n_ops = 0;
        fetch_len = {LEN_W{1'b0}};
    end

    always @(posedge clk) begin
        if (rst_n && op_req) begin
            n_ops <= n_ops + 1;
            if (op_code == OP_RDP) saw_rdp <= 1'b1;
            if (op_code == OP_READ && op_addr == IMAGE_BASE) begin
                saw_fetch <= 1'b1;
                fetch_len <= op_len;
            end
        end
    end

    task boot;
        input integer limit;
        begin
            @(negedge clk);
            saw_fetch = 1'b0; saw_rdp = 1'b0; n_ops = 0;
            fetch_len = {LEN_W{1'b0}};
            start = 1'b1;
            @(negedge clk);
            start = 1'b0;
            guard = 0;
            while (busy && guard < limit) begin
                @(negedge clk);
                guard = guard + 1;
            end
            if (guard >= limit) begin
                $display("  FAIL: boot never finished within %0d cycles", limit);
                errors = errors + 1;
            end
            @(negedge clk);
        end
    endtask

    task expect_fail;
        input [8*26:1] what;
        input [2:0]    why;
        begin
            boot(500);
            if (!boot_fail || boot_ok) begin
                $display("  FAIL: %0s did not fail the boot", what);
                errors = errors + 1;
            end
            if (fail_reason !== why) begin
                $display("  FAIL: %0s reported reason %0d, expected %0d",
                         what, fail_reason, why);
                errors = errors + 1;
            end
            // THE SAFETY PROPERTY.
            if (saw_fetch) begin
                $display("  FAIL: %0s still fetched the image", what);
                errors = errors + 1;
            end
            $display("  %0s reason=%0d, %0d opcodes, image never fetched",
                     what, fail_reason, n_ops);
        end
    endtask

    initial begin
        repeat (3) @(negedge clk);
        rst_n = 1'b1;
        @(negedge clk);

        // 1. The happy path.
        boot(500);
        if (!boot_ok || boot_fail) begin
            $display("  FAIL: a good device did not boot (ok=%0b fail=%0b reason=%0d)",
                     boot_ok, boot_fail, fail_reason);
            errors = errors + 1;
        end
        if (!saw_rdp) begin
            $display("  FAIL: the part was never released from power-down");
            errors = errors + 1;
        end
        if (!saw_fetch) begin
            $display("  FAIL: a good boot never fetched the image");
            errors = errors + 1;
        end
        if (image_len !== 24'h002000) begin
            $display("  FAIL: image_len is 0x%06h, expected 0x002000", image_len);
            errors = errors + 1;
        end
        if (fetch_len !== 24'h002000) begin
            $display("  FAIL: the fetch requested 0x%06h bytes, header said 0x002000",
                     fetch_len);
            errors = errors + 1;
        end
        if (image_base !== IMAGE_BASE) begin
            $display("  FAIL: image_base is 0x%06h", image_base);
            errors = errors + 1;
        end
        $display("  good device: booted, %0d opcodes, fetched 0x%06h bytes from 0x%06h",
                 n_ops, fetch_len, image_base);

        // 2. A wrong or absent device.
        f_id = 24'hFFFFFF;
        expect_fail("wrong ID (all ones)      ", F_ID);
        f_id = 24'h000000;
        expect_fail("no device (all zeros)    ", F_ID);
        f_id = 24'hEF4017;                 // a real but DIFFERENT part
        expect_fail("different part           ", F_ID);
        f_id = GOOD_ID;

        // 3. A device with no image.
        f_magic = 32'hFFFFFFFF;
        expect_fail("erased flash (no magic)  ", F_MAGIC);
        f_magic = 32'h5A5AC0DF;            // one bit wrong
        expect_fail("magic off by one bit     ", F_MAGIC);
        f_magic = MAGIC;

        // 4. A damaged header -- the check that protects the length.
        f_len = 32'h00002000; f_sum = 32'h00000000;
        expect_fail("header checksum wrong    ", F_SUM);

        // 5. A header that checksums but carries an impossible length. Note
        //    the checksum is CONSISTENT in both cases: a corrupt image can
        //    be internally consistent.
        f_len = 32'h00000000; f_sum = MAGIC + 32'h00000000;
        expect_fail("zero-length image        ", F_LEN);
        f_len = 32'h00040001; f_sum = MAGIC + 32'h00040001;
        expect_fail("image larger than RAM    ", F_LEN);

        // 6. The exact boundary: MAX_LEN accepted, MAX_LEN+1 rejected.
        f_len = MAX_LEN; f_sum = MAGIC + MAX_LEN;
        boot(500);
        if (!boot_ok) begin
            $display("  FAIL: an image of exactly MAX_LEN was rejected");
            errors = errors + 1;
        end
        $display("  exactly MAX_LEN (0x%06h): accepted", MAX_LEN);
        f_len = MAX_LEN + 1; f_sum = MAGIC + MAX_LEN + 1;
        expect_fail("one byte over MAX_LEN    ", F_LEN);

        // 7. Recovery after every kind of failure.
        f_len = 32'h00001000; f_sum = MAGIC + 32'h00001000;
        boot(500);
        if (!boot_ok || boot_fail || fail_reason !== F_NONE) begin
            $display("  FAIL: a good device after failures did not boot cleanly");
            errors = errors + 1;
        end
        $display("  recovery after six failures: booted, fetched 0x%06h bytes",
                 fetch_len);

        if (errors == 0)
            $display("PASS: the part is released from power-down before anything else, a wrong or absent device is rejected on its ID, an erased or damaged header is rejected on its magic, the header checksum is verified BEFORE the length is trusted, the length bound is exact at both ends, the image is never fetched after any failure, and a good device boots cleanly afterwards");
        else
            $display("FAIL: %0d error(s)", errors);
        $finish;
    end

endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
flash_boot_seq.vhd — the same sequence in VHDL
-- flash_boot_seq.vhd
--
-- Chapter 11.5 -- the boot ROM's first-fetch sequence, in VHDL.
--
-- Booting from serial flash must work before any software exists to debug
-- it, so the sequence is a chain of checks, each of which must pass before
-- the next field may be TRUSTED:
--
--   1. release from power-down    the part may be asleep
--   2. read and verify the ID     is this the device we were built for
--   3. read the header magic      is there an image here at all
--   4. verify the header checksum is the header itself intact
--   5. bound the length           only NOW may the length be used
--   6. fetch                      and only if everything above passed
--
-- The ORDER is the design. Step 5 follows step 4 because a length read from
-- an unverified header is an arbitrary number, and using it to size a fetch
-- lets corrupt flash decide how much memory to overwrite.
--
-- On ANY failure the fetch is never issued and a reason code is reported.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity flash_boot_seq is
    generic (
        ADDR_W     : positive := 24;
        LEN_W      : positive := 24;
        OP_RDP     : natural  := 16#AB#;      -- release from power-down
        OP_RDID    : natural  := 16#9F#;      -- read JEDEC ID
        OP_READ    : natural  := 16#03#;      -- plain read
        EXPECT_ID  : natural  := 16#EF4018#;
        MAGIC      : natural  := 16#5A5AC0DE#;
        HDR_BASE   : natural  := 16#000000#;
        IMG_BASE : natural  := 16#001000#;
        MAX_LEN    : natural  := 16#040000#   -- 256 KB of on-chip RAM
    );
    port (
        clk         : in  std_logic;
        rst_n       : in  std_logic;

        start       : in  std_logic;

        -- Abstract read channel. op_data carries up to four bytes with ack.
        op_req      : out std_logic;
        op_code     : out unsigned(7 downto 0);
        op_addr     : out unsigned(ADDR_W - 1 downto 0);
        op_len      : out unsigned(LEN_W - 1 downto 0);
        op_ack      : in  std_logic;
        op_data     : in  unsigned(31 downto 0);

        busy        : out std_logic;
        boot_ok     : out std_logic;
        boot_fail   : out std_logic;
        fail_reason : out unsigned(2 downto 0);
        image_base  : out unsigned(ADDR_W - 1 downto 0);
        image_len   : out unsigned(LEN_W - 1 downto 0)
    );
end entity;

architecture rtl of flash_boot_seq is

    -- Failure reasons, in the order they can be detected.
    constant F_NONE  : unsigned(2 downto 0) := "000";
    constant F_ID    : unsigned(2 downto 0) := "001";
    constant F_MAGIC : unsigned(2 downto 0) := "010";
    constant F_SUM   : unsigned(2 downto 0) := "011";
    constant F_LEN   : unsigned(2 downto 0) := "100";

    type state_t is (S_IDLE, S_RDP, S_ID, S_MAGIC, S_LEN, S_SUM, S_FETCH,
                     S_OK, S_FAIL);
    signal state : state_t := S_IDLE;

    signal hdr_magic : unsigned(31 downto 0) := (others => '0');
    signal hdr_len   : unsigned(31 downto 0) := (others => '0');

    signal req_r  : std_logic := '0';
    signal code_r : unsigned(7 downto 0) := (others => '0');
    signal addr_r : unsigned(ADDR_W - 1 downto 0) := (others => '0');
    signal len_r  : unsigned(LEN_W - 1 downto 0) := (others => '0');
    signal busy_r : std_logic := '0';
    signal ok_r   : std_logic := '0';
    signal fail_r : std_logic := '0';
    signal why_r  : unsigned(2 downto 0) := F_NONE;
    signal ilen_r : unsigned(LEN_W - 1 downto 0) := (others => '0');

begin

    seq : process (clk, rst_n)
    begin
        if rst_n = '0' then
            state     <= S_IDLE;
            req_r     <= '0';
            code_r    <= (others => '0');
            addr_r    <= (others => '0');
            len_r     <= (others => '0');
            busy_r    <= '0';
            ok_r      <= '0';
            fail_r    <= '0';
            why_r     <= F_NONE;
            ilen_r    <= (others => '0');
            hdr_magic <= (others => '0');
            hdr_len   <= (others => '0');
        elsif rising_edge(clk) then
            req_r <= '0';

            case state is
                when S_IDLE | S_OK | S_FAIL =>
                    if start = '1' then
                        -- Release from power-down first. A part left asleep
                        -- by previous software answers nothing at all, and a
                        -- boot ROM that skips this reads zeros and blames
                        -- the flash.
                        code_r <= to_unsigned(OP_RDP, 8);
                        addr_r <= (others => '0');
                        len_r  <= (others => '0');
                        req_r  <= '1';
                        busy_r <= '1';
                        ok_r   <= '0';
                        fail_r <= '0';
                        why_r  <= F_NONE;
                        ilen_r <= (others => '0');
                        state  <= S_RDP;
                    end if;

                when S_RDP =>
                    if op_ack = '1' then
                        code_r <= to_unsigned(OP_RDID, 8);
                        len_r  <= to_unsigned(3, LEN_W);
                        req_r  <= '1';
                        state  <= S_ID;
                    end if;

                when S_ID =>
                    if op_ack = '1' then
                        if op_data(23 downto 0) =
                           to_unsigned(EXPECT_ID, 24) then
                            code_r <= to_unsigned(OP_READ, 8);
                            addr_r <= to_unsigned(HDR_BASE, ADDR_W);
                            len_r  <= to_unsigned(4, LEN_W);
                            req_r  <= '1';
                            state  <= S_MAGIC;
                        else
                            -- Wrong device, or no device. Either way nothing
                            -- further can be trusted.
                            why_r  <= F_ID;
                            busy_r <= '0';
                            fail_r <= '1';
                            state  <= S_FAIL;
                        end if;
                    end if;

                when S_MAGIC =>
                    if op_ack = '1' then
                        hdr_magic <= op_data;
                        if op_data = to_unsigned(MAGIC, 32) then
                            addr_r <= to_unsigned(HDR_BASE + 4, ADDR_W);
                            len_r  <= to_unsigned(4, LEN_W);
                            req_r  <= '1';
                            state  <= S_LEN;
                        else
                            -- No image here. A blank device reads all-ones
                            -- and a wiped one all-zeros; neither matches.
                            why_r  <= F_MAGIC;
                            busy_r <= '0';
                            fail_r <= '1';
                            state  <= S_FAIL;
                        end if;
                    end if;

                when S_LEN =>
                    if op_ack = '1' then
                        -- The length is CAPTURED here and validated later.
                        -- It is not trusted yet -- the header checksum has
                        -- not been checked.
                        hdr_len <= op_data;
                        addr_r  <= to_unsigned(HDR_BASE + 8, ADDR_W);
                        len_r   <= to_unsigned(4, LEN_W);
                        req_r   <= '1';
                        state   <= S_SUM;
                    end if;

                when S_SUM =>
                    if op_ack = '1' then
                        if op_data /= (hdr_magic + hdr_len) then
                            -- The header is damaged. Without this check the
                            -- length below would be an arbitrary number
                            -- from corrupt flash, deciding how much memory
                            -- to overwrite.
                            why_r  <= F_SUM;
                            busy_r <= '0';
                            fail_r <= '1';
                            state  <= S_FAIL;
                        elsif hdr_len = 0 or
                              to_integer(hdr_len) > MAX_LEN then
                            -- Only NOW is the length trustworthy enough to
                            -- bound. Zero is a corrupt header that happens
                            -- to checksum; too large does not fit the RAM.
                            why_r  <= F_LEN;
                            busy_r <= '0';
                            fail_r <= '1';
                            state  <= S_FAIL;
                        else
                            ilen_r <= resize(hdr_len, LEN_W);
                            code_r <= to_unsigned(OP_READ, 8);
                            addr_r <= to_unsigned(IMG_BASE, ADDR_W);
                            len_r  <= resize(hdr_len, LEN_W);
                            req_r  <= '1';
                            state  <= S_FETCH;
                        end if;
                    end if;

                when S_FETCH =>
                    if op_ack = '1' then
                        busy_r <= '0';
                        ok_r   <= '1';
                        state  <= S_OK;
                    end if;
            end case;
        end if;
    end process;

    op_req      <= req_r;
    op_code     <= code_r;
    op_addr     <= addr_r;
    op_len      <= len_r;
    busy        <= busy_r;
    boot_ok     <= ok_r;
    boot_fail   <= fail_r;
    fail_reason <= why_r;
    image_base  <= to_unsigned(IMG_BASE, ADDR_W);
    image_len   <= ilen_r;

end architecture;
Azvya Education Pvt. Ltd.VLSI Mentor
flash_boot_seq_tb.vhd — the same injected failures in VHDL
-- flash_boot_seq_tb.vhd
--
-- The same checks as the SystemVerilog and Verilog testbenches: a flash
-- whose ID, magic, length and header checksum are all settable so each
-- failure can be injected independently, and a recorded fetch so the safety
-- property can be checked rather than assumed.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity flash_boot_seq_tb is
end entity;

architecture sim of flash_boot_seq_tb is

    constant ADDR_W     : positive := 24;
    constant LEN_W      : positive := 24;
    constant OP_RDP     : natural  := 16#AB#;
    constant OP_RDID    : natural  := 16#9F#;
    constant OP_READ    : natural  := 16#03#;
    constant GOOD_ID    : natural  := 16#EF4018#;
    constant MAGIC      : natural  := 16#5A5AC0DE#;
    constant HDR_BASE   : natural  := 16#000000#;
    constant IMG_BASE : natural  := 16#001000#;
    constant MAX_LEN    : natural  := 16#040000#;

    constant F_NONE  : unsigned(2 downto 0) := "000";
    constant F_ID    : unsigned(2 downto 0) := "001";
    constant F_MAGIC : unsigned(2 downto 0) := "010";
    constant F_SUM   : unsigned(2 downto 0) := "011";
    constant F_LEN   : unsigned(2 downto 0) := "100";

    signal clk   : std_logic := '0';
    signal rst_n : std_logic := '0';
    signal halt  : boolean   := false;
    signal start : std_logic := '0';

    signal op_req   : std_logic;
    signal op_code  : unsigned(7 downto 0);
    signal op_addr  : unsigned(ADDR_W - 1 downto 0);
    signal op_len   : unsigned(LEN_W - 1 downto 0);
    signal op_ack   : std_logic := '0';
    signal op_data  : unsigned(31 downto 0) := (others => '0');

    signal busy, boot_ok, boot_fail : std_logic;
    signal fail_reason : unsigned(2 downto 0);
    signal image_base  : unsigned(ADDR_W - 1 downto 0);
    signal image_len   : unsigned(LEN_W - 1 downto 0);

    -- settable flash contents
    signal dev_id    : unsigned(23 downto 0) := to_unsigned(GOOD_ID, 24);
    signal dev_magic : unsigned(31 downto 0) := to_unsigned(MAGIC, 32);
    signal dev_len   : unsigned(31 downto 0) := to_unsigned(16#2000#, 32);
    signal dev_sum   : unsigned(31 downto 0) :=
        to_unsigned(MAGIC, 32) + to_unsigned(16#2000#, 32);

    -- recorder
    signal saw_fetch : std_logic := '0';
    signal saw_rdp   : std_logic := '0';
    signal n_ops     : natural   := 0;
    signal fetch_len : unsigned(LEN_W - 1 downto 0) := (others => '0');
    signal clr_rec   : std_logic := '0';

    signal errors : natural := 0;

begin

    clk <= not clk after 5 ns when not halt else '0';

    model : process (clk, rst_n)
    begin
        if rst_n = '0' then
            op_ack  <= '0';
            op_data <= (others => '0');
        elsif rising_edge(clk) then
            op_ack <= op_req;
            if op_req = '1' then
                if op_code = to_unsigned(OP_RDP, 8) then
                    op_data <= (others => '0');
                elsif op_code = to_unsigned(OP_RDID, 8) then
                    op_data <= resize(dev_id, 32);
                elsif op_code = to_unsigned(OP_READ, 8) then
                    if op_addr = to_unsigned(HDR_BASE, ADDR_W) then
                        op_data <= dev_magic;
                    elsif op_addr = to_unsigned(HDR_BASE + 4, ADDR_W) then
                        op_data <= dev_len;
                    elsif op_addr = to_unsigned(HDR_BASE + 8, ADDR_W) then
                        op_data <= dev_sum;
                    else
                        op_data <= unsigned'(x"DEADBEEF");
                    end if;
                else
                    op_data <= (others => '0');
                end if;
            end if;
        end if;
    end process;

    dut : entity work.flash_boot_seq
        generic map (ADDR_W => ADDR_W, LEN_W => LEN_W,
                     OP_RDP => OP_RDP, OP_RDID => OP_RDID, OP_READ => OP_READ,
                     EXPECT_ID => GOOD_ID, MAGIC => MAGIC,
                     HDR_BASE => HDR_BASE, IMG_BASE => IMG_BASE,
                     MAX_LEN => MAX_LEN)
        port map (
            clk => clk, rst_n => rst_n, start => start,
            op_req => op_req, op_code => op_code, op_addr => op_addr,
            op_len => op_len, op_ack => op_ack, op_data => op_data,
            busy => busy, boot_ok => boot_ok, boot_fail => boot_fail,
            fail_reason => fail_reason,
            image_base => image_base, image_len => image_len
        );

    -- Recorder. The clear request arrives on its own signal because two
    -- processes driving one signal is a multiple-driver error in VHDL.
    recorder : process (clk)
    begin
        if rising_edge(clk) then
            if clr_rec = '1' then
                saw_fetch <= '0';
                saw_rdp   <= '0';
                n_ops     <= 0;
                fetch_len <= (others => '0');
            elsif rst_n = '1' and op_req = '1' then
                n_ops <= n_ops + 1;
                if op_code = to_unsigned(OP_RDP, 8) then
                    saw_rdp <= '1';
                end if;
                if op_code = to_unsigned(OP_READ, 8) and
                   op_addr = to_unsigned(IMG_BASE, ADDR_W) then
                    saw_fetch <= '1';
                    fetch_len <= op_len;
                end if;
            end if;
        end if;
    end process;

    stim : process
        variable errs  : natural := 0;
        variable guard : natural;

        procedure boot(limit : natural) is
        begin
            wait until falling_edge(clk);
            clr_rec <= '1';
            wait until falling_edge(clk);
            clr_rec <= '0';
            start   <= '1';
            wait until falling_edge(clk);
            start   <= '0';
            guard := 0;
            while busy = '1' and guard < limit loop
                wait until falling_edge(clk);
                guard := guard + 1;
            end loop;
            if guard >= limit then
                report "  FAIL: boot never finished in time"; errs := errs + 1;
            end if;
            wait until falling_edge(clk);
        end procedure;

        procedure expect_fail(what : string; why : unsigned(2 downto 0)) is
        begin
            boot(500);
            if boot_fail /= '1' or boot_ok = '1' then
                report "  FAIL: " & what & " did not fail the boot";
                errs := errs + 1;
            end if;
            if fail_reason /= why then
                report "  FAIL: " & what & " reported reason " &
                       integer'image(to_integer(fail_reason)) &
                       ", expected " & integer'image(to_integer(why));
                errs := errs + 1;
            end if;
            -- THE SAFETY PROPERTY.
            if saw_fetch = '1' then
                report "  FAIL: " & what & " still fetched the image";
                errs := errs + 1;
            end if;
            report "  " & what & " reason=" &
                   integer'image(to_integer(fail_reason)) & ", " &
                   integer'image(n_ops) & " opcodes, image never fetched";
        end procedure;
    begin
        for i in 0 to 2 loop
            wait until falling_edge(clk);
        end loop;
        rst_n <= '1';
        wait until falling_edge(clk);

        -- 1. The happy path.
        boot(500);
        if boot_ok /= '1' or boot_fail = '1' then
            report "  FAIL: a good device did not boot"; errs := errs + 1;
        end if;
        if saw_rdp /= '1' then
            report "  FAIL: the part was never released from power-down";
            errs := errs + 1;
        end if;
        if saw_fetch /= '1' then
            report "  FAIL: a good boot never fetched the image";
            errs := errs + 1;
        end if;
        if to_integer(image_len) /= 16#2000# then
            report "  FAIL: image_len is wrong"; errs := errs + 1;
        end if;
        if to_integer(fetch_len) /= 16#2000# then
            report "  FAIL: the fetch length does not match the header";
            errs := errs + 1;
        end if;
        if to_integer(image_base) /= IMG_BASE then
            report "  FAIL: image_base is wrong"; errs := errs + 1;
        end if;
        report "  good device: booted, " & integer'image(n_ops) &
               " opcodes, fetched " & integer'image(to_integer(fetch_len)) &
               " bytes from " & integer'image(to_integer(image_base));

        -- 2. A wrong or absent device.
        dev_id <= to_unsigned(16#FFFFFF#, 24);
        expect_fail("wrong ID (all ones)", F_ID);
        dev_id <= to_unsigned(16#000000#, 24);
        expect_fail("no device (all zeros)", F_ID);
        dev_id <= to_unsigned(16#EF4017#, 24);      -- a real but DIFFERENT part
        expect_fail("different part", F_ID);
        dev_id <= to_unsigned(GOOD_ID, 24);

        -- 3. A device with no image.
        dev_magic <= (others => '1');
        expect_fail("erased flash (no magic)", F_MAGIC);
        dev_magic <= to_unsigned(16#5A5AC0DF#, 32);  -- one bit wrong
        expect_fail("magic off by one bit", F_MAGIC);
        dev_magic <= to_unsigned(MAGIC, 32);

        -- 4. A damaged header -- the check that protects the length.
        dev_len <= to_unsigned(16#2000#, 32);
        dev_sum <= (others => '0');
        expect_fail("header checksum wrong", F_SUM);

        -- 5. A header that checksums but carries an impossible length. The
        --    checksum is CONSISTENT in both cases: a corrupt image can be
        --    internally consistent.
        dev_len <= (others => '0');
        dev_sum <= to_unsigned(MAGIC, 32);
        expect_fail("zero-length image", F_LEN);
        dev_len <= to_unsigned(MAX_LEN + 1, 32);
        dev_sum <= to_unsigned(MAGIC, 32) + to_unsigned(MAX_LEN + 1, 32);
        expect_fail("image larger than RAM", F_LEN);

        -- 6. The exact boundary: MAX_LEN accepted, MAX_LEN+1 rejected.
        dev_len <= to_unsigned(MAX_LEN, 32);
        dev_sum <= to_unsigned(MAGIC, 32) + to_unsigned(MAX_LEN, 32);
        boot(500);
        if boot_ok /= '1' then
            report "  FAIL: an image of exactly MAX_LEN was rejected";
            errs := errs + 1;
        end if;
        report "  exactly MAX_LEN: accepted";
        dev_len <= to_unsigned(MAX_LEN + 1, 32);
        dev_sum <= to_unsigned(MAGIC, 32) + to_unsigned(MAX_LEN + 1, 32);
        expect_fail("one byte over MAX_LEN", F_LEN);

        -- 7. Recovery after every kind of failure.
        dev_len <= to_unsigned(16#1000#, 32);
        dev_sum <= to_unsigned(MAGIC, 32) + to_unsigned(16#1000#, 32);
        boot(500);
        if boot_ok /= '1' or boot_fail = '1' or fail_reason /= F_NONE then
            report "  FAIL: a good device after failures did not boot cleanly";
            errs := errs + 1;
        end if;
        report "  recovery after six failures: booted, fetched " &
               integer'image(to_integer(fetch_len)) & " bytes";

        errors <= errs;
        if errs = 0 then
            report "PASS: the part is released from power-down before anything else, a wrong or absent device is rejected on its ID, an erased or damaged header is rejected on its magic, the header checksum is verified BEFORE the length is trusted, the length bound is exact at both ends, the image is never fetched after any failure, and a good device boots cleanly afterwards";
        else
            report "FAIL: " & integer'image(errs) & " error(s)" severity error;
        end if;
        halt <= true;
        wait;
    end process;

end architecture;

Parity

All three implement the same sequence: identical ports and generics, release from power-down first, ID verified before any read, magic before the header fields, the checksum verified before the length is bounded, and no transition from any failure state to the fetch. All three testbenches inject the same nine failures and report identical opcode counts and reason codes, accept exactly MAX_LEN and reject MAX_LEN + 1, and boot cleanly afterwards.

7. Why a Verification Engineer Cares

Azvya Education Pvt. Ltd.VLSI Mentor
flash_boot_seq.sva — the ordering properties
   // 1. THE SAFETY PROPERTY. The fetch is never issued after any failure.
   //    Expressed on the ISSUE rather than on a flag, because a fetch that
   //    happened and was then ignored has already read the flash and
   //    already sized something.
   a_no_fetch_on_fail : assert property (
       @(posedge clk) disable iff (!rst_n)
           (boot_fail) |-> (!fetch_issued_this_attempt))
       else $error("the image was fetched despite a failed check");

   // 2. ORDERING: the length is used only AFTER the checksum has been
   //    verified. This is the property the whole chapter turns on, and it
   //    is an ordering claim, so it needs a temporal operator rather than
   //    a state check.
   a_sum_before_len : assert property (
       @(posedge clk) disable iff (!rst_n)
           (len_used) |-> (sum_verified_earlier))
       else $error("the image length was used before the header was verified");

   // 3. The part is woken before anything is read. A sleeping device
   //    answers zeros, and zeros are a plausible-looking ID failure that
   //    sends the investigation to the wrong place.
   a_wake_first : assert property (
       @(posedge clk) disable iff (!rst_n)
           (first_read_issued) |-> (rdp_issued_earlier))
       else $error("a read was issued before releasing from power-down");

   // 4. The fetch length is EXACTLY the validated header length -- not a
   //    rounded, padded or re-read value. Two computations of the same
   //    number is how a design comes to disagree with itself.
   a_fetch_len_exact : assert property (
       @(posedge clk) disable iff (!rst_n)
           (fetch_issued) |-> (fetch_len == validated_len))
       else $error("the fetch length differs from the validated length");

   // 5. EXACTLY ONE VERDICT, and the reason names the FIRST check that
   //    failed -- so a header that is both mis-checksummed and over-long
   //    reports the checksum, which is the one that invalidates the other.
   a_one_verdict : assert property (
       @(posedge clk) disable iff (!rst_n)
           ($onehot0({boot_ok, boot_fail})))
       else $error("more than one verdict was reported");

   // 6. TERMINATION. Every attempt reaches a verdict. A boot ROM that
   //    hangs shows no symptom at all beyond a dead device.
   a_terminates : assert property (
       @(posedge clk) disable iff (!rst_n)
           (start) |-> ##[1:$] (boot_ok || boot_fail))
       else $error("the boot attempt never reached a verdict");

Property 2 is the chapter in one line, and its form is worth noting: it is an ordering property, so it cannot be written as a state check. "The length is validated" is a state; "the length was validated before it was used" is a temporal claim, and only the temporal form catches an implementation that does both in the wrong order.

Property 1 is expressed on the issue of the fetch rather than on a completion flag, and that distinction matters. A design that fetches and then discards the result has still read the flash and still sized a copy — the damage is done at issue time, so that is where the property belongs.

Property 4 guards the mistake where a length is validated in one place and re-read in another. Both values look right; only their equality is the specification.

Coverage must reach failures that a working board never produces:

Azvya Education Pvt. Ltd.VLSI Mentor
flash_boot_cg.sv — the failure space, which no good device visits
   covergroup flash_boot_cg @(posedge clk iff verdict);
       // Every reason code must be reachable, and the ones that need a
       // deliberately broken device are the ones a directed suite skips.
       cp_reason : coverpoint fail_reason {
           bins ok       = {F_NONE};
           bins bad_id   = {F_ID};
           bins bad_magic = {F_MAGIC};
           bins bad_sum  = {F_SUM};
           bins bad_len  = {F_LEN};
       }

       // The SHAPE of a bad ID distinguishes three different physical
       // faults, and a suite that only tests one value tests one fault.
       cp_id_shape : coverpoint id_class {
           bins all_ones  = {ID_FF};      // no device responding
           bins all_zeros = {ID_00};      // unpowered or asleep
           bins wrong_real = {ID_OTHER};  // a second-source substitution
           bins correct   = {ID_GOOD};
       }

       // The length bound, exactly. One apart, opposite outcomes.
       cp_len : coverpoint hdr_len {
           bins zero      = {0};                  // must reject
           bins small     = {[1:1023]};
           bins at_max    = {MAX_LEN};            // must ACCEPT
           bins one_over  = {MAX_LEN + 1};        // must reject
           bins absurd    = {[MAX_LEN+2:$]};
       }

       // Recovery after each failure -- the only way to show the verdict
       // is not sticky, and reachable only by ordering the tests so a good
       // boot follows a bad one.
       cp_after : coverpoint prev_reason {
           bins after_ok    = {F_NONE};
           bins after_id    = {F_ID};
           bins after_magic = {F_MAGIC};
           bins after_sum   = {F_SUM};
           bins after_len   = {F_LEN};
       }
   endgroup

cp_id_shape is the coverpoint worth adding. All-ones, all-zeros and a wrong-but-real ID are three different physical faults that produce the same reason code, and a suite testing one value has exercised one of them. The distinction matters in the field: all-ones sends you to chip select, all-zeros to power or the wake-up command, and a real-but-wrong value to the bill of materials.

8. Why an FPGA or ASIC Engineer Cares

Wake the part first, unconditionally. It costs one command and removes a failure whose symptom — all-zero reads — is indistinguishable from a dead device. A warm reset does not reset the flash, and previous software may have put it to sleep.

Boot at the slowest divisor with the plain read. Chapter 11.2's reset default exists for this moment: no latency to get wrong, and a rate no device can fail to meet. Speed up after the ID confirms what the part is.

Verify before you trust, in that order. The ordering is the design, and the specific inversion to avoid is using a length before checking the header that carries it.

Bound the length against the RAM, not against the flash. The flash may be larger than the RAM. The constraint that matters is where the image is going.

Make the failure reason observable. A boot ROM that can only fail silently is a boot ROM that cannot be diagnosed in the field. Three bits on a pin, a blink code, or a retained register — any of them turns "dead board" into "the ID did not match".

Never jump on a failed check. The temptation is to try anyway, on the grounds that a partial image might work. It will not, and jumping into unvalidated memory is how a corrupted flash becomes a device that behaves unpredictably rather than one that reports a fault.

Re-establish the addressing mode. A part left in 4-byte mode by previous software greets a 3-byte boot ROM with an address phase one byte longer than expected (Chapter 10.5). An explicit mode command at boot removes a failure that survives a warm reset.

9. Failure Signature — A Board That Boots Cold and Not Warm

Symptom. A product boots reliably from power-on. A software-initiated warm reset leaves it dead — no console, no activity. A power cycle recovers it every time. The failure is completely reproducible and there is no error message, because nothing is running to produce one.

What "cold works, warm does not" establishes. The boot ROM, the flash, the image and the wiring are all correct, because a cold boot exercises every one of them. The difference is state that survives a warm reset, and the boot ROM's own state does not — it starts fresh. So it is state in the flash.

Plausible mechanisms.

  • The flash left in 4-byte addressing mode by the running software. The boot ROM sends three address bytes; the device waits for a fourth, takes the first data byte as it, and returns data from a wildly wrong address. This fits perfectly and is the leading candidate.
  • The flash left in a low-power mode, so it answers nothing until woken. Fits if the boot ROM omits the wake-up command.
  • The flash left mid-erase, with WIP set, so reads return nothing meaningful until it completes.
  • A quad or DTR mode enabled, so the part is no longer speaking single-line SPI at all.
  • The flash's own reset not being asserted by the system reset — which is the common root behind all of the above rather than a separate cause.

The discriminating observation. Capture the first frames after a warm reset and count the address bytes before data appears. If the boot ROM sends three and the first returned byte arrives one byte time later than it should, the device is in 4-byte mode and consumed a data byte as address. That is decisive and takes one capture.

If instead MISO stays idle entirely, the part is asleep or mid-operation: read the status register, and a set WIP identifies an incomplete erase while no response at all identifies a power-down.

The fix, and where it belongs. Two changes, and the second is the durable one.

The boot ROM should re-establish the interface before using it: a wake-up command, then a mode reset. Many parts provide a software reset sequence (0x66 followed by 0x99) for exactly this.

But the real fix is in hardware: route the system reset to the flash's reset pin, if it has one. Then no software can leave the part in a state the boot ROM does not expect, and the whole class of failure disappears — rather than being handled one mode at a time.

Why it is not caught in testing. Because test procedures power-cycle. Warm reset is exercised by the software team during development, when the flash happens to be in its default mode because nothing has yet changed it — and the failure appears only once some feature starts using 4-byte addressing or a low-power mode, by which time the two changes look unrelated.

10. Common Misconceptions

11. Reason It Through

Work this before reading the answer.

A boot ROM reads a 12-byte header at address 0 containing magic, length and checksum, validates all three, and copies the image from 0x1000. A field failure is reported: a small fraction of units fail to boot after a power interruption during a firmware update, and the failure is permanent.

Given that all three header checks are correctly implemented, what is wrong — and what is the fix?

Start with what the checks guarantee. They guarantee that if the header is intact and consistent, the length is usable. They say nothing about whether the image matches the header.

Now think about what an interrupted update leaves behind. A firmware update writes an image and a header. If power is lost between them, the flash contains one of two states:

  • A new header and an old image, if the header was written first.
  • An old header and a new image, if the image was written first.

Both pass every check. The header is intact, its checksum is consistent, and its length is in bounds — because it is a perfectly valid header. It simply describes a different image than the one present.

So the boot copies a length from one image and the bytes from another, and jumps into the result. The header checks cannot detect this because they only validate the header, and the header is fine.

Why is the failure permanent? Because the boot ROM fails or hangs, so no software runs, so the update cannot be retried. The device has no path back.

What is the fix? Two parts, and the ordering insight is the interesting one.

An image-wide integrity check. The header must carry a checksum or hash over the image, not just over itself. Then a header describing an image that is not present fails — which is exactly the case the header checksum structurally cannot catch. Note that this check can only run after the image is read, which is why it does not replace the header checksum: you need the length to know how much to read before you can check what you read.

Write ordering, and a commit step. The update must make the new image valid only at the last moment:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   1. erase and write the image
   2. verify the image by reading it back
   3. THEN write the header            ← the commit point

With that ordering, an interruption before step 3 leaves the old header describing the old image — and the device boots the old firmware, which works. The header write is the single atomic act that switches versions.

And the more robust form is two image slots with two headers and a sequence number: write the inactive slot, verify it, then update the active-slot indicator. An interruption at any point leaves at least one complete, verified slot, so the device always boots something.

The general lesson. The header checks of §2 establish that the header's fields are usable. They cannot establish that the header describes what is there — that needs a check over the image itself, and it needs the length that the header checks made trustworthy. The two are a sequence, not alternatives. And no amount of validation at boot substitutes for write ordering that never leaves an inconsistent state to validate.

12. Understanding Check

13. Summary

Boot is the flash transaction with nothing to debug it, a device that may not be in a known state, and no downstream stage that runs unless it succeeds. So it is not a read — it is a chain of checks.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   wake → verify the ID → check the magic → verify the checksum
        → bound the length → fetch

The order is the design. Waking comes first because a sleeping part's zeros look like a dead device. The ID comes before any read because it is the only answer known in advance. And the checksum comes before the length because a length from an unverified header is an arbitrary number that would size a memory copy.

A corrupt header can be internally consistent, so bounding the length is a separate check from verifying the checksum — and the bound is against the RAM, not the flash.

The shape of an ID failure distinguishes three physical faults: all-ones means nothing responding, all-zeros means unpowered or asleep, and a real but different value means the wrong part is fitted.

In hardware, every failure leads to a state from which the fetch is structurally unreachable — stronger than a flag — and the failure reason must be observable, because a boot ROM that can only fail silently cannot be diagnosed in the field.

For verification, the central property is temporal: the length was validated before it was used. A state check cannot express it. And the no-fetch property belongs on the fetch being issued, because that is when the damage occurs.

Finally, these checks establish that the header's fields are usable — not that the header describes what is present. That needs a check over the image, and it needs write ordering at update time that never leaves an inconsistent state to validate.

14. What Comes Next

A processor's boot ROM is one consumer of a flash image. An FPGA is the other, and it does the same job with a different division of labour.

Chapter 11.6 — FPGA Configuration over SPI covers both directions: master mode, where the FPGA drives SCLK and reads its own bitstream, and slave mode, where an external host clocks it in. The chapter's finding is that the two differ only in who starts and who clocks — the bitstream parsing is identical — and it builds the loader that proves it by sharing one datapath between both, in all three HDLs.

Continue learning