t27.aiРусский

PSEL and PENABLE

You will learn

How the APB two-phase handshake works, and what the compiler emits for the bridge.

APB moves a transfer in two phases: setup, where PSEL names the servant and the address settles, and access, where PENABLE rises and the transfer completes. The spec's ApbTransfer enum carries the kinds, and the bench of the twelfth lesson drives write and read through them. The recording lowers the APB bridge spec to synthesizable Verilog: the port list, the wiring, the decode.

Try it

In the recording, find the bridge's port list; then in the spec frame find the ApbTransfer kinds and say which phase PENABLE names.

Open the interactive lesson →

t27c gen-verilog on apb_bridge.t27 -- spec to RTL
t27c gen-verilog on apb_bridge.t27 -- spec to RTL ↗

The APB bridge spec lowered to synthesizable Verilog: port list, PSEL and PENABLE wiring, the address decode.

specs/fpga/apb_bridge.t27

// SPDX-License-Identifier: Apache-2.0
// t27/specs/fpga/apb_bridge.t27
// APB (Advanced Peripheral Bus) Bridge Specification for Trinity T27 FPGA HIR
// Register-mapped peripheral bridge for low-bandwidth peripherals
// Uses flat arrays + count fields (parser-compatible)
// phi^2 + 1/phi^2 = 3 | TRINITY

module ApbBridge {

    // === APB bus width constants ===

    pub const APB_ADDR_WIDTH : u32 = 32;
    pub const APB_DATA_WIDTH : u32 = 32;
    pub const APB_STRB_WIDTH : u32 = 4;

    // === APB transfer kind ===

    pub const ApbTransfer = enum(i8) {
        idle = 0,
        setup = 1,
        access = 2,
    }

    // === APB bridge configuration ===

    pub struct ApbConfig {
        name : &str,
        addr_width : u32,
        data_width : u32,
        num_peripherals : u32,
        base_addr : u32,
        addr_mask : u32,
        has_pslverr : bool,
        has_pprot : bool,
    }

    // === Peripheral address range ===

    pub struct PeripheralMap {
        name : &str,
        base_addr : u32,
        size : u32,
        index : u32,
    }

    // === APB read/write request (for simulation) ===

    pub struct ApbRequest {
        addr : u32,
        wdata : u32,
        write : bool,
        strb : u32,
        valid : bool,
    }

    // === APB response (for simulation) ===

    pub struct ApbResponse {
        rdata : u32,
        ready : bool,
        slverr : bool,
    }

    // === Constructor helpers ===

    fn apb_bridge(name: &str, addr_width: u32, data_width: u32, num_peripherals: u32) -> ApbConfig {
        return ApbConfig{
            .name = name,
            .addr_width = addr_width,
            .data_width = data_width,
            .num_peripherals = num_peripherals,
            .base_addr = 0,
            .addr_mask = 0,
            .has_pslverr = false,
            .has_pprot = false,
        };
    }

    fn apb_bridge_with_error(name: &str, addr_width: u32, data_width: u32, num_peripherals: u32) -> ApbConfig {
        return ApbConfig{
            .name = name,
            .addr_width = addr_width,
            .data_width = data_width,
            .num_peripherals = num_peripherals,
            .base_addr = 0,
            .addr_mask = 0,
            .has_pslverr = true,
            .has_pprot = true,
        };
    }

    fn peripheral_map(name: &str, base_addr: u32, size: u32, index: u32) -> PeripheralMap {
        return PeripheralMap{
            .name = name,
            .base_addr = base_addr,
            .size = size,
            .index = index,
        };
    }

    fn apb_read_request(addr: u32) -> ApbRequest {
        return ApbRequest{
            .addr = addr,
            .wdata = 0,
            .write = false,
            .strb = 15,
            .valid = true,
        };
    }

    fn apb_write_request(addr: u32, data: u32, strb: u32) -> ApbRequest {
        return ApbRequest{
            .addr = addr,
            .wdata = data,
            .write = true,
            .strb = strb,
            .valid = true,
        };
    }

    fn apb_ok_response(data: u32) -> ApbResponse {
        return ApbResponse{
            .rdata = data,
            .ready = true,
            .slverr = false,
        };
    }

    fn apb_error_response() -> ApbResponse {
        return ApbResponse{
            .rdata = 0,
            .ready = true,
            .slverr = true,
        };
    }

    // === Query functions ===

    fn strb_width(cfg: ApbConfig) -> u32 {
        return cfg.data_width / 8;
    }

    fn addr_bits_for_peripherals(cfg: ApbConfig) -> u32 {
        var n : u32 = cfg.num_peripherals;
        if (n <= 1) {
            return 0;
        }
        var bits : u32 = 0;
        while (n > 1) {
            bits = bits + 1;
            n = n / 2;
        }
        return bits;
    }

    fn peripheral_addr_offset(cfg: ApbConfig, periph_index: u32) -> u32 {
        var offset_bits : u32 = addr_bits_for_peripherals(cfg);
        return periph_index << offset_bits;
    }

    fn is_read(req: ApbRequest) -> bool {
        return req.valid and req.write == false;
    }

    fn is_write(req: ApbRequest) -> bool {
        return req.valid and req.write;
    }

