From fc998e4bb5a8621ba8bf793c6dbcecfc70630230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Chaine?= Date: Sun, 28 Dec 2025 00:20:53 +0100 Subject: [PATCH 1/3] Add bootloader for copying code from flash to ram --- CMakeLists.txt | 2 +- firmware/CMakeLists.txt | 3 +- firmware/bootloader/bootloader.S | 77 +++--- firmware/bootloader/elf.S | 263 +++++++++++++++++++++ firmware/bootloader/flash.S | 191 +++++++++++++++ firmware/bootloader/spi.S | 4 +- firmware/bootloader/uart.S | 4 +- firmware/link.ld | 36 ++- src/ecap5_dsoc.sv | 3 +- tests/CMakeLists.txt | 54 +---- tests/benches/CMakeLists.txt | 74 ++++++ tests/benches/ecap5_dsoc/tb_ecap5_dsoc.cpp | 2 +- tests/programs/CMakeLists.txt | 43 ++++ tests/programs/README.md | 5 + tests/programs/link.ld | 53 +++++ tests/programs/main.c | 54 +++++ 16 files changed, 766 insertions(+), 102 deletions(-) create mode 100644 firmware/bootloader/elf.S create mode 100644 firmware/bootloader/flash.S create mode 100644 tests/benches/CMakeLists.txt create mode 100644 tests/programs/CMakeLists.txt create mode 100644 tests/programs/README.md create mode 100644 tests/programs/link.ld create mode 100644 tests/programs/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d9469a..20aa7ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,7 +123,7 @@ if(PROJECT_IS_TOP_LEVEL) # Bitstream generation - set(CLOCK_FREQUENCY 60) + set(CLOCK_FREQUENCY 78) add_ecp5_pll_generation( INPUT_FREQ 24 OUTPUT_FREQ ${CLOCK_FREQUENCY} diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index ddd8ca7..88846dd 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -56,8 +56,7 @@ add_custom_command( add_custom_target(helloworld DEPENDS helloworld.dump helloworld.load) - -add_executable(bootloader.elf ${CMAKE_CURRENT_LIST_DIR}/bootloader/bootloader.S ${CMAKE_CURRENT_LIST_DIR}/bootloader/uart.S ${CMAKE_CURRENT_LIST_DIR}/bootloader/spi.S) +add_executable(bootloader.elf ${CMAKE_CURRENT_LIST_DIR}/bootloader/bootloader.S ${CMAKE_CURRENT_LIST_DIR}/bootloader/uart.S ${CMAKE_CURRENT_LIST_DIR}/bootloader/spi.S ${CMAKE_CURRENT_LIST_DIR}/bootloader/flash.S ${CMAKE_CURRENT_LIST_DIR}/bootloader/elf.S) set_target_properties(bootloader.elf PROPERTIES COMPILE_FLAGS "${CC_OPTS} -march=rv32g -mabi=ilp32" LINK_FLAGS "${CC_OPTS} -march=rv32g -mabi=ilp32 -T${CMAKE_CURRENT_LIST_DIR}/link.ld") diff --git a/firmware/bootloader/bootloader.S b/firmware/bootloader/bootloader.S index 3a683b1..16a121c 100644 --- a/firmware/bootloader/bootloader.S +++ b/firmware/bootloader/bootloader.S @@ -19,61 +19,60 @@ # You should have received a copy of the GNU General Public License # along with ECAP5-DSOC. If not, see . +.equ FLASH_MFID, 0xEF +.equ FLASH_DEVTYPE, 0x40 +.equ FLASH_SIZE, 0x18 + +.equ EXE_ADDR, 0x300000 + .section .text .global _start _start: + la sp, __BOOT_STACK_TOP__ + call configure_uart call configure_spi - call enable_cs - - la a0, boot_str - call send_string - la a0, readingflash_str - call send_string + # Checking the flash ID + li a0, FLASH_MFID + li a1, FLASH_DEVTYPE + li a2, FLASH_SIZE + call check_flash_id + bne a0, x0, _booterr - li a0, 0x9F - call send_spi + # Checking ELF + li a0, EXE_ADDR + call check_elf_type + bne a0, x0, _booterr - li a0, 0x00 - call send_spi - mv s0, a0 + # Loading ELF in RAM + li a0, EXE_ADDR + call load_elf_in_ram - li a0, 0x00 - call send_spi - mv s1, a0 + # Get the ELF entry point + la a0, EXE_ADDR + call get_elf_entry - li a0, 0x00 - call send_spi - mv s2, a0 + # Move the entry to ra + mv ra, a0 - la a0, mfid_str - call send_string - - mv a0, s0 - call send_hex + # Set the stack pointer + la sp, __RAM_END__ - la a0, type_str - call send_string + # Clear registers (except x0, ra and sp) + .irp r, 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 + mv x\r, x0 + .endr - mv a0, s1 - call send_hex + # Jump to ra + ret - la a0, size_str +_booterr: + la a0, bootfail_str call send_string - - mv a0, s2 - call send_hex - - call disable_cs - -_loop: +_loop: j _loop .align 4 -boot_str: .asciz "\n\nBooting..." -readingflash_str: .asciz "\nReading flash..." -mfid_str: .asciz "\nManufacturer ID : " -type_str: .asciz "\nType : " -size_str: .asciz "\nSize : " +bootfail_str: .asciz "\n[BOOT] FAIL\n" diff --git a/firmware/bootloader/elf.S b/firmware/bootloader/elf.S new file mode 100644 index 0000000..6e13cab --- /dev/null +++ b/firmware/bootloader/elf.S @@ -0,0 +1,263 @@ +# __ _ +# ________/ / ___ _(_)__ ___ +# / __/ __/ _ \/ _ `/ / _ \/ -_) +# \__/\__/_//_/\_,_/_/_//_/\__/ +# +# Copyright (C) Clément Chaine +# This file is part of ECAP5-DSOC +# +# ECAP5-DSOC is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ECAP5-DSOC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ECAP5-DSOC. If not, see . + +.equ ELF_CLASS_OFFSET, 0x4 +.equ ELF_MACHINE_OFFSET, 0x12 +.equ ELF_PHOFF_OFFSET, 0x1C +.equ ELF_PHNUM_OFFSET, 0x2C +.equ ELF_ENTRY_OFFSET, 0x18 + +.equ ELF_SEGMENT_HEADER_SIZE, 32 +.equ ELF_SEGMENT_TYPE_PTLOAD, 1 + +.equ ELF_CLASS_ELF32, 1 +.equ ELF_MACHINE_RISCV, 0xF3 + +# +# Checks the elf type +# a0 : address in flash +# +.global check_elf_type +check_elf_type: + addi sp, sp, -16 + sw ra, 12(sp) + sw s0, 8(sp) + # local 4 byte buffer at offset 4 + + # s0 = address of the elf in flash + mv s0, a0 + + # Read the elf magic (4 bytes) + mv a0, s0 # src + addi a1, sp, 4 # dst + li a2, 3 # size + call copy_flash_to_ram + + # Compare the elf magic + la t0, elf_magic + lw t1, 0(t0) + lw t2, 4(sp) + # Clear the 4th byte + li t3, 0xFFFFFF + and t1, t1, t3 + and t2, t2, t3 + bne t1, t2, _bad_magic + + # Read the elf class + addi a0, s0, ELF_CLASS_OFFSET + addi a1, sp, 4 + li a2, 1 + call copy_flash_to_ram + + # Compare the elf class + li t0, ELF_CLASS_ELF32 + mv t1, x0 + lbu t1, 4(sp) + bne t0, t1, _bad_class + + # Read the elf machine + addi a0, s0, ELF_MACHINE_OFFSET + addi a1, sp, 4 + li a2, 1 + call copy_flash_to_ram + + # Compare the elf class + li t0, ELF_MACHINE_RISCV + mv t1, x0 + lbu t1, 4(sp) + bne t0, t1, _bad_machine + + # Set the return value + mv a0, x0 + + j _check_elf_ret + +_bad_magic: + la a0, bad_magic_str + call send_string + + j _check_elf_ret_bad + +_bad_class: + la a0, bad_class_str + call send_string + + j _check_elf_ret_bad + +_bad_machine: + la a0, bad_machine_str + call send_string + +_check_elf_ret_bad: + # Set the return value + li a0, 1 +_check_elf_ret: + lw ra, 12(sp) + lw s0, 8(sp) + addi sp, sp, 16 + + ret + +# +# Copies the ELF in RAM +# a0 : address in flash +# +.global load_elf_in_ram +load_elf_in_ram: + addi sp, sp, -64 + sw ra, 60(sp) + sw s0, 56(sp) + sw s1, 52(sp) + sw s2, 48(sp) + # 32 byte array at offset 0(sp) to store a segment header + + # s0 = address of the elf in flash + mv s0, a0 + # s1 = number of segments + mv s1, x0 + + # Read the number of entries in the table + addi a0, s0, ELF_PHNUM_OFFSET + mv a1, sp + li a2, 2 + call copy_flash_to_ram + # Store the number of entries + mv s1, x0 + lhu s1, 0(sp) + + # Read the program header table offset + addi a0, s0, ELF_PHOFF_OFFSET + mv a1, sp + li a2, 4 + call copy_flash_to_ram + # Store the address of the program header table + lw t0, 0(sp) + add s2, s0, t0 + + # s2 = address of the program header table + +_read_segment: + # Read the segment header + mv a0, s2 + mv a1, sp + li a2, ELF_SEGMENT_HEADER_SIZE + call copy_flash_to_ram + + # Skip the copy if the segment is not PT_LOAD + li t0, ELF_SEGMENT_TYPE_PTLOAD + lw t1, 0(sp) # p_type + bne t0, t1, _next_segment + + # Skip if the destination address is not in RAM + lw t0, 0x8(sp) # p_vaddr + lw t1, 0x14(sp) # p_memsz + add t1, t0, t1 # p_vaddr + p_memsz + la t2, __RAM_START__ + la t3, __RAM_END__ + blt t0, t2, _segment_not_in_ram + bgt t1, t3, _segment_not_in_ram + + # Copy the segment data in RAM + lw a0, 0x4(sp) # p_offset + add a0, a0, s0 # base + p_offset + lw a1, 0x8(sp) # p_vaddr + lw a2, 0x10(sp) # p_filesz + call copy_flash_to_ram + + # Check if addresses need to be cleared + lw t0, 0x14(sp) # p_memsz + lw t1, 0x10(sp) # p_filesz + beq t0, t1, _next_segment + + # Prepare the clear + sub t0, t0, t1 # p_memsz - p_filesz + lw t2, 0x8(sp) # p_vaddr + add t1, t2, t1 # p_vaddr + p_filesz + +_clear_bss: + # Write zeros (1 instead of 4 as sizes may vary) + sb x0, 0(t1) + + # Move to next address to clear + addi t1, t1, 1 + # Decrement the number of addresses to clear + addi t0, t0, -1 + + # Check if last address to clear + bne t0, x0, _clear_bss + + j _next_segment + +_segment_not_in_ram: + la a0, segnotinram_str + call send_string + + mv a0, s1 + call send_hex + +_next_segment: + # Move to the next segment + addi s2, s2, ELF_SEGMENT_HEADER_SIZE + # Decrement the number of segments left + addi s1, s1, -1 + + # Check if last segment + bne s1, x0, _read_segment + +_load_elf_in_ram_ret: + lw ra, 60(sp) + lw s0, 56(sp) + lw s1, 52(sp) + lw s2, 48(sp) + addi sp, sp, 64 + + ret + +# +# Returns the elf entry point +# a0 : address in flash +# +.global get_elf_entry +get_elf_entry: + addi sp, sp, -16 + sw ra, 12(sp) + # 1 byte used as local table at offset 0 + + # Get the entry field + addi a0, a0, ELF_ENTRY_OFFSET # base + e_entry + mv a1, sp + li a2, 1 + call copy_flash_to_ram + + # Set the return value + lw a0, 0(sp) + + lw ra, 12(sp) + addi sp, sp, 16 + ret + +.align 4 + +elf_magic: .byte 0x7F, 'E', 'L', 'F' +bad_magic_str: .asciz "\n[BOOT] BAD MAGIC " +bad_class_str: .asciz "\n[BOOT] BAD CLASS " +bad_machine_str: .asciz "\n[BOOT] BAD MACHINE " +segnotinram_str: .asciz "\n[BOOT] WARN segment not in RAM " diff --git a/firmware/bootloader/flash.S b/firmware/bootloader/flash.S new file mode 100644 index 0000000..965d99f --- /dev/null +++ b/firmware/bootloader/flash.S @@ -0,0 +1,191 @@ +# __ _ +# ________/ / ___ _(_)__ ___ +# / __/ __/ _ \/ _ `/ / _ \/ -_) +# \__/\__/_//_/\_,_/_/_//_/\__/ +# +# Copyright (C) Clément Chaine +# This file is part of ECAP5-DSOC +# +# ECAP5-DSOC is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ECAP5-DSOC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ECAP5-DSOC. If not, see . + +.section .text + +# +# Checks the flash JEDEC identification +# a0 : manufacturer id +# a1 : device type +# a2 : device size +# +.global check_flash_id +check_flash_id: + addi sp, sp, -32 + sw ra, 28(sp) + sw s0, 24(sp) + sw s1, 20(sp) + sw s2, 16(sp) + sw s3, 12(sp) + sw s4, 8(sp) + sw s5, 4(sp) + + # s0 = expected manufacturer id + mv s0, a0 + # s1 = expected device id + mv s1, a1 + # s2 = expected device size + mv s2, a2 + + call enable_cs + + # send the read Jedec ID command + li a0, 0x9F + call send_spi + # read the manufacturer id + li a0, 0x00 + call send_spi + mv s3, a0 + # read the device type + li a0, 0x00 + call send_spi + mv s4, a0 + # read the device size + li a0, 0x00 + call send_spi + mv s5, a0 + + call disable_cs + + # If the manufacturer id is wrong + bne s0, s3, _display_bad + # If the device type is wrong + bne s1, s4, _display_bad + # If the device size is wrong + bne s2, s5, _display_bad + + # set the return value = 0 + mv a0, x0 + + j _check_flash_id_ret + +_display_bad: + la a0, badflash_str + call send_string + + # display the manufacturer id + la a0, mfid_str + call send_string + + mv a0, s3 + call send_hex + + # display the device type + la a0, type_str + call send_string + + mv a0, s4 + call send_hex + + # display the device size + la a0, size_str + call send_string + + mv a0, s5 + call send_hex + + # set the return value = 1 + li a0, 1 + +_check_flash_id_ret: + lw ra, 28(sp) + lw s0, 24(sp) + lw s1, 20(sp) + lw s2, 16(sp) + lw s3, 12(sp) + lw s4, 8(sp) + lw s5, 4(sp) + addi sp, sp, 32 + + ret + +# +# Copies data from flash to ram +# a0 : src address in flash +# a1 : dst address in ram +# a2 : size in bytes +# +.global copy_flash_to_ram +copy_flash_to_ram: + addi sp, sp, -16 + sw ra, 12(sp) + sw s0, 8(sp) + sw s1, 4(sp) + sw s2, 0(sp) + + # s0 = src + mv s0, a0 + # s1 = dst + mv s1, a1 + # s2 = size + mv s2, a2 + + call enable_cs + + # send fast read command + li a0, 0xB + call send_spi + # send fast read address A23-A16 + srli a0, s0, 16 + andi a0, a0, 0xFF + call send_spi + # send fast read address A15-A8 + srli a0, s0, 8 + andi a0, a0, 0xFF + call send_spi + # send fast read address A7-A0 + andi a0, s0, 0xFF + call send_spi + # send dummy byte + li a0, 0x00 + call send_spi +_copy: + # Load a value from flash + li a0, 0x00 + call send_spi + # a0 contains the read value + sb a0, 0(s1) + + # Increment the dst + addi s1, s1, 1 + # Decrement the size + addi s2, s2, -1 + + # Check if done + bne s2, x0, _copy + +_copy_flash_to_ram_ret: + call disable_cs + + lw ra, 12(sp) + lw s0, 8(sp) + lw s1, 4(sp) + lw s2, 0(sp) + addi sp, sp, 16 + + ret + +.align 4 + +badflash_str: .asciz "\n[BOOT] BAD FLASH" +mfid_str: .asciz "\n Manufacturer ID : " +type_str: .asciz "\n Type : " +size_str: .asciz "\n Size : " diff --git a/firmware/bootloader/spi.S b/firmware/bootloader/spi.S index d07ec86..752bd35 100644 --- a/firmware/bootloader/spi.S +++ b/firmware/bootloader/spi.S @@ -110,8 +110,8 @@ _check_send_done: beq t1, x0, _check_send_done _read_response: - lb a0, SPI_RXDR(t4) - andi a0, a0, 0xFF + mv a0, x0 + lbu a0, SPI_RXDR(t4) ret diff --git a/firmware/bootloader/uart.S b/firmware/bootloader/uart.S index 275b234..25a4506 100644 --- a/firmware/bootloader/uart.S +++ b/firmware/bootloader/uart.S @@ -30,7 +30,7 @@ .equ UART_DS_MASK, (1 << 3) .equ UART_TXE_MASK, (1 << 1) -.equ UART_ACC_INCR, 10 +.equ UART_ACC_INCR, 97 .equ UART_DS, 1 .section .text @@ -74,7 +74,7 @@ _check_send_ready_string: _send_char: # Load the char to send - lb t0, 0(t5) + lbu t0, 0(t5) # If the string is fully sent return beq t0, x0, _return_send_string diff --git a/firmware/link.ld b/firmware/link.ld index 739b83d..a124a0b 100644 --- a/firmware/link.ld +++ b/firmware/link.ld @@ -1,8 +1,40 @@ +/* __ _ + * ________/ / ___ _(_)__ ___ + * / __/ __/ _ \/ _ `/ / _ \/ -_) + * \__/\__/_//_/\_,_/_/_//_/\__/ + * + * Copyright (C) Clément Chaine + * This file is part of ECAP5-DSOC + * + * ECAP5-DSOC is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ECAP5-DSOC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ECAP5-DSOC. If not, see . + */ + OUTPUT_ARCH( "riscv" ) ENTRY(_start) - +MEMORY +{ + RAM (rwx) : ORIGIN = 0x0, LENGTH = 0x1A000 + BOOT (rwx) : ORIGIN = 0x1A000-2K, LENGTH = 2K +} SECTIONS { - .text : { *(.text) } + .text : { + *(.text) + } > BOOT } +__BOOT_STACK_TOP__ = ORIGIN(BOOT) + LENGTH(BOOT); + +__RAM_START__ = ORIGIN(RAM); +__RAM_END__ = ORIGIN(RAM) + LENGTH(RAM); diff --git a/src/ecap5_dsoc.sv b/src/ecap5_dsoc.sv index b372966..2f5d7a3 100644 --- a/src/ecap5_dsoc.sv +++ b/src/ecap5_dsoc.sv @@ -134,7 +134,8 @@ generated_pll pll_inst ( ); ecap5_dproc #( - .BOOT_ADDRESS (32'h0) + // last 4K region for boot + .BOOT_ADDRESS (32'h19800) ) core_inst ( .clk_i (clk), .rst_i (rst), diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4f13b16..99decf6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -19,56 +19,6 @@ # You should have received a copy of the GNU General Public License # along with ECAP5-DSOC. If not, see . -# Bench parent directory -set(BENCH_DIR ${CMAKE_CURRENT_SOURCE_DIR}/benches/) -# C++ bench include directory -set(TEST_INCLUDE_DIRS ${ecap5_ecap5_dtestlib_SOURCE_DIR}/include) -# Bench CSV output directory -set(TESTDATA_DIR ${CMAKE_CURRENT_BINARY_DIR}/) +add_subdirectory(benches) +add_subdirectory(programs) -# Lint -add_lint_target( - TARGET lint - LIB ecap5_dsoc -) - -# Generate a mocked pll -file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/generated_pll.v - "module generated_pll(\n" - " input clkin,\n" - " output clkout0,\n" - " output locked\n" - ");\n" - "logic locked_q = 1'b0;\n" - "logic locked_qq;\n" - "always_ff @(posedge clkin) begin\n" - " locked_q <= 1;\n" - " locked_qq <= locked_q;\n" - "end\n" - "assign clkout0 = clkin;\n" - "assign locked = locked_qq;\n" - "endmodule\n" -) -# Create a test interface library -add_library(ecap5_dsoc_tests INTERFACE) -target_sources(ecap5_dsoc_tests INTERFACE - ${CMAKE_CURRENT_BINARY_DIR}/generated_pll.v -) -target_link_libraries(ecap5_dsoc_tests INTERFACE - ecap5_dsoc -) - -# Add testbenches -add_testbench( - MODULE ecap5_dsoc - LIBS ecap5_dsoc_tests - BENCH_DIR ${BENCH_DIR} - TESTDATA_DIR ${TESTDATA_DIR} - TEST_INCLUDE_DIRS ${TEST_INCLUDE_DIRS} - DEPENDS bootloader - DEFINES BOOTLOADER_PATH=\\\"${CMAKE_BINARY_DIR}/firmware/bootloader.load\\\" -) - -# Main targets -add_custom_target(build DEPENDS ${TEST_BINARIES}) -add_custom_target(tests DEPENDS ${TEST_TARGETS}) diff --git a/tests/benches/CMakeLists.txt b/tests/benches/CMakeLists.txt new file mode 100644 index 0000000..1273f19 --- /dev/null +++ b/tests/benches/CMakeLists.txt @@ -0,0 +1,74 @@ +# __ _ +# ________/ / ___ _(_)__ ___ +# / __/ __/ _ \/ _ `/ / _ \/ -_) +# \__/\__/_//_/\_,_/_/_//_/\__/ +# +# Copyright (C) Clément Chaine +# This file is part of ECAP5-DSOC +# +# ECAP5-DSOC is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ECAP5-DSOC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ECAP5-DSOC. If not, see . + +# Bench parent directory +set(BENCH_DIR ${CMAKE_CURRENT_SOURCE_DIR}/) +# C++ bench include directory +set(TEST_INCLUDE_DIRS ${ecap5_ecap5_dtestlib_SOURCE_DIR}/include) +# Bench CSV output directory +set(TESTDATA_DIR ${CMAKE_CURRENT_BINARY_DIR}/../) + +# Lint +add_lint_target( + TARGET lint + LIB ecap5_dsoc +) + +# Generate a mocked pll +file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/generated_pll.v + "module generated_pll(\n" + " input clkin,\n" + " output clkout0,\n" + " output locked\n" + ");\n" + "logic locked_q = 1'b0;\n" + "logic locked_qq;\n" + "always_ff @(posedge clkin) begin\n" + " locked_q <= 1;\n" + " locked_qq <= locked_q;\n" + "end\n" + "assign clkout0 = clkin;\n" + "assign locked = locked_qq;\n" + "endmodule\n" +) +# Create a test interface library +add_library(ecap5_dsoc_tests INTERFACE) +target_sources(ecap5_dsoc_tests INTERFACE + ${CMAKE_CURRENT_BINARY_DIR}/generated_pll.v +) +target_link_libraries(ecap5_dsoc_tests INTERFACE + ecap5_dsoc +) + +# Add testbenches +add_testbench( + MODULE ecap5_dsoc + LIBS ecap5_dsoc_tests + BENCH_DIR ${BENCH_DIR} + TESTDATA_DIR ${TESTDATA_DIR} + TEST_INCLUDE_DIRS ${TEST_INCLUDE_DIRS} + DEPENDS bootloader + DEFINES BOOTLOADER_PATH=\\\"${CMAKE_BINARY_DIR}/firmware/bootloader.load\\\" +) + +# Main targets +add_custom_target(build DEPENDS ${TEST_BINARIES}) +add_custom_target(tests DEPENDS ${TEST_TARGETS}) diff --git a/tests/benches/ecap5_dsoc/tb_ecap5_dsoc.cpp b/tests/benches/ecap5_dsoc/tb_ecap5_dsoc.cpp index e11325b..dd87f94 100644 --- a/tests/benches/ecap5_dsoc/tb_ecap5_dsoc.cpp +++ b/tests/benches/ecap5_dsoc/tb_ecap5_dsoc.cpp @@ -69,7 +69,7 @@ int main(int argc, char ** argv, char ** env) { /************************************************************/ tb->reset(); - // 10ms + // 1ms tb->n_tick(600000); /************************************************************/ diff --git a/tests/programs/CMakeLists.txt b/tests/programs/CMakeLists.txt new file mode 100644 index 0000000..5277c92 --- /dev/null +++ b/tests/programs/CMakeLists.txt @@ -0,0 +1,43 @@ +# __ _ +# ________/ / ___ _(_)__ ___ +# / __/ __/ _ \/ _ `/ / _ \/ -_) +# \__/\__/_//_/\_,_/_/_//_/\__/ +# +# Copyright (C) Clément Chaine +# This file is part of ECAP5-DSOC +# +# ECAP5-DSOC is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ECAP5-DSOC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ECAP5-DSOC. If not, see . + +set(CROSS_COMPILE riscv64-unknown-elf-) +set(CMAKE_ASM_COMPILER ${CROSS_COMPILE}gcc) +set(CMAKE_AR ${CROSS_COMPILE}ar) +set(CMAKE_ASM_COMPILER ${CROSS_COMPILE}gcc) +set(CMAKE_C_COMPILER ${CROSS_COMPILE}gcc) +set(CMAKE_CXX_COMPILER ${CROSS_COMPILE}g++) + +enable_language(ASM) + +set(CC_OPTS "-static \ + -mcmodel=medany \ + -fvisibility=hidden \ + -nostdlib \ + -nostartfiles") + +set(SOURCES ${CMAKE_CURRENT_LIST_DIR}/main.c) +set(LD_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/link.ld) + +add_executable(main.elf ${SOURCES}) +set_target_properties(main.elf + PROPERTIES COMPILE_FLAGS "${CC_OPTS} -march=rv32g -mabi=ilp32" + LINK_FLAGS "${CC_OPTS} -march=rv32g -mabi=ilp32 -T${LD_SCRIPT}") diff --git a/tests/programs/README.md b/tests/programs/README.md new file mode 100644 index 0000000..783c9d3 --- /dev/null +++ b/tests/programs/README.md @@ -0,0 +1,5 @@ +# How to load in flash + +```bash +ecpprog -o 3M main.elf +``` diff --git a/tests/programs/link.ld b/tests/programs/link.ld new file mode 100644 index 0000000..e5ce43a --- /dev/null +++ b/tests/programs/link.ld @@ -0,0 +1,53 @@ +/* __ _ + * ________/ / ___ _(_)__ ___ + * / __/ __/ _ \/ _ `/ / _ \/ -_) + * \__/\__/_//_/\_,_/_/_//_/\__/ + * + * Copyright (C) Clément Chaine + * This file is part of ECAP5-DSOC + * + * ECAP5-DSOC is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ECAP5-DSOC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ECAP5-DSOC. If not, see . + */ + +OUTPUT_ARCH( "riscv" ) +ENTRY(main) +MEMORY +{ + RAM (rwx) : ORIGIN = 0x0, LENGTH = 0x1A000 + /* the flash is not memory mapped, but giving it an address + * will help determine what needs to be loaded in ram */ + FLASH (r) : ORIGIN = 0x10000000, LENGTH = 0xD00000 +} +SECTIONS +{ + .text : { + *(.text) + } > RAM AT > FLASH + + .rodata : { + *(.rodata) + } > RAM AT > FLASH + + .data : { + *(.data) + *(.sdata) + } > RAM AT > FLASH + + .bss : { + *(.bss) + *(.sbss) + } > RAM AT > FLASH + + _stack_top = ORIGIN(RAM) + LENGTH(RAM); +} diff --git a/tests/programs/main.c b/tests/programs/main.c new file mode 100644 index 0000000..a8a64a9 --- /dev/null +++ b/tests/programs/main.c @@ -0,0 +1,54 @@ +/* __ _ + * ________/ / ___ _(_)__ ___ + * / __/ __/ _ \/ _ `/ / _ \/ -_) + * \__/\__/_//_/\_,_/_/_//_/\__/ + * + * Copyright (C) Clément Chaine + * This file is part of ECAP5-DSOC + * + * ECAP5-DSOC is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ECAP5-DSOC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ECAP5-DSOC. If not, see . + */ + +#include +#include + +#define UART_BASE 0x40000000 +#define UART_SR_TXE_MASK (1 << 1) + +typedef struct { + volatile uint32_t sr; + volatile uint32_t cr; + volatile uint32_t rxdr; + volatile uint32_t txdr; +} uart_regs_t; + +void send_string(char * str) { + uart_regs_t * uart = (uart_regs_t *)UART_BASE; + + char * c = str; + while(*c != '\0') { + // Wait for the uart to be ready + while(!(uart->sr & UART_SR_TXE_MASK)) {} + + uart->txdr = *c; + + c += 1; + } +} + +void main(void) { + send_string("Hello, World!\n"); + + while(1); +} From 6e610bfba7463e457237b45af473c900be379d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Chaine?= Date: Mon, 29 Dec 2025 23:18:12 +0100 Subject: [PATCH 2/3] Fix issues with high address memory --- CMakeLists.txt | 2 +- firmware/bootloader/uart.S | 2 +- firmware/link.ld | 4 ++-- src/ecap5_dsoc.sv | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 20aa7ba..28362a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,7 +123,7 @@ if(PROJECT_IS_TOP_LEVEL) # Bitstream generation - set(CLOCK_FREQUENCY 78) + set(CLOCK_FREQUENCY 72) add_ecp5_pll_generation( INPUT_FREQ 24 OUTPUT_FREQ ${CLOCK_FREQUENCY} diff --git a/firmware/bootloader/uart.S b/firmware/bootloader/uart.S index 25a4506..0f7ecfc 100644 --- a/firmware/bootloader/uart.S +++ b/firmware/bootloader/uart.S @@ -30,7 +30,7 @@ .equ UART_DS_MASK, (1 << 3) .equ UART_TXE_MASK, (1 << 1) -.equ UART_ACC_INCR, 97 +.equ UART_ACC_INCR, 105 .equ UART_DS, 1 .section .text diff --git a/firmware/link.ld b/firmware/link.ld index a124a0b..b692d3d 100644 --- a/firmware/link.ld +++ b/firmware/link.ld @@ -24,8 +24,8 @@ OUTPUT_ARCH( "riscv" ) ENTRY(_start) MEMORY { - RAM (rwx) : ORIGIN = 0x0, LENGTH = 0x1A000 - BOOT (rwx) : ORIGIN = 0x1A000-2K, LENGTH = 2K + RAM (rwx) : ORIGIN = 0x0, LENGTH = 0x68000 + BOOT (rwx) : ORIGIN = 0x68000-4K, LENGTH = 4K } SECTIONS { diff --git a/src/ecap5_dsoc.sv b/src/ecap5_dsoc.sv index 2f5d7a3..0315d5c 100644 --- a/src/ecap5_dsoc.sv +++ b/src/ecap5_dsoc.sv @@ -135,7 +135,7 @@ generated_pll pll_inst ( ecap5_dproc #( // last 4K region for boot - .BOOT_ADDRESS (32'h19800) + .BOOT_ADDRESS (32'h67000) ) core_inst ( .clk_i (clk), .rst_i (rst), From 78714fcc8c41e576a4a1ac00595f34d52a1927b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Chaine?= Date: Tue, 30 Dec 2025 00:26:26 +0100 Subject: [PATCH 3/3] Fix bootloader issues --- CMakeLists.txt | 2 +- firmware/bootloader/bootloader.S | 2 ++ firmware/bootloader/elf.S | 27 ++++++++++++++++++++-- firmware/bootloader/uart.S | 2 +- firmware/link.ld | 3 +++ tests/benches/ecap5_dsoc/tb_ecap5_dsoc.cpp | 2 +- 6 files changed, 33 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 28362a1..1adb0f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,7 +123,7 @@ if(PROJECT_IS_TOP_LEVEL) # Bitstream generation - set(CLOCK_FREQUENCY 72) + set(CLOCK_FREQUENCY 68) add_ecp5_pll_generation( INPUT_FREQ 24 OUTPUT_FREQ ${CLOCK_FREQUENCY} diff --git a/firmware/bootloader/bootloader.S b/firmware/bootloader/bootloader.S index 16a121c..d6dd042 100644 --- a/firmware/bootloader/bootloader.S +++ b/firmware/bootloader/bootloader.S @@ -48,6 +48,7 @@ _start: # Loading ELF in RAM li a0, EXE_ADDR call load_elf_in_ram + bne a0, x0, _booterr # Get the ELF entry point la a0, EXE_ADDR @@ -76,3 +77,4 @@ _loop: .align 4 bootfail_str: .asciz "\n[BOOT] FAIL\n" +pon_str: .asciz "\n[BOOT] FAIL\n" diff --git a/firmware/bootloader/elf.S b/firmware/bootloader/elf.S index 6e13cab..97992e7 100644 --- a/firmware/bootloader/elf.S +++ b/firmware/bootloader/elf.S @@ -175,6 +175,15 @@ _read_segment: blt t0, t2, _segment_not_in_ram bgt t1, t3, _segment_not_in_ram + # Error if trying to overwrite bootloader + la t2, __BOOT_START__ + bge t0, t2, _bootloader_overwrite + bgt t1, t2, _bootloader_overwrite + + # Skip the copy if the segment file size is zero + lw t0, 0x10(sp) + beq t0, x0, _skip_copy + # Copy the segment data in RAM lw a0, 0x4(sp) # p_offset add a0, a0, s0 # base + p_offset @@ -182,6 +191,7 @@ _read_segment: lw a2, 0x10(sp) # p_filesz call copy_flash_to_ram +_skip_copy: # Check if addresses need to be cleared lw t0, 0x14(sp) # p_memsz lw t1, 0x10(sp) # p_filesz @@ -222,6 +232,18 @@ _next_segment: # Check if last segment bne s1, x0, _read_segment + # Load the return value + mv a0, x0 + + j _load_elf_in_ram_ret + +_bootloader_overwrite: + la a0, bootoverwrite_str + call send_string + + # Load the return value + li a0, 1 + _load_elf_in_ram_ret: lw ra, 60(sp) lw s0, 56(sp) @@ -244,7 +266,7 @@ get_elf_entry: # Get the entry field addi a0, a0, ELF_ENTRY_OFFSET # base + e_entry mv a1, sp - li a2, 1 + li a2, 4 call copy_flash_to_ram # Set the return value @@ -260,4 +282,5 @@ elf_magic: .byte 0x7F, 'E', 'L', 'F' bad_magic_str: .asciz "\n[BOOT] BAD MAGIC " bad_class_str: .asciz "\n[BOOT] BAD CLASS " bad_machine_str: .asciz "\n[BOOT] BAD MACHINE " -segnotinram_str: .asciz "\n[BOOT] WARN segment not in RAM " +segnotinram_str: .asciz "\n[BOOT] WARN SEGMENT NOT IN RAM " +bootoverwrite_str: .asciz "\n[BOOT] ERROR BOOT OVERWRITE " diff --git a/firmware/bootloader/uart.S b/firmware/bootloader/uart.S index 0f7ecfc..40ad04b 100644 --- a/firmware/bootloader/uart.S +++ b/firmware/bootloader/uart.S @@ -30,7 +30,7 @@ .equ UART_DS_MASK, (1 << 3) .equ UART_TXE_MASK, (1 << 1) -.equ UART_ACC_INCR, 105 +.equ UART_ACC_INCR, 111 .equ UART_DS, 1 .section .text diff --git a/firmware/link.ld b/firmware/link.ld index b692d3d..ffe297c 100644 --- a/firmware/link.ld +++ b/firmware/link.ld @@ -38,3 +38,6 @@ __BOOT_STACK_TOP__ = ORIGIN(BOOT) + LENGTH(BOOT); __RAM_START__ = ORIGIN(RAM); __RAM_END__ = ORIGIN(RAM) + LENGTH(RAM); + +__BOOT_START__ = ORIGIN(BOOT); +__BOOT_END__ = ORIGIN(BOOT) + LENGTH(BOOT); diff --git a/tests/benches/ecap5_dsoc/tb_ecap5_dsoc.cpp b/tests/benches/ecap5_dsoc/tb_ecap5_dsoc.cpp index dd87f94..26cd0d2 100644 --- a/tests/benches/ecap5_dsoc/tb_ecap5_dsoc.cpp +++ b/tests/benches/ecap5_dsoc/tb_ecap5_dsoc.cpp @@ -70,7 +70,7 @@ int main(int argc, char ** argv, char ** env) { tb->reset(); // 1ms - tb->n_tick(600000); + tb->n_tick(60000); /************************************************************/