Ready-to-run snippets

Code Examples

Ready-to-run Verilog, SystemVerilog, UVM, bus protocol, formal verification, STA, and physical design examples. Click any to open it live in the IDE.

← All examples

SoC Design: Boot C on cvsoc

SoC Design SoC Design

Compile C for RISC-V and boot it on cvsoc: the cvr32 core with a TCM, an AXI4-Lite interconnect, SRAM, UART, timer and GPIO. All the RTL is here to read and change. Press Run Simulation: the program prints over the UART to the Console.

Open in IDE
Related tutorial:
main.c
/* main.c: runs on the cvsoc SoC. The program loads into the TCM, and SRAM, the UART
   and GPIO are reached over the bus. cvsoc.h has the register addresses and drivers. */
#include "cvsoc.h"

int main(void)
{
    volatile u32 *sram = (volatile u32 *)SRAM_BASE;
    u32 sum = 0;

    uart_puts("Booted on cvsoc\n");
    for (u32 i = 0; i < 8; i++)
        sram[i] = i * i;
    for (u32 i = 0; i < 8; i++)
        sum += sram[i];

    uart_puts("sum: 0x");
    uart_hex(sum);
    uart_putc('\n');
    GPIO_OUT = sum;
    uart_wait_idle();
    return sum;
}
cvsoc.h
/* cvsoc memory map and tiny drivers. No C library. */
typedef unsigned int u32;

#define SRAM_BASE   0x20000000u
#define UART_BASE   0x40000000u
#define TIMER_BASE  0x40001000u
#define GPIO_BASE   0x40002000u

#define REG(a)      (*(volatile u32 *)(a))
#define UART_TXDATA REG(UART_BASE + 0x0)
#define UART_STATUS REG(UART_BASE + 0x4)
#define UART_DIV    REG(UART_BASE + 0x8)
#define TIMER_LO    REG(TIMER_BASE + 0x0)
#define GPIO_OUT    REG(GPIO_BASE + 0x0)
#define GPIO_IN     REG(GPIO_BASE + 0x4)

static inline u32 xs32(u32 *s)
{
    u32 x = *s;
    x ^= x << 13;
    x ^= x >> 17;
    x ^= x << 5;
    *s = x;
    return x;
}

#ifndef HOST
static void uart_putc(char c)
{
    while (UART_STATUS & 1)            /* wait while the FIFO is full */
        ;
    UART_TXDATA = (u32)c;
}

static void uart_puts(const char *s)
{
    while (*s)
        uart_putc(*s++);
}

static void uart_hex(u32 v)
{
    for (int i = 28; i >= 0; i -= 4) {
        u32 d = (v >> i) & 0xF;
        uart_putc(d < 10 ? '0' + d : 'a' + d - 10);
    }
}