    fn apb_port_count(cfg: ApbConfig) -> u32 {
        var count : u32 = 0;
        count = count + 1;
        count = count + 1;
        count = count + cfg.addr_width;
        count = count + cfg.data_width;
        count = count + cfg.data_width / 8;
        count = count + cfg.data_width;
        count = count + 1;
        if (cfg.has_pslverr) {
            count = count + 1;
        }
        if (cfg.has_pprot) {
            count = count + 3;
        }
        return count;
    }

    fn select_peripheral(cfg: ApbConfig, addr: u32, maps: [16]PeripheralMap, map_count: u32) -> u32 {
        var i : u32 = 0;
        while (i < map_count) {
            var base : u32 = maps[i].base_addr;
            var size : u32 = maps[i].size;
            if (addr >= base and addr < base + size) {
                return i;
            }
            i = i + 1;
        }
        return 65535;
    }

    // === Validation ===

    fn validate_apb(cfg: ApbConfig) -> u32 {
        var errors : u32 = 0;
        if (cfg.name == "") {
            errors = errors + 1;
        }
        if (cfg.addr_width == 0) {
            errors = errors + 1;
        }
        if (cfg.data_width == 0) {
            errors = errors + 1;
        }
        if (cfg.data_width % 8 != 0) {
            errors = errors + 1;
        }
        if (cfg.num_peripherals == 0) {
            errors = errors + 1;
        }
        return errors;
    }

    fn validate_peripheral_map(m: PeripheralMap) -> u32 {
        var errors : u32 = 0;
        if (m.name == "") {
            errors = errors + 1;
        }
        if (m.size == 0) {
            errors = errors + 1;
        }
        return errors;
    }

    // === Tests ===

    test apb_bridge_creation
        given cfg = apb_bridge("apb0", 32, 32, 4)
        then cfg.name == "apb0"
        and cfg.addr_width == 32
        and cfg.data_width == 32
        and cfg.num_peripherals == 4
        and cfg.has_pslverr == false

    test apb_bridge_with_error
        given cfg = apb_bridge_with_error("apb1", 32, 32, 8)
        then cfg.has_pslverr == true
        and cfg.has_pprot == true

    test strb_width_32bit
        given cfg = apb_bridge("apb0", 32, 32, 4)
        then strb_width(cfg) == 4

    test strb_width_16bit
        given cfg = apb_bridge("apb0", 16, 16, 4)
        then strb_width(cfg) == 2

    test addr_bits_for_1_peripheral
        given cfg = apb_bridge("apb0", 32, 32, 1)
        then addr_bits_for_peripherals(cfg) == 0

    test addr_bits_for_4_peripherals
        given cfg = apb_bridge("apb0", 32, 32, 4)
        then addr_bits_for_peripherals(cfg) == 2

    test addr_bits_for_8_peripherals
        given cfg = apb_bridge("apb0", 32, 32, 8)
        then addr_bits_for_peripherals(cfg) == 3

    test read_request
        given req = apb_read_request(256)
        then is_read(req) == true
        and is_write(req) == false
        and req.addr == 256

    test write_request
        given req = apb_write_request(256, 42, 15)
        then is_read(req) == false
        and is_write(req) == true
        and req.wdata == 42

    test ok_response
        given resp = apb_ok_response(99)
        then resp.rdata == 99
        and resp.ready == true
        and resp.slverr == false

    test error_response
        given resp = apb_error_response()
        then resp.slverr == true

    test validate_ok
        given cfg = apb_bridge("apb0", 32, 32, 4)
        then validate_apb(cfg) == 0

    test validate_empty_name
        given cfg = apb_bridge("", 32, 32, 4)
        then validate_apb(cfg) > 0

    test validate_zero_addr
        given cfg = apb_bridge("apb0", 0, 32, 4)
        then validate_apb(cfg) > 0

    test validate_zero_peripherals
        given cfg = apb_bridge("apb0", 32, 32, 0)
        then validate_apb(cfg) > 0

    test validate_peripheral_map_ok
        given m = peripheral_map("uart0", 4096, 256, 0)
        then validate_peripheral_map(m) == 0

    test validate_peripheral_map_no_name
        given m = peripheral_map("", 4096, 256, 0)
        then validate_peripheral_map(m) > 0

    test apb_port_count_basic
        given cfg = apb_bridge("apb0", 32, 32, 4)
        then apb_port_count(cfg) > 0

    // === Invariants ===

    invariant addr_width_positive
        given cfg = apb_bridge("inv", 32, 32, 4)
        assert cfg.addr_width > 0

    invariant data_width_byte_aligned
        given cfg = apb_bridge("inv", 32, 32, 4)
        assert cfg.data_width % 8 == 0

    invariant strb_matches_data
        given cfg = apb_bridge("inv", 32, 32, 4)
        assert strb_width(cfg) == cfg.data_width / 8

    invariant num_peripherals_positive
        given cfg = apb_bridge("inv", 32, 32, 4)
        assert cfg.num_peripherals > 0

    invariant validate_non_negative
        given cfg = apb_bridge("inv", 32, 32, 4)
        assert validate_apb(cfg) >= 0

    // === Benchmarks ===

    bench validate_latency
        measure: nanoseconds to validate_apb(apb_bridge("b", 32, 32, 4))
        target: < 100ns
}

// phi^2 + 1/phi^2 = 3 | TRINITY

Open the lesson's spec in the player ↗

All lessons