Active

Environment Setup

A ground-up CPU RTL design in SystemVerilog — from combinational building blocks through a fully pipelined processor, with open-source tooling.

Install Core Tools

Bash
# Core simulation & synthesis
brew install icarus-verilog verilator yosys make
# Verible (requires dedicated tap on macOS)
brew tap chipsalliance/verible
brew install verible
# Waveform viewer
brew install surfer
# Schematic generation
npm install -g netlistsvg
# Python dependencies for TerosHDL
brew install uv
uv venv ~/.teroshdl-venv
uv pip install --python ~/.teroshdl-venv/bin/python teroshdl vunit-hdl edalize

TerosHDL Configuration

Install the TerosHDL extension (teros-technology.teroshdl) from VS Code Marketplace, then configure via the TerosHDL sidebar:

SettingValue
General → Python path~/.teroshdl-venv/bin/python
General → Make binary directory/opt/homebrew/opt/make/libexec/gnubin/
General → SimulatorIcarus Verilog
Linter → Verilog/SV error linterVerilator
Linter → Verilog/SV style linterVerible
Linter → Verilator → Arguments-Wall --language 1800-2017 -f yantra-cpu.f
Schematic viewer → BackendYosys
Tools → Icarus Verilog → Installation path/opt/homebrew/bin
Tools → Verilator → Installation path/opt/homebrew/bin
Tools → Verible → Installation path/opt/homebrew/bin
Tools → Yosys → Installation path/opt/homebrew/bin

What each tool gives you through TerosHDL:

  • Verilator → Real-time error linting (missing signals, width mismatches, latch inference)
  • Verible → Style linting (naming conventions, indentation) + auto-formatting + language server (go-to-definition, hover docs)
  • Icarus Verilog → Run simulations directly from the sidebar
  • Yosys → View synthesized schematic diagrams of your modules
  • Surfer → Open .vcd waveform files: surfer dump.vcd

Note: The -f yantra-cpu.f flag tells Verilator to read include paths from the project's yantra-cpu.f filelist.

VS Code Workspace Settings

.vscode/settings.json:

JSON
{
"files.associations": {
"*.sv": "systemverilog",
"*.svh": "systemverilog"
},
"[systemverilog]": {
"editor.defaultFormatter": "teros-technology.teroshdl",
"editor.formatOnSave": true
}
}

Build Script Usage

The build system supports linting (errors + style), simulation (single or batch), synthesis with gate-level stats, SVG schematic generation, waveform viewing, and auto-formatting — all from a single run.sh entry point.

Bash
./run.sh lint # Verilator (errors) + Verible (style) on all RTL
./run.sh fmt # Auto-format all .sv files with Verible
./run.sh sim tb_alu # Compile and simulate a testbench
./run.sh sim tb_alu --wave # Simulate and open waveform in Surfer
./run.sh sim:unit # Run all unit testbenches
./run.sh sim:all # Run all testbenches (unit + integration)
./run.sh synth alu # Synthesize a module with Yosys
./run.sh schematic alu # Generate SVG schematic via netlistsvg
./run.sh check # Verify all tools are installed
./run.sh clean # Remove build artifacts

The Running Script

run.sh:

