Bringing up a custom FPGA board or soft-core microcontroller block requires structured verification: starting from clock distribution constraints, to pin mappings, simulation setups, and finally, executing test firmware. This post serves as a technical log detailing clock divider VHDL blocks, Verilog interface modules, assembly test routines, and ModelSim compile scripts.

VHDL Clock Divider Module

For clock routing and sub-harmonic clock generation, we use a simple VHDL clock divider. VHDL is strongly-typed and highly structured, making it suitable for safe clock-tree definitions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity clock_divider is
    generic (
        DIV_FACTOR : integer := 4
    );
    port (
        clk_in  : in  std_logic;
        reset_n : in  std_logic;
        clk_out : out std_logic
    );
end entity clock_divider;

architecture rtl of clock_divider is
    signal r_counter : integer range 0 to (DIV_FACTOR/2)-1 := 0;
    signal r_clk_reg : std_logic := '0';
begin
    process(clk_in, reset_n)
    begin
        if reset_n = '0' then
            r_counter <= 0;
            r_clk_reg <= '0';
        elsif rising_edge(clk_in) then
            if r_counter = (DIV_FACTOR/2)-1 then
                r_counter <= 0;
                r_clk_reg <= not r_clk_reg;
            else
                r_counter <= r_counter + 1;
            end if;
        end if;
    end process;

    clk_out <= r_clk_reg;
end architecture rtl;

Verilog Reset Synchronizer

Metastability is a significant hazard during FPGA power-up. We implement a classic dual-flop reset synchronizer in Verilog to align external resets to the local clock domain.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module reset_synchronizer (
    input  wire clk,
    input  wire rst_n_in,
    output wire rst_n_out
);

    reg sync_reg_0;
    reg sync_reg_1;

    always @(posedge clk or negedge rst_n_in) begin
        if (!rst_n_in) begin
            sync_reg_0 <= 1'b0;
            sync_reg_1 <= 1'b0;
        end else begin
            sync_reg_0 <= 1'b1;
            sync_reg_1 <= sync_reg_0;
        end
    end

    assign rst_n_out = sync_reg_1;

endmodule

RISC-V Assembly Boot Test

For testing the soft-core processor embedded in the logic array, we run a short RISC-V assembly script that initializes registers, clears memory sections, and blinks a status LED.

.section .text
.global _start

_start:
    # Initialize stack pointer
    la sp, _stack_pointer

    # Zero-out the BSS segment
    la a0, _bss_start
    la a1, _bss_end
zero_loop:
    bgeu a0, a1, boot_fw
    sw zero, 0(a0)
    addi a0, a0, 4
    j zero_loop

boot_fw:
    # Blink LED routine
    li t0, 0x40001000      # Address of GPIO Output register
    li t1, 0x01            # Output value (LED on)
blink_loop:
    sw t1, 0(t0)           # Write to GPIO
    li t2, 500000          # Loop count delay
delay_loop:
    addi t2, t2, -1
    bnez t2, delay_loop
    xori t1, t1, 0x01      # Toggle LED bit
    j blink_loop

ModelSim Testbench Simulation Commands

To verify our VHDL and Verilog designs in ModelSim before board deployment, we write a compile and simulation script in Tcl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Create project library
vlib work
vmap work work

# Compile VHDL source files
vcom -93 -work work ./src/clock_divider.vhd

# Compile Verilog source files
vlog -work work ./src/reset_synchronizer.v

# Compile Testbench
vlog -work work ./sim/tb_system.v

# Start simulator with 1ps precision (disable optimization for debug)
vsim -t 1ps -novopt work.tb_system

# Add waves to viewer window
add wave -position insertpoint sim:/tb_system/*
add wave -position insertpoint sim:/tb_system/dut/clock_divider/*

# Run simulation for 10 microseconds
run 10 us