static void uart_wait_idle(void)
{
    while (UART_STATUS & 2)            /* bytes queued or still sending */
        ;
}
#endif
cvr32_alu.sv
// cvr32_alu: the integer ALU of the cvr32 core (RV32I)
module cvr32_alu (
    input  logic [3:0]  op,      // operation, one of the ALU_* codes below
    input  logic [31:0] a,       // first operand (rs1, PC or zero)
    input  logic [31:0] b,       // second operand (rs2 or immediate)
    output logic [31:0] y        // result
);

    localparam logic [3:0] ALU_ADD  = 4'd0;
    localparam logic [3:0] ALU_SUB  = 4'd1;
    localparam logic [3:0] ALU_SLL  = 4'd2;
    localparam logic [3:0] ALU_SLT  = 4'd3;
    localparam logic [3:0] ALU_SLTU = 4'd4;
    localparam logic [3:0] ALU_XOR  = 4'd5;
    localparam logic [3:0] ALU_SRL  = 4'd6;
    localparam logic [3:0] ALU_SRA  = 4'd7;
    localparam logic [3:0] ALU_OR   = 4'd8;
    localparam logic [3:0] ALU_AND  = 4'd9;

    always @(*) begin
        case (op)
            ALU_ADD:  y = a + b;
            ALU_SUB:  y = a - b;
            ALU_SLL:  y = a << b[4:0];                       // shift amount is the low 5 bits
            ALU_SLT:  y = {31'd0, $signed(a) < $signed(b)};
            ALU_SLTU: y = {31'd0, a < b};
            ALU_XOR:  y = a ^ b;
            ALU_SRL:  y = a >> b[4:0];
            ALU_SRA:  y = $unsigned($signed(a) >>> b[4:0]);  // arithmetic shift keeps the sign
            ALU_OR:   y = a | b;
            ALU_AND:  y = a & b;
            default:  y = a + b;
        endcase
    end

endmodule
cvr32_regfile.sv
// cvr32_regfile: 32 x 32-bit register file, two read ports and one write port.
// x0 always reads as zero. A write in the same cycle as a read of the same
// register is passed straight to the read port (write-through), so the
// write-back stage never causes a stall.
module cvr32_regfile (
    input  logic        clk,
    input  logic [4:0]  ra1,     // read address 1 (rs1)
    input  logic [4:0]  ra2,     // read address 2 (rs2)
    output logic [31:0] rd1,
    output logic [31:0] rd2,
    input  logic        we,      // write enable from write-back
    input  logic [4:0]  wa,      // write address (rd)
    input  logic [31:0] wd       // write data
);

    logic [31:0] regs [1:31];

    always_ff @(posedge clk) begin
        if (we && wa != 5'd0)
            regs[wa] <= wd;
    end

    always @(*) begin
        if (ra1 == 5'd0)
            rd1 = 32'd0;
        else if (we && wa == ra1)
            rd1 = wd;
        else
            rd1 = regs[ra1];

        if (ra2 == 5'd0)
            rd2 = 32'd0;
        else if (we && wa == ra2)
            rd2 = wd;
        else
            rd2 = regs[ra2];
    end

endmodule
cvr32_decode.sv
// cvr32_decode: turns one RV32I instruction into the control signals the
// pipeline needs. Pure combinational logic, used in the decode (ID) stage.
module cvr32_decode (
    input  logic [31:0] instr,
    output logic [4:0]  rs1,
    output logic [4:0]  rs2,
    output logic [4:0]  rd,
    output logic [31:0] imm,        // sign-extended immediate for this format
    output logic [3:0]  alu_op,
    output logic [1:0]  a_sel,      // ALU operand A: 0 = rs1, 1 = PC, 2 = zero
    output logic        b_imm,      // ALU operand B: 1 = immediate, 0 = rs2
    output logic        reg_write,
    output logic        mem_read,
    output logic        mem_write,
    output logic [2:0]  funct3,     // load/store size or branch condition
    output logic        is_branch,
    output logic        is_jal,
    output logic        is_jalr,
    output logic        uses_rs1,   // used by the hazard logic
    output logic        uses_rs2
);

    localparam logic [6:0] OP_LUI    = 7'b0110111;
    localparam logic [6:0] OP_AUIPC  = 7'b0010111;
    localparam logic [6:0] OP_JAL    = 7'b1101111;
    localparam logic [6:0] OP_JALR   = 7'b1100111;
    localparam logic [6:0] OP_BRANCH = 7'b1100011;
    localparam logic [6:0] OP_LOAD   = 7'b0000011;
    localparam logic [6:0] OP_STORE  = 7'b0100011;
    localparam logic [6:0] OP_IMM    = 7'b0010011;
    localparam logic [6:0] OP_REG    = 7'b0110011;

    localparam logic [3:0] ALU_ADD  = 4'd0;
    localparam logic [3:0] ALU_SUB  = 4'd1;
    localparam logic [3:0] ALU_SLL  = 4'd2;
    localparam logic [3:0] ALU_SLT  = 4'd3;
    localparam logic [3:0] ALU_SLTU = 4'd4;
    localparam logic [3:0] ALU_XOR  = 4'd5;
    localparam logic [3:0] ALU_SRL  = 4'd6;
    localparam logic [3:0] ALU_SRA  = 4'd7;
    localparam logic [3:0] ALU_OR   = 4'd8;
    localparam logic [3:0] ALU_AND  = 4'd9;

    logic [6:0] opcode;
    logic [3:0] alu_f3;             // ALU operation chosen by funct3 (OP and OP-IMM)

    // the five immediate formats: the immediate's bits are spread over the
    // instruction differently in each, and every one is sign-extended
    // from instruction bit 31 to 32 bits
    logic [31:0] imm_i, imm_s, imm_b, imm_u, imm_j;
    assign imm_i = {{20{instr[31]}}, instr[31:20]};
    assign imm_s = {{20{instr[31]}}, instr[31:25], instr[11:7]};
    assign imm_b = {{20{instr[31]}}, instr[7], instr[30:25], instr[11:8], 1'b0};
    assign imm_u = {instr[31:12], 12'd0};
    assign imm_j = {{12{instr[31]}}, instr[19:12], instr[20], instr[30:21], 1'b0};

    assign opcode = instr[6:0];
    assign rd     = instr[11:7];
    assign funct3 = instr[14:12];
    assign rs1    = instr[19:15];
    assign rs2    = instr[24:20];

    // funct3 to ALU operation; bit 30 picks SUB/SRA
    always @(*) begin
        case (funct3)
            3'b000:  alu_f3 = ALU_ADD;
            3'b001:  alu_f3 = ALU_SLL;
            3'b010:  alu_f3 = ALU_SLT;
            3'b011:  alu_f3 = ALU_SLTU;
            3'b100:  alu_f3 = ALU_XOR;
            3'b101:  alu_f3 = instr[30] ? ALU_SRA : ALU_SRL;
            3'b110:  alu_f3 = ALU_OR;
            default: alu_f3 = ALU_AND;
        endcase
    end

    always @(*) begin
        // defaults: an instruction that does nothing (FENCE and SYSTEM end up here)
        imm       = 32'd0;
        alu_op    = ALU_ADD;
        a_sel     = 2'd0;
        b_imm     = 1'b1;
        reg_write = 1'b0;
        mem_read  = 1'b0;
        mem_write = 1'b0;
        is_branch = 1'b0;
        is_jal    = 1'b0;
        is_jalr   = 1'b0;
        uses_rs1  = 1'b0;
        uses_rs2  = 1'b0;

        case (opcode)
            OP_LUI: begin                                    // rd = imm << 12
                imm       = imm_u;
                a_sel     = 2'd2;
                reg_write = 1'b1;
            end
            OP_AUIPC: begin                                  // rd = PC + (imm << 12)
                imm       = imm_u;
                a_sel     = 2'd1;
                reg_write = 1'b1;
            end
            OP_JAL: begin                                    // rd = PC + 4, jump to PC + imm
                imm       = imm_j;
                reg_write = 1'b1;
                is_jal    = 1'b1;
            end
            OP_JALR: begin                                   // rd = PC + 4, jump to rs1 + imm
                imm       = imm_i;
                reg_write = 1'b1;
                is_jalr   = 1'b1;
                uses_rs1  = 1'b1;
            end
            OP_BRANCH: begin                                 // compare rs1 and rs2, jump to PC + imm
                imm       = imm_b;
                is_branch = 1'b1;
                uses_rs1  = 1'b1;
                uses_rs2  = 1'b1;
            end
            OP_LOAD: begin                                   // rd = mem[rs1 + imm]
                imm       = imm_i;
                reg_write = 1'b1;
                mem_read  = 1'b1;
                uses_rs1  = 1'b1;
            end
            OP_STORE: begin                                  // mem[rs1 + imm] = rs2
                imm       = imm_s;
                mem_write = 1'b1;
                uses_rs1  = 1'b1;
                uses_rs2  = 1'b1;
            end
            OP_IMM: begin                                    // rd = rs1 op imm
                imm       = imm_i;
                alu_op    = alu_f3;                          // no SUBI: funct3 000 is always ADD
                reg_write = 1'b1;
                uses_rs1  = 1'b1;
            end
            OP_REG: begin                                    // rd = rs1 op rs2
                alu_op    = (funct3 == 3'b000 && instr[30]) ? ALU_SUB : alu_f3;
                b_imm     = 1'b0;
                reg_write = 1'b1;
                uses_rs1  = 1'b1;
                uses_rs2  = 1'b1;
            end
            default: ;
        endcase
    end

endmodule
cvr32_core.sv
// cvr32_core: a 5-stage in-order RV32I pipeline (IF, ID, EX, MEM, WB).
//
// - Branches and jumps resolve in EX. The core predicts "not taken", so a
//   taken branch or a jump flushes the two younger instructions.
// - With cfg_fwd_en = 1, results are forwarded from MEM and WB to EX, and only
//   a load followed by a dependent instruction stalls (one cycle).
//   With cfg_fwd_en = 0, a dependent instruction waits in ID until its source
//   reaches write-back.
// - Both memory ports have a ready signal; a slow data access freezes the pipe.
// - Every pipeline slot carries a "cause" tag. When WB sees an empty slot, the
//   tag says why it is empty, so cycles = instructions + sum of all bubbles.
module cvr32_core (
    input  logic        clk,
    input  logic        rst_n,
    input  logic        cfg_fwd_en,     // 1 = forwarding on

    // instruction port
    output logic [31:0] imem_addr,
    input  logic [31:0] imem_rdata,
    input  logic        imem_ready,

    // data port
    output logic [31:0] dmem_addr,
    output logic [31:0] dmem_wdata,
    output logic [3:0]  dmem_wstrb,
    output logic        dmem_we,
    output logic        dmem_re,
    input  logic [31:0] dmem_rdata,
    input  logic        dmem_ready
);

    // why a pipeline slot is empty
    localparam logic [2:0] B_START   = 3'd0;   // pipeline filling after reset
    localparam logic [2:0] B_FETCH   = 3'd1;   // instruction memory not ready
    localparam logic [2:0] B_LOADUSE = 3'd2;   // load followed by a dependent instruction
    localparam logic [2:0] B_RAW     = 3'd3;   // data dependency without forwarding
    localparam logic [2:0] B_FLUSH   = 3'd4;   // wrong-path instruction after a taken branch or jump
    localparam logic [2:0] B_DMEM    = 3'd5;   // data memory not ready

    // ------------------------------------------------------------------
    // Pipeline registers
    // ------------------------------------------------------------------
    logic [31:0] pc_f;

    // IF/ID
    logic        d_valid;
    logic [2:0]  d_cause;
    logic [31:0] d_pc, d_instr;

    // ID/EX
    logic        e_valid;
    logic [2:0]  e_cause;
    logic [31:0] e_pc, e_rv1, e_rv2, e_imm;
    logic [4:0]  e_rs1, e_rs2, e_rd;
    logic [3:0]  e_alu_op;
    logic [1:0]  e_a_sel;
    logic        e_b_imm, e_reg_write, e_mem_read, e_mem_write;
    logic        e_branch, e_jal, e_jalr;
    logic [2:0]  e_funct3;

    // EX/MEM
    logic        m_valid;
    logic [2:0]  m_cause;
    logic [31:0] m_result, m_store_data;
    logic [4:0]  m_rd;
    logic        m_reg_write, m_mem_read, m_mem_write;
    logic [2:0]  m_funct3;

    // MEM/WB
    logic        w_valid;
    logic [2:0]  w_cause;
    logic [31:0] w_value;
    logic [4:0]  w_rd;
    logic        w_reg_write;

    // ------------------------------------------------------------------
    // ID: decode and register read
    // ------------------------------------------------------------------
    logic [4:0]  d_rs1, d_rs2, d_rd;
    logic [31:0] d_imm, d_rv1, d_rv2;
    logic [3:0]  d_alu_op;
    logic [1:0]  d_a_sel;
    logic [2:0]  d_funct3;
    logic        d_b_imm, d_reg_write, d_mem_read, d_mem_write;
    logic        d_branch, d_jal, d_jalr, d_uses_rs1, d_uses_rs2;

    cvr32_decode u_decode (
        .instr     (d_instr),
        .rs1       (d_rs1),
        .rs2       (d_rs2),
        .rd        (d_rd),
        .imm       (d_imm),
        .alu_op    (d_alu_op),
        .a_sel     (d_a_sel),
        .b_imm     (d_b_imm),
        .reg_write (d_reg_write),
        .mem_read  (d_mem_read),
        .mem_write (d_mem_write),
        .funct3    (d_funct3),
        .is_branch (d_branch),
        .is_jal    (d_jal),
        .is_jalr   (d_jalr),
        .uses_rs1  (d_uses_rs1),
        .uses_rs2  (d_uses_rs2)
    );

    cvr32_regfile u_regfile (
        .clk (clk),
        .ra1 (d_rs1),
        .ra2 (d_rs2),
        .rd1 (d_rv1),
        .rd2 (d_rv2),
        .we  (w_valid && w_reg_write),
        .wa  (w_rd),
        .wd  (w_value)
    );

    // ------------------------------------------------------------------
    // Hazard detection
    // ------------------------------------------------------------------
    logic dep_e, dep_m;           // the ID instruction reads a register that EX / MEM will write
    logic load_use, raw_wait, id_stall, mem_stall, redirect;
    logic [2:0] stall_cause;

    assign dep_e = e_valid && e_reg_write && (e_rd != 5'd0) &&
                   ((d_uses_rs1 && d_rs1 == e_rd) || (d_uses_rs2 && d_rs2 == e_rd));
    assign dep_m = m_valid && m_reg_write && (m_rd != 5'd0) &&
                   ((d_uses_rs1 && d_rs1 == m_rd) || (d_uses_rs2 && d_rs2 == m_rd));

    // a load result is ready only after MEM, so even forwarding needs one bubble
    assign load_use = dep_e && e_mem_read;
    // without forwarding, wait until the producer has left EX and MEM
    assign raw_wait = !cfg_fwd_en && (dep_e || dep_m);

    assign id_stall    = d_valid && (load_use || raw_wait);
    assign stall_cause = load_use ? B_LOADUSE : B_RAW;

    // a data access that is not ready freezes the whole pipeline
    assign mem_stall = m_valid && (m_mem_read || m_mem_write) && !dmem_ready;

    // ------------------------------------------------------------------
    // EX: forwarding, ALU, branch resolution
    // ------------------------------------------------------------------
    logic [31:0] fwd_a, fwd_b, alu_a, alu_b, alu_y, ex_result, target;
    logic        taken;

    always @(*) begin
        fwd_a = e_rv1;
        fwd_b = e_rv2;
        if (cfg_fwd_en) begin
            if (m_valid && m_reg_write && m_rd != 5'd0 && m_rd == e_rs1)
                fwd_a = m_result;
            else if (w_valid && w_reg_write && w_rd != 5'd0 && w_rd == e_rs1)
                fwd_a = w_value;
            if (m_valid && m_reg_write && m_rd != 5'd0 && m_rd == e_rs2)
                fwd_b = m_result;
            else if (w_valid && w_reg_write && w_rd != 5'd0 && w_rd == e_rs2)
                fwd_b = w_value;
        end
    end

    assign alu_a = (e_a_sel == 2'd1) ? e_pc : (e_a_sel == 2'd2) ? 32'd0 : fwd_a;
    assign alu_b = e_b_imm ? e_imm : fwd_b;

    cvr32_alu u_alu (
        .op (e_alu_op),
        .a  (alu_a),
        .b  (alu_b),
        .y  (alu_y)
    );

    always @(*) begin
        case (e_funct3)
            3'b000:  taken = (fwd_a == fwd_b);                    // BEQ
            3'b001:  taken = (fwd_a != fwd_b);                    // BNE
            3'b100:  taken = ($signed(fwd_a) <  $signed(fwd_b));  // BLT
            3'b101:  taken = ($signed(fwd_a) >= $signed(fwd_b));  // BGE
            3'b110:  taken = (fwd_a <  fwd_b);                    // BLTU
            3'b111:  taken = (fwd_a >= fwd_b);                    // BGEU
            default: taken = 1'b0;
        endcase
    end

    assign target    = e_jalr ? ((fwd_a + e_imm) & ~32'd1) : (e_pc + e_imm);
    assign ex_result = (e_jal || e_jalr) ? (e_pc + 32'd4) : alu_y;
    assign redirect  = e_valid && !mem_stall && (e_jal || e_jalr || (e_branch && taken));

    // ------------------------------------------------------------------
    // MEM: data port and load alignment
    // ------------------------------------------------------------------
    logic [31:0] load_shifted, load_value, m_value;

    assign dmem_addr = m_result;
    assign dmem_re   = m_valid && m_mem_read;
    assign dmem_we   = m_valid && m_mem_write;

    always @(*) begin
        case (m_funct3[1:0])
            2'b00: begin                                          // SB
                dmem_wdata = {4{m_store_data[7:0]}};
                dmem_wstrb = 4'b0001 << m_result[1:0];
            end
            2'b01: begin                                          // SH
                dmem_wdata = {2{m_store_data[15:0]}};
                dmem_wstrb = m_result[1] ? 4'b1100 : 4'b0011;
            end
            default: begin                                        // SW
                dmem_wdata = m_store_data;
                dmem_wstrb = 4'b1111;
            end
        endcase
    end

    assign load_shifted = dmem_rdata >> {m_result[1:0], 3'b000};

    always @(*) begin
        case (m_funct3)
            3'b000:  load_value = {{24{load_shifted[7]}},  load_shifted[7:0]};   // LB
            3'b001:  load_value = {{16{load_shifted[15]}}, load_shifted[15:0]};  // LH
            3'b100:  load_value = {24'd0, load_shifted[7:0]};                    // LBU
            3'b101:  load_value = {16'd0, load_shifted[15:0]};                   // LHU
            default: load_value = dmem_rdata;                                    // LW
        endcase
    end

    assign m_value = m_mem_read ? load_value : m_result;

    // ------------------------------------------------------------------
    // Pipeline register updates
    // ------------------------------------------------------------------
    assign imem_addr = pc_f;

    // IF: program counter
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            pc_f <= 32'd0;
        else if (mem_stall)
            pc_f <= pc_f;
        else if (redirect)
            pc_f <= target;
        else if (id_stall || !imem_ready)
            pc_f <= pc_f;
        else
            pc_f <= pc_f + 32'd4;
    end

    // IF/ID
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            d_valid <= 1'b0;
            d_cause <= B_START;
            d_pc    <= 32'd0;
            d_instr <= 32'h0000_0013;                             // NOP (addi x0, x0, 0)
        end else if (mem_stall || (id_stall && !redirect)) begin
            // hold
        end else if (redirect) begin
            d_valid <= 1'b0;
            d_cause <= B_FLUSH;
        end else if (!imem_ready) begin
            d_valid <= 1'b0;
            d_cause <= B_FETCH;
        end else begin
            d_valid <= 1'b1;
            d_pc    <= pc_f;
            d_instr <= imem_rdata;
        end
    end

    // ID/EX: valid bit and bubble cause (reset)
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            e_valid <= 1'b0;
            e_cause <= B_START;
        end else if (mem_stall) begin
            // hold
        end else if (redirect) begin
            e_valid <= 1'b0;
            e_cause <= B_FLUSH;
        end else if (id_stall) begin
            e_valid <= 1'b0;
            e_cause <= stall_cause;
        end else begin
            e_valid <= d_valid;
            e_cause <= d_cause;
        end
    end

    // ID/EX: instruction data (no reset needed, e_valid qualifies it)
    always_ff @(posedge clk) begin
        if (mem_stall) begin
            // hold the instruction, but keep its operands current: a value it
            // gets by forwarding from WB disappears while the pipe is frozen
            e_rv1 <= fwd_a;
            e_rv2 <= fwd_b;
        end else if (!redirect && !id_stall) begin
            e_pc        <= d_pc;
            e_rv1       <= d_rv1;
            e_rv2       <= d_rv2;
            e_imm       <= d_imm;
            e_rs1       <= d_rs1;
            e_rs2       <= d_rs2;
            e_rd        <= d_rd;
            e_alu_op    <= d_alu_op;
            e_a_sel     <= d_a_sel;
            e_b_imm     <= d_b_imm;
            e_reg_write <= d_reg_write;
            e_mem_read  <= d_mem_read;
            e_mem_write <= d_mem_write;
            e_branch    <= d_branch;
            e_jal       <= d_jal;
            e_jalr      <= d_jalr;
            e_funct3    <= d_funct3;
        end
    end

    // EX/MEM
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            m_valid <= 1'b0;
            m_cause <= B_START;
        end else if (!mem_stall) begin
            m_valid      <= e_valid;
            m_cause      <= e_cause;
            m_result     <= ex_result;
            m_store_data <= fwd_b;
            m_rd         <= e_rd;
            m_reg_write  <= e_reg_write;
            m_mem_read   <= e_mem_read;
            m_mem_write  <= e_mem_write;
            m_funct3     <= e_funct3;
        end
    end

    // MEM/WB
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            w_valid <= 1'b0;
            w_cause <= B_START;
        end else if (mem_stall) begin
            w_valid <= 1'b0;
            w_cause <= B_DMEM;
        end else begin
            w_valid     <= m_valid;
            w_cause     <= m_cause;
            w_value     <= m_value;
            w_rd        <= m_rd;
            w_reg_write <= m_reg_write;
        end
    end

    // ------------------------------------------------------------------
    // Performance counters (counted where instructions retire, in WB)
    // ------------------------------------------------------------------
    logic [63:0] perf_cycles, perf_instret;
    logic [31:0] perf_bubble [0:5];
    logic [31:0] perf_branches, perf_taken, perf_jumps, perf_loads, perf_stores;

    // cycles and retired instructions: the two numbers behind CPI
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            perf_cycles  <= '0;
            perf_instret <= '0;
        end else begin
            perf_cycles <= perf_cycles + 1'b1;
            if (w_valid)
                perf_instret <= perf_instret + 1'b1;
        end
    end

    // empty pipeline slots, by the reason they are empty
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            for (int i = 0; i < 6; i++)
                perf_bubble[i] <= 32'd0;
        end else begin
            if (!w_valid)
                perf_bubble[w_cause] <= perf_bubble[w_cause] + 32'd1;
        end
    end

    // instruction mix, counted when an instruction leaves EX
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            perf_branches <= 32'd0;
            perf_taken    <= 32'd0;
            perf_jumps    <= 32'd0;
            perf_loads    <= 32'd0;
            perf_stores   <= 32'd0;
        end else if (e_valid && !mem_stall) begin
            if (e_branch)
                perf_branches <= perf_branches + 32'd1;
            if (e_branch && taken)
                perf_taken <= perf_taken + 32'd1;
            if (e_jal || e_jalr)
                perf_jumps <= perf_jumps + 32'd1;
            if (e_mem_read)
                perf_loads <= perf_loads + 32'd1;
            if (e_mem_write)
                perf_stores <= perf_stores + 32'd1;
        end
    end

endmodule
cvsoc_cpu_subsys.sv
// cvsoc_cpu_subsys: the CPU subsystem of cvsoc.
//
// - cvr32 core
// - 64 KB tightly coupled memory (TCM) at 0x0000_0000: code, data and stack,
//   one-cycle access from both the instruction and the data port
// - every other data address goes out on an AXI4-Lite master port
//
// The AXI4-Lite master handles one transaction at a time: the core's memory
// stage waits (dmem_ready = 0) until the response comes back.
module cvsoc_cpu_subsys (
    input  logic        clk,
    input  logic        rst_n,

    // AXI4-Lite master port (to the interconnect)
    output logic [31:0] m_awaddr,
    output logic        m_awvalid,
    input  logic        m_awready,
    output logic [31:0] m_wdata,
    output logic [3:0]  m_wstrb,
    output logic        m_wvalid,
    input  logic        m_wready,
    input  logic [1:0]  m_bresp,
    input  logic        m_bvalid,
    output logic        m_bready,
    output logic [31:0] m_araddr,
    output logic        m_arvalid,
    input  logic        m_arready,
    input  logic [31:0] m_rdata,
    input  logic [1:0]  m_rresp,
    input  logic        m_rvalid,
    output logic        m_rready
);

    // ------------------------------------------------------------------
    // Core
    // ------------------------------------------------------------------
    logic [31:0] imem_addr, imem_rdata;
    logic [31:0] dmem_addr, dmem_wdata, dmem_rdata;
    logic [3:0]  dmem_wstrb;
    logic        dmem_we, dmem_re, dmem_ready;

    cvr32_core u_core (
        .clk        (clk),
        .rst_n      (rst_n),
        .cfg_fwd_en (1'b1),
        .imem_addr  (imem_addr),
        .imem_rdata (imem_rdata),
        .imem_ready (1'b1),
        .dmem_addr  (dmem_addr),
        .dmem_wdata (dmem_wdata),
        .dmem_wstrb (dmem_wstrb),
        .dmem_we    (dmem_we),
        .dmem_re    (dmem_re),
        .dmem_rdata (dmem_rdata),
        .dmem_ready (dmem_ready)
    );

    // ------------------------------------------------------------------
    // Local address decode: TCM or bus
    // ------------------------------------------------------------------
    logic d_tcm, d_bus;
    assign d_tcm = (dmem_addr[31:16] == 16'h0000);
    assign d_bus = (dmem_we || dmem_re) && !d_tcm;

    // ------------------------------------------------------------------
    // TCM: 16K x 32-bit words, instruction port + data port
    // ------------------------------------------------------------------
    logic [31:0] tcm [0:16383];

    assign imem_rdata = tcm[imem_addr[15:2]];

    always_ff @(posedge clk) begin
        if (dmem_we && d_tcm) begin
            for (int b = 0; b < 4; b++)
                if (dmem_wstrb[b])
                    tcm[dmem_addr[15:2]][8*b +: 8] <= dmem_wdata[8*b +: 8];
        end
    end

    // ------------------------------------------------------------------
    // AXI4-Lite master: one transaction at a time
    // ------------------------------------------------------------------
    localparam logic [1:0] S_IDLE = 2'd0;
    localparam logic [1:0] S_WRITE = 2'd1;
    localparam logic [1:0] S_READ = 2'd2;
    localparam logic [1:0] S_DONE = 2'd3;

    logic [1:0]  state;
    logic        aw_done, w_done;
    logic [31:0] bus_rdata;
    logic        bus_error;                // last response was SLVERR or DECERR

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state     <= S_IDLE;
            aw_done   <= 1'b0;
            w_done    <= 1'b0;
            bus_rdata <= 32'd0;
            bus_error <= 1'b0;
        end else begin
            case (state)
                S_IDLE: begin
                    if (d_bus) begin
                        aw_done <= 1'b0;
                        w_done  <= 1'b0;
                        state   <= dmem_we ? S_WRITE : S_READ;
                    end
                end
                S_WRITE: begin
                    if (m_awvalid && m_awready)
                        aw_done <= 1'b1;
                    if (m_wvalid && m_wready)
                        w_done <= 1'b1;
                    if (m_bvalid) begin
                        bus_error <= (m_bresp != 2'b00);
                        state     <= S_DONE;
                    end
                end
                S_READ: begin
                    if (m_rvalid) begin
                        bus_rdata <= m_rdata;
                        bus_error <= (m_rresp != 2'b00);
                        state     <= S_DONE;
                    end
                end
                default: state <= S_IDLE;           // S_DONE: the core takes the result this cycle
            endcase
        end
    end

    assign m_awaddr  = dmem_addr;
    assign m_awvalid = (state == S_WRITE) && !aw_done;
    assign m_wdata   = dmem_wdata;
    assign m_wstrb   = dmem_wstrb;
    assign m_wvalid  = (state == S_WRITE) && !w_done;
    assign m_bready  = (state == S_WRITE);

    // the read address is offered in the cycle after the request is seen
    logic ar_done;
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            ar_done <= 1'b0;
        else if (state == S_IDLE)
            ar_done <= 1'b0;
        else if (m_arvalid && m_arready)
            ar_done <= 1'b1;
    end

    assign m_araddr  = dmem_addr;
    assign m_arvalid = (state == S_READ) && !ar_done;
    assign m_rready  = (state == S_READ);

    // ------------------------------------------------------------------
    // Data back to the core
    // ------------------------------------------------------------------
    assign dmem_ready = !d_bus || (state == S_DONE);
    assign dmem_rdata = d_tcm ? tcm[dmem_addr[15:2]] : bus_rdata;

endmodule
cvsoc_axil_xbar.sv
// cvsoc_axil_xbar: AXI4-Lite interconnect, 1 master to N slaves.
//
// Each slave owns the address window [BASE, BASE + SIZE). The interconnect
// routes a transaction to the slave whose window holds the address. An
// address that no slave owns gets a DECERR response from the interconnect
// itself, so the master never hangs.
//
// Slave signals are packed side by side: slave i uses bits [32*i +: 32] of a
// 32-bit field and bit [i] of a 1-bit field.
module cvsoc_axil_xbar #(
    parameter int          N    = 2,
    parameter logic [63:0] BASE = {32'h4000_0000, 32'h2000_0000},  // slave 1, slave 0
    parameter logic [63:0] SIZE = {32'h0001_0000, 32'h0000_4000}
) (
    input  logic            clk,
    input  logic            rst_n,

    // from the master
    input  logic [31:0]     s_awaddr,
    input  logic            s_awvalid,
    output logic            s_awready,
    input  logic [31:0]     s_wdata,
    input  logic [3:0]      s_wstrb,
    input  logic            s_wvalid,
    output logic            s_wready,
    output logic [1:0]      s_bresp,
    output logic            s_bvalid,
    input  logic            s_bready,
    input  logic [31:0]     s_araddr,
    input  logic            s_arvalid,
    output logic            s_arready,
    output logic [31:0]     s_rdata,
    output logic [1:0]      s_rresp,
    output logic            s_rvalid,
    input  logic            s_rready,

    // to the slaves
    output logic [32*N-1:0] m_awaddr,
    output logic [N-1:0]    m_awvalid,
    input  logic [N-1:0]    m_awready,
    output logic [32*N-1:0] m_wdata,
    output logic [4*N-1:0]  m_wstrb,
    output logic [N-1:0]    m_wvalid,
    input  logic [N-1:0]    m_wready,
    input  logic [2*N-1:0]  m_bresp,
    input  logic [N-1:0]    m_bvalid,
    output logic [N-1:0]    m_bready,
    output logic [32*N-1:0] m_araddr,
    output logic [N-1:0]    m_arvalid,
    input  logic [N-1:0]    m_arready,
    input  logic [32*N-1:0] m_rdata,
    input  logic [2*N-1:0]  m_rresp,
    input  logic [N-1:0]    m_rvalid,
    output logic [N-1:0]    m_rready
);

    // ------------------------------------------------------------------
    // Address decode: one-hot slave select, all zero = no slave
    // ------------------------------------------------------------------
    function automatic logic [N-1:0] decode(input logic [31:0] addr);
        logic [N-1:0] sel;
        sel = '0;
        for (int i = 0; i < N; i++)
            if (addr >= BASE[32*i +: 32] && addr - BASE[32*i +: 32] < SIZE[32*i +: 32])
                sel[i] = 1'b1;
        decode = sel;
    endfunction

    logic [N-1:0] aw_sel, ar_sel;
    assign aw_sel = decode(s_awaddr);
    assign ar_sel = decode(s_araddr);

    // ------------------------------------------------------------------
    // Write path: address and data fan out, the response comes back from
    // the selected slave. wr_sel remembers the target until B is accepted.
    // ------------------------------------------------------------------
    logic [N-1:0] wr_sel;
    logic         wr_busy, wr_err, wr_err_bvalid;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            wr_busy       <= 1'b0;
            wr_sel        <= '0;
            wr_err        <= 1'b0;
            wr_err_bvalid <= 1'b0;
        end else begin
            if (!wr_busy && s_awvalid) begin
                wr_busy <= 1'b1;
                wr_sel  <= aw_sel;
                wr_err  <= (aw_sel == '0);
            end
            // a decode error is answered once both address and data have arrived
            if (wr_busy && wr_err && s_wvalid && !wr_err_bvalid)
                wr_err_bvalid <= 1'b1;
            if (s_bvalid && s_bready) begin
                wr_busy       <= 1'b0;
                wr_err        <= 1'b0;
                wr_err_bvalid <= 1'b0;
            end
        end
    end

    logic [N-1:0] wr_route;           // selected slave, valid from the first cycle
    assign wr_route = wr_busy ? wr_sel : aw_sel;

    always @(*) begin
        for (int i = 0; i < N; i++) begin
            m_awaddr[32*i +: 32] = s_awaddr;
            m_wdata[32*i +: 32]  = s_wdata;
            m_wstrb[4*i +: 4]    = s_wstrb;
            m_awvalid[i]         = s_awvalid && wr_route[i];
            m_wvalid[i]          = s_wvalid && wr_route[i];
            m_bready[i]          = s_bready && wr_sel[i] && wr_busy;
        end
        s_awready = 1'b0;
        s_wready  = 1'b0;
        s_bvalid  = 1'b0;
        s_bresp   = 2'b00;
        for (int i = 0; i < N; i++) begin
            if (wr_route[i]) begin
                s_awready = m_awready[i];
                s_wready  = m_wready[i];
            end
            if (wr_busy && wr_sel[i]) begin
                s_bvalid = m_bvalid[i];
                s_bresp  = m_bresp[2*i +: 2];
            end
        end
        if (wr_route == '0) begin           // no slave: accept and answer DECERR
            s_awready = s_awvalid;
            s_wready  = s_wvalid && wr_busy;
        end
        if (wr_err_bvalid) begin
            s_bvalid = 1'b1;
            s_bresp  = 2'b11;
        end
    end

    // ------------------------------------------------------------------
    // Read path: same idea
    // ------------------------------------------------------------------
    logic [N-1:0] rd_sel;
    logic         rd_busy, rd_err;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            rd_busy <= 1'b0;
            rd_sel  <= '0;
            rd_err  <= 1'b0;
        end else begin
            if (!rd_busy && s_arvalid) begin
                rd_busy <= 1'b1;
                rd_sel  <= ar_sel;
                rd_err  <= (ar_sel == '0);
            end
            if (s_rvalid && s_rready) begin
                rd_busy <= 1'b0;
                rd_err  <= 1'b0;
            end
        end
    end

    logic [N-1:0] rd_route;
    assign rd_route = rd_busy ? rd_sel : ar_sel;

    always @(*) begin
        for (int i = 0; i < N; i++) begin
            m_araddr[32*i +: 32] = s_araddr;
            m_arvalid[i]         = s_arvalid && rd_route[i];
            m_rready[i]          = s_rready && rd_sel[i] && rd_busy;
        end
        s_arready = 1'b0;
        s_rvalid  = 1'b0;
        s_rdata   = 32'd0;
        s_rresp   = 2'b00;
        for (int i = 0; i < N; i++) begin
            if (rd_route[i])
                s_arready = m_arready[i];
            if (rd_busy && rd_sel[i]) begin
                s_rvalid = m_rvalid[i];
                s_rdata  = m_rdata[32*i +: 32];
                s_rresp  = m_rresp[2*i +: 2];
            end
        end
        if (rd_route == '0)
            s_arready = s_arvalid;
        if (rd_busy && rd_err) begin
            s_rvalid = 1'b1;
            s_rresp  = 2'b11;
        end
    end

endmodule
cvsoc_axil_sram.sv
// cvsoc_axil_sram: system SRAM behind an AXI4-Lite slave port.
// A write is accepted when address and data are both present; the response
// follows one cycle later. A read returns data one cycle after the address.
module cvsoc_axil_sram #(
    parameter int WORDS = 4096                     // 16 KB
) (
    input  logic        clk,
    input  logic        rst_n,
    input  logic [31:0] s_awaddr,
    input  logic        s_awvalid,
    output logic        s_awready,
    input  logic [31:0] s_wdata,
    input  logic [3:0]  s_wstrb,
    input  logic        s_wvalid,
    output logic        s_wready,
    output logic [1:0]  s_bresp,
    output logic        s_bvalid,
    input  logic        s_bready,
    input  logic [31:0] s_araddr,
    input  logic        s_arvalid,
    output logic        s_arready,
    output logic [31:0] s_rdata,
    output logic [1:0]  s_rresp,
    output logic        s_rvalid,
    input  logic        s_rready
);

    localparam int AW = $clog2(WORDS);

    logic [31:0] mem [0:WORDS-1];

    // write: take address and data together, then answer
    assign s_awready = s_awvalid && s_wvalid && !s_bvalid;
    assign s_wready  = s_awready;
    assign s_bresp   = 2'b00;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            s_bvalid <= 1'b0;
        else if (s_awready)
            s_bvalid <= 1'b1;
        else if (s_bready)
            s_bvalid <= 1'b0;
    end

    always_ff @(posedge clk) begin
        if (s_awready)
            for (int b = 0; b < 4; b++)
                if (s_wstrb[b])
                    mem[s_awaddr[AW+1:2]][8*b +: 8] <= s_wdata[8*b +: 8];
    end

    // read
    assign s_arready = s_arvalid && !s_rvalid;
    assign s_rresp   = 2'b00;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            s_rvalid <= 1'b0;
        else if (s_arready)
            s_rvalid <= 1'b1;
        else if (s_rready)
            s_rvalid <= 1'b0;
    end

    always_ff @(posedge clk) begin
        if (s_arready)
            s_rdata <= mem[s_araddr[AW+1:2]];
    end

endmodule
cvsoc_axil2apb.sv
// cvsoc_axil2apb: AXI4-Lite slave to APB bridge for the peripheral block.
//
// Each AXI4-Lite transaction becomes one APB transfer: a SETUP cycle (PSEL=1,
// PENABLE=0) and then ACCESS cycles (PENABLE=1) until the peripheral raises
// PREADY. Up to 16 peripherals sit behind the bridge, each with a 4 KB window
// chosen by address bits [15:12].
module cvsoc_axil2apb #(
    parameter int NP = 4                             // number of APB peripherals
) (
    input  logic          clk,
    input  logic          rst_n,

    // AXI4-Lite slave
    input  logic [31:0]   s_awaddr,
    input  logic          s_awvalid,
    output logic          s_awready,
    input  logic [31:0]   s_wdata,
    input  logic [3:0]    s_wstrb,
    input  logic          s_wvalid,
    output logic          s_wready,
    output logic [1:0]    s_bresp,
    output logic          s_bvalid,
    input  logic          s_bready,
    input  logic [31:0]   s_araddr,
    input  logic          s_arvalid,
    output logic          s_arready,
    output logic [31:0]   s_rdata,
    output logic [1:0]    s_rresp,
    output logic          s_rvalid,
    input  logic          s_rready,

    // APB master (shared signals, one PSEL per peripheral)
    input  logic [4*NP-1:0]  slot,                   // address bits [15:12] of each peripheral
    output logic [NP-1:0]    psel,
    output logic             penable,
    output logic             pwrite,
    output logic [11:0]      paddr,
    output logic [31:0]      pwdata,
    output logic [3:0]       pstrb,
    input  logic [32*NP-1:0] prdata,
    input  logic [NP-1:0]    pready,
    input  logic [NP-1:0]    pslverr
);

    localparam logic [1:0] S_IDLE   = 2'd0;
    localparam logic [1:0] S_SETUP  = 2'd1;
    localparam logic [1:0] S_ACCESS = 2'd2;
    localparam logic [1:0] S_RESP   = 2'd3;

    logic [1:0]    state;
    logic          is_write, no_target;
    logic [31:0]   addr, wdata, rdata;
    logic [3:0]    strb;
    logic [NP-1:0] sel;
    logic          err;

    // which peripheral owns an address
    function automatic logic [NP-1:0] find(input logic [31:0] a);
        logic [NP-1:0] f;
        f = '0;
        for (int i = 0; i < NP; i++)
            if (a[15:12] == slot[4*i +: 4])
                f[i] = 1'b1;
        find = f;
    endfunction

    // accept a write (address + data) or a read when idle; writes go first
    assign s_awready = (state == S_IDLE) && s_awvalid && s_wvalid;
    assign s_wready  = s_awready;
    assign s_arready = (state == S_IDLE) && s_arvalid && !(s_awvalid && s_wvalid);

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state     <= S_IDLE;
            is_write  <= 1'b0;
            no_target <= 1'b0;
            sel       <= '0;
            err       <= 1'b0;
            addr      <= 32'd0;
            wdata     <= 32'd0;
            rdata     <= 32'd0;
            strb      <= 4'd0;
        end else begin
            case (state)
                S_IDLE: begin
                    if (s_awready) begin
                        is_write  <= 1'b1;
                        addr      <= s_awaddr;
                        wdata     <= s_wdata;
                        strb      <= s_wstrb;
                        sel       <= find(s_awaddr);
                        no_target <= (find(s_awaddr) == '0);
                        state     <= S_SETUP;
                    end else if (s_arready) begin
                        is_write  <= 1'b0;
                        addr      <= s_araddr;
                        sel       <= find(s_araddr);
                        no_target <= (find(s_araddr) == '0);
                        state     <= S_SETUP;
                    end
                end
                S_SETUP: begin
                    if (no_target) begin                     // nothing there: slave error
                        err   <= 1'b1;
                        rdata <= 32'd0;
                        state <= S_RESP;
                    end else begin
                        state <= S_ACCESS;
                    end
                end
                S_ACCESS: begin
                    for (int i = 0; i < NP; i++)
                        if (sel[i] && pready[i]) begin
                            rdata <= prdata[32*i +: 32];
                            err   <= pslverr[i];
                            state <= S_RESP;
                        end
                end
                default: begin                               // S_RESP
                    if ((is_write && s_bready) || (!is_write && s_rready)) begin
                        err   <= 1'b0;
                        state <= S_IDLE;
                    end
                end
            endcase
        end
    end

    assign psel    = (state == S_SETUP || state == S_ACCESS) ? sel : '0;
    assign penable = (state == S_ACCESS);
    assign pwrite  = is_write;
    assign paddr   = addr[11:0];
    assign pwdata  = wdata;
    assign pstrb   = strb;

    assign s_bvalid = (state == S_RESP) && is_write;
    assign s_bresp  = err ? 2'b10 : 2'b00;               // SLVERR
    assign s_rvalid = (state == S_RESP) && !is_write;
    assign s_rdata  = rdata;
    assign s_rresp  = err ? 2'b10 : 2'b00;

endmodule
cvsoc_periph.sv
// cvsoc peripherals on APB: UART transmitter, timer, GPIO and simulation control.
// Every peripheral answers in the first ACCESS cycle (PREADY = 1).

// ----------------------------------------------------------------------
// UART transmitter: 8 data bits, no parity, 1 stop bit, 8-byte FIFO.
//   0x000 TXDATA  write: queue one byte
//   0x004 STATUS  bit 0 = FIFO full, bit 1 = busy (a byte is queued or being sent)
//   0x008 DIV     clock cycles per bit (reset 16)
// ----------------------------------------------------------------------
module cvsoc_uart (
    input  logic        clk,
    input  logic        rst_n,
    input  logic        psel,
    input  logic        penable,
    input  logic        pwrite,
    input  logic [11:0] paddr,
    input  logic [31:0] pwdata,
    output logic [31:0] prdata,
    output logic        pready,
    output logic        pslverr,
    output logic        tx                          // serial line, idle high
);

    logic [7:0]  fifo [0:7];
    logic [2:0]  wptr, rptr;
    logic [3:0]  count;
    logic [15:0] div;
    logic [15:0] baud_cnt;
    logic [3:0]  bit_idx;                           // 0 = start, 1..8 = data, 9 = stop
    logic [7:0]  shift;
    logic        busy;
    logic        wr, pop;

    assign pready  = 1'b1;
    assign pslverr = 1'b0;
    assign wr      = psel && penable && pwrite;

    always @(*) begin
        case (paddr)
            12'h004: prdata = {30'd0, busy || (count != 4'd0), count == 4'd8};
            12'h008: prdata = {16'd0, div};
            default: prdata = 32'd0;
        endcase
    end

    assign pop = !busy && (count != 4'd0);

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            wptr     <= 3'd0;
            rptr     <= 3'd0;
            count    <= 4'd0;
            div      <= 16'd16;
            busy     <= 1'b0;
            baud_cnt <= 16'd0;
            bit_idx  <= 4'd0;
            shift    <= 8'd0;
            tx       <= 1'b1;
        end else begin
            // register writes
            if (wr && paddr == 12'h000 && count != 4'd8) begin
                fifo[wptr] <= pwdata[7:0];
                wptr       <= wptr + 3'd1;
            end
            if (wr && paddr == 12'h008)
                div <= pwdata[15:0];
            count <= count + ((wr && paddr == 12'h000 && count != 4'd8) ? 4'd1 : 4'd0)
                           - (pop ? 4'd1 : 4'd0);

            // transmitter
            if (pop) begin
                shift    <= fifo[rptr];
                rptr     <= rptr + 3'd1;
                busy     <= 1'b1;
                bit_idx  <= 4'd0;
                baud_cnt <= 16'd0;
                tx       <= 1'b0;                   // start bit
            end else if (busy) begin
                if (baud_cnt == div - 16'd1) begin
                    baud_cnt <= 16'd0;
                    bit_idx  <= bit_idx + 4'd1;
                    if (bit_idx < 4'd8) begin
                        tx    <= shift[0];          // LSB first
                        shift <= shift >> 1;
                    end else if (bit_idx == 4'd8) begin
                        tx <= 1'b1;                 // stop bit
                    end else begin
                        busy <= 1'b0;
                    end
                end else begin
                    baud_cnt <= baud_cnt + 16'd1;
                end
            end
        end
    end

endmodule

// ----------------------------------------------------------------------
// Timer: a 64-bit counter that increments every clock, and a compare value.
//   0x000 MTIME_LO   0x004 MTIME_HI   (read only)
//   0x008 CMP_LO     0x00C CMP_HI     irq = 1 while MTIME >= CMP
// ----------------------------------------------------------------------
module cvsoc_timer (
    input  logic        clk,
    input  logic        rst_n,
    input  logic        psel,
    input  logic        penable,
    input  logic        pwrite,
    input  logic [11:0] paddr,
    input  logic [31:0] pwdata,
    output logic [31:0] prdata,
    output logic        pready,
    output logic        pslverr,
    output logic        irq
);

    logic [63:0] mtime, cmp;

    assign pready  = 1'b1;
    assign pslverr = 1'b0;
    assign irq     = (mtime >= cmp);

    always @(*) begin
        case (paddr)
            12'h000: prdata = mtime[31:0];
            12'h004: prdata = mtime[63:32];
            12'h008: prdata = cmp[31:0];
            12'h00C: prdata = cmp[63:32];
            default: prdata = 32'd0;
        endcase
    end

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            mtime <= 64'd0;
            cmp   <= 64'hFFFF_FFFF_FFFF_FFFF;
        end else begin
            mtime <= mtime + 64'd1;
            if (psel && penable && pwrite && paddr == 12'h008)
                cmp[31:0] <= pwdata;
            if (psel && penable && pwrite && paddr == 12'h00C)
                cmp[63:32] <= pwdata;
        end
    end

endmodule

// ----------------------------------------------------------------------
// GPIO: 32 outputs and 32 inputs.
//   0x000 OUT (read/write)   0x004 IN (read only)
// ----------------------------------------------------------------------
module cvsoc_gpio (
    input  logic        clk,
    input  logic        rst_n,
    input  logic        psel,
    input  logic        penable,
    input  logic        pwrite,
    input  logic [11:0] paddr,
    input  logic [31:0] pwdata,
    output logic [31:0] prdata,
    output logic        pready,
    output logic        pslverr,
    output logic [31:0] gpio_out,
    input  logic [31:0] gpio_in
);

    assign pready  = 1'b1;
    assign pslverr = 1'b0;
    assign prdata  = (paddr == 12'h004) ? gpio_in : gpio_out;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            gpio_out <= 32'd0;
        else if (psel && penable && pwrite && paddr == 12'h000)
            gpio_out <= pwdata;
    end

endmodule

// ----------------------------------------------------------------------
// Simulation control (simulation only, not real hardware): the program
// writes its result here to end the test.
//   0x000 EXIT  write: done = 1, code = written value
// ----------------------------------------------------------------------
module cvsoc_simctrl (
    input  logic        clk,
    input  logic        rst_n,
    input  logic        psel,
    input  logic        penable,
    input  logic        pwrite,
    input  logic [11:0] paddr,
    input  logic [31:0] pwdata,
    output logic [31:0] prdata,
    output logic        pready,
    output logic        pslverr,
    output logic        done,
    output logic [31:0] code
);

    assign pready  = 1'b1;
    assign pslverr = 1'b0;
    assign prdata  = code;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            done <= 1'b0;
            code <= 32'd0;
        end else if (psel && penable && pwrite && paddr == 12'h000 && !done) begin
            done <= 1'b1;
            code <= pwdata;
        end
    end

endmodule
cvsoc_top.sv
// cvsoc_top: the cvsoc reference SoC, version 0.
//
// Memory map
//   0x0000_0000 - 0x0000_FFFF  TCM, 64 KB (inside the CPU subsystem)
//   0x2000_0000 - 0x2000_3FFF  system SRAM, 16 KB      (AXI4-Lite slave 0)
//   0x4000_0000 - 0x4000_FFFF  peripherals on APB      (AXI4-Lite slave 1)
//       0x4000_0000  UART
//       0x4000_1000  timer
//       0x4000_2000  GPIO
//       0x4000_F000  simulation control (simulation only)
//   anything else              DECERR from the interconnect
module cvsoc_top (
    input  logic        clk,
    input  logic        rst_n,
    output logic        uart_tx,
    output logic [31:0] gpio_out,
    input  logic [31:0] gpio_in,
    output logic        timer_irq,
    output logic        sim_done,
    output logic [31:0] sim_code
);

    // ------------------------------------------------------------------
    // CPU subsystem -> interconnect
    // ------------------------------------------------------------------
    logic [31:0] c_awaddr, c_wdata, c_araddr, c_rdata;
    logic [3:0]  c_wstrb;
    logic [1:0]  c_bresp, c_rresp;
    logic        c_awvalid, c_awready, c_wvalid, c_wready, c_bvalid, c_bready;
    logic        c_arvalid, c_arready, c_rvalid, c_rready;

    cvsoc_cpu_subsys u_cpu (
        .clk       (clk),
        .rst_n     (rst_n),
        .m_awaddr  (c_awaddr),
        .m_awvalid (c_awvalid),
        .m_awready (c_awready),
        .m_wdata   (c_wdata),
        .m_wstrb   (c_wstrb),
        .m_wvalid  (c_wvalid),
        .m_wready  (c_wready),
        .m_bresp   (c_bresp),
        .m_bvalid  (c_bvalid),
        .m_bready  (c_bready),
        .m_araddr  (c_araddr),
        .m_arvalid (c_arvalid),
        .m_arready (c_arready),
        .m_rdata   (c_rdata),
        .m_rresp   (c_rresp),
        .m_rvalid  (c_rvalid),
        .m_rready  (c_rready)
    );

    // ------------------------------------------------------------------
    // Interconnect: slave 0 = SRAM, slave 1 = APB bridge
    // ------------------------------------------------------------------
    localparam int NS = 2;

    logic [32*NS-1:0] x_awaddr, x_wdata, x_araddr, x_rdata;
    logic [4*NS-1:0]  x_wstrb;
    logic [2*NS-1:0]  x_bresp, x_rresp;
    logic [NS-1:0]    x_awvalid, x_awready, x_wvalid, x_wready, x_bvalid, x_bready;
    logic [NS-1:0]    x_arvalid, x_arready, x_rvalid, x_rready;

    cvsoc_axil_xbar #(
        .N    (NS),
        .BASE ({32'h4000_0000, 32'h2000_0000}),
        .SIZE ({32'h0001_0000, 32'h0000_4000})
    ) u_xbar (
        .clk       (clk),
        .rst_n     (rst_n),
        .s_awaddr  (c_awaddr),
        .s_awvalid (c_awvalid),
        .s_awready (c_awready),
        .s_wdata   (c_wdata),
        .s_wstrb   (c_wstrb),
        .s_wvalid  (c_wvalid),
        .s_wready  (c_wready),
        .s_bresp   (c_bresp),
        .s_bvalid  (c_bvalid),
        .s_bready  (c_bready),
        .s_araddr  (c_araddr),
        .s_arvalid (c_arvalid),
        .s_arready (c_arready),
        .s_rdata   (c_rdata),
        .s_rresp   (c_rresp),
        .s_rvalid  (c_rvalid),
        .s_rready  (c_rready),
        .m_awaddr  (x_awaddr),
        .m_awvalid (x_awvalid),
        .m_awready (x_awready),
        .m_wdata   (x_wdata),
        .m_wstrb   (x_wstrb),
        .m_wvalid  (x_wvalid),
        .m_wready  (x_wready),
        .m_bresp   (x_bresp),
        .m_bvalid  (x_bvalid),
        .m_bready  (x_bready),
        .m_araddr  (x_araddr),
        .m_arvalid (x_arvalid),
        .m_arready (x_arready),
        .m_rdata   (x_rdata),
        .m_rresp   (x_rresp),
        .m_rvalid  (x_rvalid),
        .m_rready  (x_rready)
    );

    // ------------------------------------------------------------------
    // Slave 0: system SRAM
    // ------------------------------------------------------------------
    cvsoc_axil_sram #(.WORDS(4096)) u_sram (
        .clk       (clk),
        .rst_n     (rst_n),
        .s_awaddr  (x_awaddr[0 +: 32]),
        .s_awvalid (x_awvalid[0]),
        .s_awready (x_awready[0]),
        .s_wdata   (x_wdata[0 +: 32]),
        .s_wstrb   (x_wstrb[0 +: 4]),
        .s_wvalid  (x_wvalid[0]),
        .s_wready  (x_wready[0]),
        .s_bresp   (x_bresp[0 +: 2]),
        .s_bvalid  (x_bvalid[0]),
        .s_bready  (x_bready[0]),
        .s_araddr  (x_araddr[0 +: 32]),
        .s_arvalid (x_arvalid[0]),
        .s_arready (x_arready[0]),
        .s_rdata   (x_rdata[0 +: 32]),
        .s_rresp   (x_rresp[0 +: 2]),
        .s_rvalid  (x_rvalid[0]),
        .s_rready  (x_rready[0])
    );

    // ------------------------------------------------------------------
    // Slave 1: APB bridge and peripherals
    // ------------------------------------------------------------------
    localparam int NP = 4;                          // UART, timer, GPIO, simctrl

    logic [NP-1:0]    psel, pready, pslverr;
    logic             penable, pwrite;
    logic [11:0]      paddr;
    logic [31:0]      pwdata;
    logic [3:0]       pstrb;
    logic [32*NP-1:0] prdata;

    cvsoc_axil2apb #(.NP(NP)) u_apb (
        .clk       (clk),
        .rst_n     (rst_n),
        .s_awaddr  (x_awaddr[32 +: 32]),
        .s_awvalid (x_awvalid[1]),
        .s_awready (x_awready[1]),
        .s_wdata   (x_wdata[32 +: 32]),
        .s_wstrb   (x_wstrb[4 +: 4]),
        .s_wvalid  (x_wvalid[1]),
        .s_wready  (x_wready[1]),
        .s_bresp   (x_bresp[2 +: 2]),
        .s_bvalid  (x_bvalid[1]),
        .s_bready  (x_bready[1]),
        .s_araddr  (x_araddr[32 +: 32]),
        .s_arvalid (x_arvalid[1]),
        .s_arready (x_arready[1]),
        .s_rdata   (x_rdata[32 +: 32]),
        .s_rresp   (x_rresp[2 +: 2]),
        .s_rvalid  (x_rvalid[1]),
        .s_rready  (x_rready[1]),
        .slot      ({4'hF, 4'h2, 4'h1, 4'h0}),     // simctrl, GPIO, timer, UART
        .psel      (psel),
        .penable   (penable),
        .pwrite    (pwrite),
        .paddr     (paddr),
        .pwdata    (pwdata),
        .pstrb     (pstrb),
        .prdata    (prdata),
        .pready    (pready),
        .pslverr   (pslverr)
    );

    cvsoc_uart u_uart (
        .clk     (clk),
        .rst_n   (rst_n),
        .psel    (psel[0]),
        .penable (penable),
        .pwrite  (pwrite),
        .paddr   (paddr),
        .pwdata  (pwdata),
        .prdata  (prdata[0 +: 32]),
        .pready  (pready[0]),
        .pslverr (pslverr[0]),
        .tx      (uart_tx)
    );

    cvsoc_timer u_timer (
        .clk     (clk),
        .rst_n   (rst_n),
        .psel    (psel[1]),
        .penable (penable),
        .pwrite  (pwrite),
        .paddr   (paddr),
        .pwdata  (pwdata),
        .prdata  (prdata[32 +: 32]),
        .pready  (pready[1]),
        .pslverr (pslverr[1]),
        .irq     (timer_irq)
    );

    cvsoc_gpio u_gpio (
        .clk      (clk),
        .rst_n    (rst_n),
        .psel     (psel[2]),
        .penable  (penable),
        .pwrite   (pwrite),
        .paddr    (paddr),
        .pwdata   (pwdata),
        .prdata   (prdata[64 +: 32]),
        .pready   (pready[2]),
        .pslverr  (pslverr[2]),
        .gpio_out (gpio_out),
        .gpio_in  (gpio_in)
    );

    cvsoc_simctrl u_simctrl (
        .clk     (clk),
        .rst_n   (rst_n),
        .psel    (psel[3]),
        .penable (penable),
        .pwrite  (pwrite),
        .paddr   (paddr),
        .pwdata  (pwdata),
        .prdata  (prdata[96 +: 32]),
        .pready  (pready[3]),
        .pslverr (pslverr[3]),
        .done    (sim_done),
        .code    (sim_code)
    );

endmodule
tb_boot.sv
// tb_boot: boots cvsoc from the program in main.c.
//
// Pressing Run Simulation builds the C code first, so main.hex always matches the
// sources. The load sequence is the $readmemh line: it fills the TCM inside the CPU
// subsystem, as a debugger or boot ROM would. The serial line is decoded like a
// terminal, and the program ends by writing its result to simulation control.
module tb_boot;

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

    logic        uart_tx, timer_irq, sim_done;
    logic [31:0] gpio_out, sim_code;

    cvsoc_top u_soc (
        .clk       (clk),
        .rst_n     (rst_n),
        .uart_tx   (uart_tx),
        .gpio_out  (gpio_out),
        .gpio_in   (32'h0000_C0DE),
        .timer_irq (timer_irq),
        .sim_done  (sim_done),
        .sim_code  (sim_code)
    );

    // UART terminal, 8N1 at the reset value of the DIV register
    localparam int BIT_CYCLES = 16;
    logic [7:0] rx_byte;

    initial begin
        forever begin
            @(negedge uart_tx);                     // start bit
            repeat (BIT_CYCLES + BIT_CYCLES / 2) @(posedge clk);
            for (int i = 0; i < 8; i++) begin
                rx_byte[i] = uart_tx;
                repeat (BIT_CYCLES) @(posedge clk);
            end
            $write("%c", rx_byte);
        end
    end

    integer cycles = 0;
    always @(posedge clk)
        if (rst_n) cycles <= cycles + 1;

    initial begin
        for (int i = 0; i < 16384; i++)
            u_soc.u_cpu.tcm[i] = 32'd0;
        $readmemh("main.hex", u_soc.u_cpu.tcm);

        repeat (3) @(posedge clk);
        rst_n <= 1'b1;

        fork
            begin
                wait (sim_done);
                $display("Program finished after %0d cycles, result 0x%08x", cycles, sim_code);
            end
            begin
                repeat (2_000_000) @(posedge clk);
                $display("TIMEOUT: the program did not finish within 2,000,000 cycles");
            end
        join_any
        repeat (4) @(posedge clk);                  // let the final write complete
        $finish;
    end

endmodule
memory_map.json
{
  "version": 1,
  "attached": true,
  "regions": [
    {
      "name": "tcm",
      "kind": "ram",
      "origin": "0x00000000",
      "length": "0x00010000",
      "width": 32
    }
  ],
  "exit_addr": "0x4000F000",
  "console_addr": "0x40000000"
}
Admin mode — edit and delete controls are visible on each row. Use the IDE to add new snippets via File → Save as Snippet.