Bash
#!/opt/homebrew/bin/bash # ============================================================================ # Yantra CPU — Build, Lint, Simulate, and View # Usage: ./run.sh <command> [options] # ============================================================================ clear set -euo pipefail # ── Configuration ─────────────────────────────────────────────────────────── PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BUILD_DIR="${PROJECT_ROOT}/build" RTL_DIR="${PROJECT_ROOT}/rtl" TB_DIR="${PROJECT_ROOT}/tb" PROGRAMS_DIR="${TB_DIR}/programs" # Tools IVERILOG="iverilog -g2012" VVP="vvp" VERILATOR="verilator" VERIBLE_LINT="verible-verilog-lint" VERIBLE_FMT="verible-verilog-format" YOSYS="yosys" SURFER="surfer" # Package file (created later — auto-detected if present) # Packages must be compiled first so other modules can import them PKG="${RTL_DIR}/common/yantra_pkg.sv" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' # No Color # ── Helper Functions ──────────────────────────────────────────────────────── info() { echo -e "${CYAN}[INFO]${NC} $*"; } pass() { echo -e "${GREEN}[PASS]${NC} $*"; } fail() { echo -e "${RED}[FAIL]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } ensure_build_dir() { mkdir -p "${BUILD_DIR}" } # Make a Yosys-compatible copy of an SV file. # Yosys doesn't support: import statements, custom enum types, or enum values # from packages. This function strips/replaces them with raw literals. # The package file itself is read by Yosys unmodified — only module files need this. yosys_sanitize() { local src="$1" local dst="$2" sed -e 's/^[[:space:]]*import[[:space:]].*;//' \ -e 's/alu_op_t/logic [3:0]/g' \ -e "s/ALU_ADD/4'b0000/g" \ -e "s/ALU_SUB/4'b0001/g" \ -e "s/ALU_AND/4'b0010/g" \ -e "s/ALU_OR/4'b0011/g" \ -e "s/ALU_XOR/4'b0100/g" \ -e "s/ALU_SLL/4'b0101/g" \ -e "s/ALU_SRL/4'b0110/g" \ -e "s/ALU_SRA/4'b0111/g" \ -e "s/ALU_SLT/4'b1000/g" \ -e "s/ALU_NOR/4'b1001/g" \ "$src" > "$dst" } # ── Commands ──────────────────────────────────────────────────────────────── cmd_help() { cat <<EOF Yantra CPU Build System Usage: ./run.sh <command> [args] Commands: lint Lint all RTL with Verilator (errors) + Verible (style) lint:errors Lint with Verilator only (catches real bugs) lint:style Lint with Verible only (style checks) fmt Auto-format all .sv files with Verible fmt:check Check formatting without modifying files sim <testbench> Compile and simulate a testbench (e.g., sim tb_mux) sim:unit Run all unit testbenches sim:integration Run all integration testbenches sim:all Run every testbench (unit + integration) wave <name> Open waveform in Surfer (e.g., wave tb_mux) synth <module> Synthesize a module with Yosys and show stats schematic <module> Generate clean SVG schematic (requires netlistsvg) check Verify all tools are installed and accessible clean Remove all build artifacts help Show this help message Examples: ./run.sh lint # Lint everything ./run.sh sim tb_mux # Simulate the mux testbench ./run.sh sim tb_mux --wave # Simulate and auto-open waveforms ./run.sh sim:all # Run all tests ./run.sh wave tb_mux # Open existing waveform ./run.sh synth mux # Synthesize mux module ./run.sh schematic mux # Generate mux schematic SVG ./run.sh fmt # Format all source files EOF } cmd_check() { info "Checking tool installations..." local all_ok=true for tool in iverilog vvp verilator verible-verilog-lint verible-verilog-format yosys surfer netlistsvg; do if command -v "$tool" &>/dev/null; then pass "$tool → $(command -v "$tool")" else fail "$tool → NOT FOUND" all_ok=false fi done echo "" if $all_ok; then pass "All tools installed!" else fail "Some tools are missing. Run 'brew install <tool>' to fix." exit 1 fi } # ── Linting ───────────────────────────────────────────────────────────────── cmd_lint() { cmd_lint_errors echo "" cmd_lint_style } cmd_lint_errors() { info "Linting with Verilator (error checking)..." local sv_files=() while IFS= read -r -d '' f; do sv_files+=("$f") done < <(find "${RTL_DIR}" -name "*.sv" -print0 | sort -z) if [[ ${#sv_files[@]} -eq 0 ]]; then warn "No .sv files found in ${RTL_DIR}" return fi if ${VERILATOR} --lint-only -Wall --language 1800-2017 "${sv_files[@]}" 2>&1; then pass "Verilator: No errors found" else fail "Verilator: Errors detected (see above)" return 1 fi } cmd_lint_style() { info "Linting with Verible (style checking)..." local sv_files=() while IFS= read -r -d '' f; do sv_files+=("$f") done < <(find "${RTL_DIR}" -name "*.sv" -print0 | sort -z) if [[ ${#sv_files[@]} -eq 0 ]]; then warn "No .sv files found in ${RTL_DIR}" return fi local style_ok=true for f in "${sv_files[@]}"; do if ! ${VERIBLE_LINT} "$f" 2>&1; then style_ok=false fi done if $style_ok; then pass "Verible: Style checks passed" else warn "Verible: Style issues found (see above)" fi } # ── Formatting ────────────────────────────────────────────────────────────── cmd_fmt() { info "Formatting all .sv files with Verible..." local count=0 while IFS= read -r -d '' f; do ${VERIBLE_FMT} --inplace "$f" ((count++)) done < <(find "${RTL_DIR}" "${TB_DIR}" -name "*.sv" -print0 | sort -z) pass "Formatted ${count} files" } cmd_fmt_check() { info "Checking formatting (no changes)..." local issues=0 while IFS= read -r -d '' f; do if ! diff -q <(${VERIBLE_FMT} "$f") "$f" &>/dev/null; then warn "Needs formatting: $f" ((issues++)) fi done < <(find "${RTL_DIR}" "${TB_DIR}" -name "*.sv" -print0 | sort -z) if [[ $issues -eq 0 ]]; then pass "All files properly formatted" else fail "${issues} file(s) need formatting. Run: ./run.sh fmt" return 1 fi } # ── Simulation ────────────────────────────────────────────────────────────── cmd_sim() { local tb_name="${1:-}" local open_wave=false # Parse flags for arg in "$@"; do case "$arg" in --wave|-w) open_wave=true ;; esac done if [[ -z "$tb_name" ]]; then fail "Usage: ./run.sh sim <testbench_name> [--wave]" echo " Example: ./run.sh sim tb_mux" echo " Example: ./run.sh sim tb_mux --wave" return 1 fi ensure_build_dir # Find the testbench file local tb_file tb_file=$(find "${TB_DIR}" -name "${tb_name}.sv" | head -1) if [[ -z "$tb_file" ]]; then fail "Testbench not found: ${tb_name}.sv" echo " Looked in: ${TB_DIR}/" echo " Available testbenches:" find "${TB_DIR}" -name "tb_*.sv" -exec basename {} .sv \; | sed 's/^/ /' return 1 fi info "Found testbench: ${tb_file}" # Gather RTL source files (package first, then everything else) local rtl_files=() [[ -f "$PKG" ]] && rtl_files+=("$PKG") while IFS= read -r -d '' f; do [[ "$f" != "$PKG" ]] && rtl_files+=("$f") done < <(find "${RTL_DIR}" -name "*.sv" -print0 | sort -z) local out_file="${BUILD_DIR}/${tb_name}.out" local vcd_file="${BUILD_DIR}/${tb_name}.vcd" # Compile info "Compiling: ${tb_name}..." if ! ${IVERILOG} -o "${out_file}" "${rtl_files[@]}" "${tb_file}"; then fail "Compilation failed" return 1 fi pass "Compiled → ${out_file}" # Simulate info "Simulating: ${tb_name}..." echo "────────────────────────────────────────" (cd "${BUILD_DIR}" && ${VVP} "${out_file}") local sim_exit=$? echo "────────────────────────────────────────" if [[ $sim_exit -eq 0 ]]; then pass "Simulation complete" else fail "Simulation exited with code ${sim_exit}" fi # Open waveforms if requested and .vcd exists if $open_wave; then # Check for .vcd in build dir (testbench may write it there or in cwd) local found_vcd="" for candidate in "${vcd_file}" "${BUILD_DIR}/"*.vcd; do if [[ -f "$candidate" ]]; then found_vcd="$candidate" break fi done if [[ -n "$found_vcd" ]]; then info "Opening waveform: ${found_vcd}" ${SURFER} "${found_vcd}" &>/dev/null & disown else warn "No .vcd file generated. Add \$dumpfile/\$dumpvars to your testbench." fi fi } cmd_sim_unit() { info "Running all unit testbenches..." local pass_count=0 local fail_count=0 while IFS= read -r -d '' tb_file; do local tb_name tb_name=$(basename "$tb_file" .sv) echo "" info "━━━ ${tb_name} ━━━" if cmd_sim "$tb_name"; then ((pass_count++)) else ((fail_count++)) fi done < <(find "${TB_DIR}/unit" -name "tb_*.sv" -print0 2>/dev/null | sort -z) echo "" echo "════════════════════════════════════════" pass "Passed: ${pass_count}" [[ $fail_count -gt 0 ]] && fail "Failed: ${fail_count}" echo "════════════════════════════════════════" [[ $fail_count -eq 0 ]] } cmd_sim_integration() { info "Running all integration testbenches..." local pass_count=0 local fail_count=0 while IFS= read -r -d '' tb_file; do local tb_name tb_name=$(basename "$tb_file" .sv) echo "" info "━━━ ${tb_name} ━━━" if cmd_sim "$tb_name"; then ((pass_count++)) else ((fail_count++)) fi done < <(find "${TB_DIR}/integration" -name "tb_*.sv" -print0 2>/dev/null | sort -z) echo "" echo "════════════════════════════════════════" pass "Passed: ${pass_count}" [[ $fail_count -gt 0 ]] && fail "Failed: ${fail_count}" echo "════════════════════════════════════════" [[ $fail_count -eq 0 ]] } cmd_sim_all() { cmd_sim_unit echo "" cmd_sim_integration } # ── Waveform Viewing ──────────────────────────────────────────────────────── cmd_wave() { local name="${1:-}" if [[ -z "$name" ]]; then fail "Usage: ./run.sh wave <testbench_name>" echo " Available waveforms:" find "${BUILD_DIR}" -name "*.vcd" -exec basename {} .vcd \; 2>/dev/null | sed 's/^/ /' return 1 fi local vcd_file="${BUILD_DIR}/${name}.vcd" if [[ ! -f "$vcd_file" ]]; then # Try to find any matching .vcd vcd_file=$(find "${BUILD_DIR}" -name "${name}*.vcd" | head -1) fi if [[ -z "$vcd_file" || ! -f "$vcd_file" ]]; then fail "No waveform found for: ${name}" echo " Run './run.sh sim ${name}' first to generate it." return 1 fi info "Opening: ${vcd_file}" ${SURFER} "${vcd_file}" &>/dev/null & disown } # ── Synthesis ─────────────────────────────────────────────────────────────── cmd_synth() { local module_name="${1:-}" if [[ -z "$module_name" ]]; then fail "Usage: ./run.sh synth <module_name>" echo " Example: ./run.sh synth mux" return 1 fi ensure_build_dir # Find the module file local module_file module_file=$(find "${RTL_DIR}" -name "${module_name}.sv" | head -1) if [[ -z "$module_file" ]]; then fail "Module not found: ${module_name}.sv" return 1 fi # Create Yosys-compatible copies (strip import lines) local yosys_dir="${BUILD_DIR}/yosys_tmp" mkdir -p "${yosys_dir}" local read_cmds="" # Package first (unmodified) if [[ -f "$PKG" ]]; then read_cmds+="read_verilog -sv ${PKG}; " fi # All other RTL files — sanitize for Yosys compatibility while IFS= read -r -d '' f; do if [[ "$f" != "$PKG" ]]; then local tmp_file="${yosys_dir}/$(basename "$f")" yosys_sanitize "$f" "$tmp_file" read_cmds+="read_verilog -sv ${tmp_file}; " fi done < <(find "${RTL_DIR}" -name "*.sv" -print0 | sort -z) local json_out="${BUILD_DIR}/${module_name}_synth.json" info "Synthesizing ${module_name} with Yosys..." ${YOSYS} -p "${read_cmds} synth -top ${module_name}; stat; write_json ${json_out}" 2>&1 | \ grep -E "^[0-9]|Number|Printing|Warning|Error|stat|===" # Clean up temp files rm -rf "${yosys_dir}" if [[ -f "$json_out" ]]; then pass "Synthesis complete → ${json_out}" else fail "Synthesis failed" return 1 fi } # ── Schematic Generation ──────────────────────────────────────────────────── cmd_schematic() { local module_name="${1:-}" if [[ -z "$module_name" ]]; then fail "Usage: ./run.sh schematic <module_name>" echo " Example: ./run.sh schematic mux" return 1 fi # Check netlistsvg is installed if ! command -v netlistsvg &>/dev/null; then fail "netlistsvg not found. Install with: npm install -g netlistsvg" return 1 fi ensure_build_dir # Find the module file local module_file module_file=$(find "${RTL_DIR}" -name "${module_name}.sv" | head -1) if [[ -z "$module_file" ]]; then fail "Module not found: ${module_name}.sv" return 1 fi # Gather all RTL and create Yosys-compatible copies # Yosys doesn't support 'import' — but it reads packages globally, # so stripping import lines is safe when the package is read first. local yosys_dir="${BUILD_DIR}/yosys_tmp" mkdir -p "${yosys_dir}" local read_cmds="" # Package first (unmodified) if [[ -f "$PKG" ]]; then read_cmds+="read_verilog -sv ${PKG}; " fi # All other RTL files — sanitize for Yosys compatibility while IFS= read -r -d '' f; do if [[ "$f" != "$PKG" ]]; then local tmp_file="${yosys_dir}/$(basename "$f")" yosys_sanitize "$f" "$tmp_file" read_cmds+="read_verilog -sv ${tmp_file}; " fi done < <(find "${RTL_DIR}" -name "*.sv" -print0 | sort -z) local json_out="${BUILD_DIR}/${module_name}_netlist.json" local svg_out="${BUILD_DIR}/${module_name}_schematic.svg" info "Generating schematic for ${module_name}..." # Step 1: Yosys — prep (not synth!) preserves high-level cells like $mux if ! ${YOSYS} -p "${read_cmds} prep -top ${module_name}; write_json -compat-int ${json_out}" 2>&1 | \ grep -E "Warning|Error|Generating" || true; then true # grep may return non-zero if no matches, that's fine fi # Clean up temp files rm -rf "${yosys_dir}" if [[ ! -f "${json_out}" ]]; then fail "Yosys JSON generation failed" return 1 fi # Step 2: netlistsvg — renders proper digital logic symbols if netlistsvg "${json_out}" -o "${svg_out}" 2>&1; then pass "Schematic generated → ${svg_out}" info "Opening schematic..." open "${svg_out}" &>/dev/null & disown else fail "netlistsvg rendering failed" return 1 fi } # ── Clean ─────────────────────────────────────────────────────────────────── cmd_clean() { info "Cleaning build artifacts..." rm -rf "${BUILD_DIR}" pass "Build directory removed" } # ── Main Dispatcher ───────────────────────────────────────────────────────── main() { local cmd="${1:-help}" shift 2>/dev/null || true case "$cmd" in help|--help|-h) cmd_help ;; check) cmd_check ;; lint) cmd_lint ;; lint:errors) cmd_lint_errors ;; lint:style) cmd_lint_style ;; fmt) cmd_fmt ;; fmt:check) cmd_fmt_check ;; sim) cmd_sim "$@" ;; sim:unit) cmd_sim_unit ;; sim:integration) cmd_sim_integration ;; sim:all) cmd_sim_all ;; wave) cmd_wave "$@" ;; synth) cmd_synth "$@" ;; schematic) cmd_schematic "$@" ;; clean) cmd_clean ;; *) fail "Unknown command: ${cmd}" echo "Run './run.sh help' for available commands." exit 1 ;; esac } main "$@"
systemverilogcpu-designcomputer-architecturedigital-designyosys
Was this helpful?