feat: publish FreeRTOS C FC07 card

This commit is contained in:
2026-07-19 16:36:03 +02:00
commit 9fad4f4ccc
196 changed files with 59368 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
#ifndef _HAZARD3_CSR_H
#define _HAZARD3_CSR_H
#ifndef __ASSEMBLER__
#include "stdint.h"
#endif
#define hazard3_csr_dmdata0 0xbff // Debug-mode shadow CSR for DM data transfer
#define hazard3_csr_meiea 0xbe0 // External interrupt pending array
#define hazard3_csr_meipa 0xbe1 // External interrupt enable array
#define hazard3_csr_meifa 0xbe2 // External interrupt force array
#define hazard3_csr_meipra 0xbe3 // External interrupt priority array
#define hazard3_csr_meinext 0xbe4 // Next external interrupt
#define hazard3_csr_meicontext 0xbe5 // External interrupt context register
#define hazard3_csr_msleep 0xbf0 // M-mode sleep control register
#define hazard3_csr_pmpcfgm0 0xbd0 // Non-locking M-mode enables for PMP regions
#define _read_csr(csrname) ({ \
uint32_t __csr_tmp_u32; \
asm volatile ("csrr %0, " #csrname : "=r" (__csr_tmp_u32)); \
__csr_tmp_u32; \
})
#define _write_csr(csrname, data) ({ \
asm volatile ("csrw " #csrname ", %0" : : "r" (data)); \
})
#define _set_csr(csrname, data) ({ \
asm volatile ("csrs " #csrname ", %0" : : "r" (data)); \
})
#define _clear_csr(csrname, data) ({ \
asm volatile ("csrc " #csrname ", %0" : : "r" (data)); \
})
#define _read_write_csr(csrname, data) ({ \
uint32_t __csr_tmp_u32; \
asm volatile ("csrrw %0, " #csrname ", %1" : "=r" (__csr_tmp_u32) : "r" (data)); \
__csr_tmp_u32; \
})
#define _read_set_csr(csrname, data) ({ \
uint32_t __csr_tmp_u32; \
asm volatile ("csrrs %0, " #csrname ", %1" : "=r" (__csr_tmp_u32) : "r" (data)); \
__csr_tmp_u32; \
})
#define _read_clear_csr(csrname, data) ({ \
uint32_t __csr_tmp_u32; \
asm volatile ("csrrc %0, " #csrname ", %1" : "=r" (__csr_tmp_u32) : "r" (data)); \
__csr_tmp_u32; \
})
// Argument macro expansion layer
#define read_csr(csrname) _read_csr(csrname)
#define write_csr(csrname, data) _write_csr(csrname, data)
#define set_csr(csrname, data) _set_csr(csrname, data)
#define clear_csr(csrname, data) _clear_csr(csrname, data)
#define read_write_csr(csrname, data) _read_write_csr(csrname, data)
#define read_set_csr(csrname, data) _read_set_csr(csrname, data)
#define read_clear_csr(csrname, data) _read_clear_csr(csrname, data)
#endif
+32
View File
@@ -0,0 +1,32 @@
#ifndef _HAZARD3_INSTR_H
#define _HAZARD3_INSTR_H
#include <stdint.h>
// C macros for Hazard3 custom instructions
// nbits must be a constant expression
#define __hazard3_bextm(nbits, rs1, rs2) ({\
uint32_t __h3_bextm_rd; \
asm (".insn r 0x0b, 0, %3, %0, %1, %2"\
: "=r" (__h3_bextm_rd) \
: "r" (rs1), "r" (rs2), "i" ((((nbits) - 1) & 0x7) << 1)\
); \
__h3_bextm_rd; \
})
// nbits and shamt must be constant expressions
#define __hazard3_bextmi(nbits, rs1, shamt) ({\
uint32_t __h3_bextmi_rd; \
asm (".insn i 0x0b, 0x4, %0, %1, %2"\
: "=r" (__h3_bextmi_rd) \
: "r" (rs1), "i" ((((nbits) - 1) & 0x7) << 6 | ((shamt) & 0x1f)) \
); \
__h3_bextmi_rd; \
})
#define __hazard3_block() asm ("slt x0, x0, x0" : : : "memory")
#define __hazard3_unblock() asm ("slt x0, x0, x1" : : : "memory")
#endif
+98
View File
@@ -0,0 +1,98 @@
#ifndef _HAZARD3_IRQ_H
#define _HAZARD3_IRQ_H
#include "hazard3_csr.h"
#include "stdint.h"
#include "stdbool.h"
// Should match processor configuration in testbench:
#define NUM_IRQS 32
#define MAX_PRIORITY 15
// Declarations for irq_dispatch.S
extern uintptr_t _external_irq_table[NUM_IRQS];
extern uint32_t _external_irq_entry_count;
#define h3irq_array_read(csr, index) (read_set_csr(csr, (index)) >> 16)
#define h3irq_array_write(csr, index, data) (write_csr(csr, (index) | ((uint32_t)(data) << 16)))
#define h3irq_array_set(csr, index, data) (set_csr(csr, (index) | ((uint32_t)(data) << 16)))
#define h3irq_array_clear(csr, index, data) (clear_csr(csr, (index) | ((uint32_t)(data) << 16)))
static inline void h3irq_enable(unsigned int irq, bool enable) {
if (enable) {
h3irq_array_set(hazard3_csr_meiea, irq >> 4, 1u << (irq & 0xfu));
}
else {
h3irq_array_clear(hazard3_csr_meiea, irq >> 4, 1u << (irq & 0xfu));
}
}
static inline bool h3irq_pending(unsigned int irq) {
return h3irq_array_read(hazard3_csr_meipa, irq >> 4) & (1u << (irq & 0xfu));
}
static inline void h3irq_force_pending(unsigned int irq, bool force) {
if (force) {
h3irq_array_set(hazard3_csr_meifa, irq >> 4, 1u << (irq & 0xfu));
}
else {
h3irq_array_clear(hazard3_csr_meifa, irq >> 4, 1u << (irq & 0xfu));
}
}
static inline bool h3irq_is_forced(unsigned int irq) {
return h3irq_array_read(hazard3_csr_meifa, irq >> 4) & (1u << (irq & 0xfu));
}
// -1 for no IRQ
static inline int h3irq_get_current_irq() {
uint32_t meicontext = read_csr(hazard3_csr_meicontext);
return meicontext & 0x8000u ? -1 : (meicontext >> 4) & 0x1ffu;
}
static inline void h3irq_set_priority(unsigned int irq, uint32_t priority) {
// Don't want read-modify-write, but no instruction for atomically writing
// a bitfield. So, first drop priority to minimum, then set to the target
// value. It should be safe to drop an IRQ's priority below its current
// even from within that IRQ (but it is never safe to boost an IRQ when
// it may already be in an older stack frame)
h3irq_array_clear(hazard3_csr_meipra, irq >> 2, 0xfu << (4 * (irq & 0x3)));
h3irq_array_set(hazard3_csr_meipra, irq >> 2, (priority & 0xfu) << (4 * (irq & 0x3)));
}
static inline void h3irq_set_handler(unsigned int irq, void (*handler)(void)) {
_external_irq_table[irq] = (uintptr_t)handler;
}
static inline void global_irq_enable(bool en) {
// mstatus.mie
if (en) {
set_csr(mstatus, 0x8);
}
else {
clear_csr(mstatus, 0x8);
}
}
static inline void external_irq_enable(bool en) {
// mie.meie
if (en) {
set_csr(mie, 0x800);
}
else {
clear_csr(mie, 0x800);
}
}
static inline void timer_irq_enable(bool en) {
// mie.mtie
if (en) {
set_csr(mie, 0x080);
}
else {
clear_csr(mie, 0x080);
}
}
#endif
+234
View File
@@ -0,0 +1,234 @@
#include "hazard3_csr.h"
#define IO_BASE 0xc0000000
#define IO_PRINT_CHAR (IO_BASE + 0x0)
#define IO_PRINT_U32 (IO_BASE + 0x4)
#define IO_EXIT (IO_BASE + 0x8)
// Provide trap vector table, reset handler and weak default trap handlers for
// Hazard3. This is not a crt0: the reset handler calls an external _start
.option push
.option norelax
.file 1 "vendor/Hazard3/test/sim/common/init.S"
.section .vectors,"ax",@progbits
.macro VEC name:req
.p2align 2
j \name
.p2align 2
.endm
// ----------------------------------------------------------------------------
// Vector table (must be at least aligned to its size rounded up to power of 2)
.p2align 12
.vector_table:
// Single exception vector, also takes IRQs if vectoring is disabled
VEC handle_exception
// Standard interrupts, if vectoring is enabled
// Note: global EIRQ does not fire. Instead we have 16 separate vectors
// handle_exception ^^^ takes the slot where U-mode softirq would be
VEC .halt
VEC .halt
VEC isr_machine_softirq
VEC .halt
VEC .halt
VEC .halt
VEC isr_machine_timer
VEC .halt
VEC .halt
VEC .halt
VEC isr_external_irq
VEC .halt
VEC .halt
VEC .halt
VEC .halt
// ----------------------------------------------------------------------------
// Reset handler
.reset_handler:
// Set counters running, as they are off by default. This may trap if counters
// are unimplemented, so catch the trap and continue.
.loc 1 59 0 ; la a0, 1f
.loc 1 60 0 ; csrw mtvec, a0
.loc 1 61 0 ; csrci mcountinhibit, 0x5
.loc 1 62 0 ; j 2f
.p2align 2
1:
.loc 1 65 0 ; csrw mcause, zero
2:
// Set up trap vector table. mtvec LSB enables vectoring
.loc 1 69 0 ; la a0, .vector_table + 1
.loc 1 70 0 ; csrw mtvec, a0
// Ensure gp is initialised on all cores -- don't wait for newlib _start
// as that is core-0-only
.option push
.option norelax
.loc 1 76 0 ; la gp, __global_pointer$
.option pop
// Put spare cores to sleep before setting up core 0 stack
// Note csrr is a NOP when there are no CSRs at all (no traps!):
.loc 1 81 0 ; li a0, 0
.loc 1 82 0 ; csrr a0, mhartid
.loc 1 83 0 ; bnez a0, .core1_wait
// Set up stack pointer before doing anything else
.loc 1 86 0 ; la sp, __stack_top
// newlib _start expects argc, argv on the stack. Leave stack 16-byte aligned.
.loc 1 89 0 ; addi sp, sp, -16
.loc 1 90 0 ; li a0, 1
.loc 1 91 0 ; sw a0, (sp)
.loc 1 92 0 ; la a0, progname
.loc 1 93 0 ; sw a0, 4(sp)
.loc 1 95 0 ; jal _start
.loc 1 96 0 ; j .halt
.core1_wait:
// IRQs disabled, but soft IRQ unmasked -> soft IRQ will exit WFI.
csrci mstatus, 0x8
csrw mie, 0x8
.core1_wait_loop:
wfi
la a0, core1_entry_vector
lw a0, (a0)
beqz a0, .core1_wait_loop
la sp, __stack_top - 0x10000
jalr a0
.core1_finish:
wfi
j .core1_finish
.p2align 2
.global core1_entry_vector
core1_entry_vector:
.word 0
.global _exit
_exit:
li a1, IO_EXIT
sw a0, (a1)
.global _sbrk
_sbrk:
la a1, heap_ptr
lw a2, (a1)
add a0, a0, a2
sw a0, (a1)
mv a0, a2
ret
.p2align 2
heap_ptr:
.word _end
.global .halt
.halt:
j .halt
progname:
.asciz "hazard3-testbench"
.p2align 2
// ----------------------------------------------------------------------------
// Weak handler/ISR symbols
// Routine to print out trap name, trap address, and some core registers
// (x8..x15, ra, sp). The default handlers are all patched into this routine,
// so the CPU will print some basic diagnostics on any unhandled trap
// (assuming the processor is not internally completely broken)
// argument in t0, IO pointer in t1, return in tp, trashes t0 and t2;
_tb_puts:
1:
lbu t2, (t0)
addi t0, t0, 1
beqz t2, 2f
sw t2, IO_PRINT_CHAR - IO_BASE(t1)
j 1b
2:
jr tp
.macro print_reg str reg
la t0, \str
jal tp, _tb_puts
sw \reg, IO_PRINT_U32 - IO_BASE(t1)
.endm
_weak_handler_name_in_gp:
la t0, _str_unhandled_trap
li t1, IO_BASE
jal tp, _tb_puts
mv t0, gp
jal tp, _tb_puts
la t0, _str_at_mepc
jal tp, _tb_puts
csrr t0, mepc
sw t0, IO_PRINT_U32 - IO_BASE(t1)
csrr gp, mcause
bltz gp, 1f
print_reg _str_mcause gp
1:
print_reg _str_s0 s0
print_reg _str_s1 s1
print_reg _str_a0 a0
print_reg _str_a1 a1
print_reg _str_a2 a2
print_reg _str_a3 a3
print_reg _str_a4 a4
print_reg _str_a5 a5
print_reg _str_ra ra
print_reg _str_sp sp
li t2, -1
sw t2, IO_EXIT - IO_BASE(t1)
// Should be unreachable:
j .halt
_str_unhandled_trap: .asciz "*** Unhandled trap ***\n"
_str_at_mepc: .asciz " @ mepc = "
_str_mcause: .asciz " mcause = "
_str_s0: .asciz "s0: "
_str_s1: .asciz "s1: "
_str_a0: .asciz "a0: "
_str_a1: .asciz "a1: "
_str_a2: .asciz "a2: "
_str_a3: .asciz "a3: "
_str_a4: .asciz "a4: "
_str_a5: .asciz "a5: "
_str_ra: .asciz "ra: "
_str_sp: .asciz "sp: "
.p2align 2
// Provide a default weak handler for each trap, which calls into the above
// diagnostic routine with the trap name (a null-terminated string) in gp
.macro weak_handler name:req
.p2align 2
.global \name
.weak \name
\name:
la gp, _str_\name
j _weak_handler_name_in_gp
_str_\name:
.asciz "\name"
.endm
weak_handler handle_exception
weak_handler isr_machine_softirq
weak_handler isr_machine_timer
weak_handler isr_external_irq
// You can relax now
.option pop
+135
View File
@@ -0,0 +1,135 @@
#include "hazard3_csr.h"
.global isr_external_irq
isr_external_irq:
// Save caller saves and exception return state whilst IRQs are disabled.
// We can't be pre-empted during this time, but if a higher-priority IRQ
// arrives ("late arrival"), that will be the one displayed in meinext.
addi sp, sp, -80
sw ra, 0(sp)
sw t0, 4(sp)
sw t1, 8(sp)
sw t2, 12(sp)
sw a0, 16(sp)
sw a1, 20(sp)
sw a2, 24(sp)
sw a3, 28(sp)
sw a4, 32(sp)
sw a5, 36(sp)
#if __riscv_i
sw a6, 40(sp)
sw a7, 44(sp)
sw t3, 48(sp)
sw t4, 52(sp)
sw t5, 56(sp)
sw t6, 60(sp)
#endif
// Update a count of the number of external IRQ vector entries (just for
// use in tests)
la a0, _external_irq_entry_count
lw a1, (a0)
addi a1, a1, 1
sw a1, (a0)
// Make sure to delete the above ^^^ if you use this code for real!
csrr a0, mepc
sw a0, 64(sp)
// Make sure to set meicontext.clearts to clear and save mie.msie/mtie
// when saving context.
csrrsi a0, hazard3_csr_meicontext, 0x2
sw a0, 68(sp)
csrr a0, mstatus
sw a0, 72(sp)
j get_next_irq
dispatch_irq:
// Preemption priority was configured by meinext update, so enable preemption:
csrsi mstatus, 0x8
// meinext is pre-shifted by 2, so only an add is required to index table
la a1, _external_irq_table
add a1, a1, a0
lw a1, (a1)
jalr ra, a1
// Disable IRQs on returning so we can sample the next IRQ
csrci mstatus, 0x8
get_next_irq:
// Sample the current highest-priority active IRQ (left-shifted by 2) from
// meinext, and write 1 to the LSB to tell hardware to tell hw to update
// meicontext with the preemption priority (and IRQ number) of this IRQ
csrrsi a0, hazard3_csr_meinext, 0x1
// MSB will be set if there is no active IRQ at the current priority level
bgez a0, dispatch_irq
no_more_irqs:
// Restore saved context and return from IRQ
lw a0, 64(sp)
csrw mepc, a0
lw a0, 68(sp)
csrw hazard3_csr_meicontext, a0
lw a0, 72(sp)
csrw mstatus, a0
lw ra, 0(sp)
lw t0, 4(sp)
lw t1, 8(sp)
lw t2, 12(sp)
lw a0, 16(sp)
lw a1, 20(sp)
lw a2, 24(sp)
lw a3, 28(sp)
lw a4, 32(sp)
lw a5, 36(sp)
#if __riscv_i
lw a6, 40(sp)
lw a7, 44(sp)
lw t3, 48(sp)
lw t4, 52(sp)
lw t5, 56(sp)
lw t6, 60(sp)
#endif
addi sp, sp, 80
mret
// ------------------------------------------------------------
// Handler table and default handler symbols
// Provide weak symbol for all IRQs, pointing to a breakpoint instruction:
.macro decl_eirq num
.weak isr_irq\num
isr_irq\num:
.endm
.macro ref_eirq num
.word isr_irq\num
.endm
#define NUM_IRQS 32
.equ i, 0
.rept NUM_IRQS
decl_eirq i
.equ i, i + 1
.endr
ebreak
// Soft vector table is preloaded to RAM, and by default contains the weak ISR
// symbols, but can also be patched at runtime:
.section .data
.global _external_irq_table
_external_irq_table:
.equ i, 0
.rept NUM_IRQS
ref_eirq i
.equ i, i + 1
.endr
.global _external_irq_entry_count
_external_irq_entry_count:
.word 0
+48
View File
@@ -0,0 +1,48 @@
OUTPUT_FORMAT("elf32-littleriscv")
OUTPUT_ARCH(riscv)
ENTRY(_start)
SECTIONS
{
/* Hazard3 testbench RAM window */
. = 0x80000000;
PROVIDE(__stack_top = 0x80100000);
/* Reset/trap vectors are in Hazard3 init.S (.vectors). Keep them first so
.reset_handler lands at 0x80000040 (Hazard3 RESET_VECTOR). */
.text : ALIGN(4)
{
KEEP(*(.vectors))
*(.text .text.*)
}
/* Keep constants non-executable. Merging arbitrary-length strings into
.text makes some RISC-V objdump versions try to decode the final partial
instruction and abort instead of producing the teaching listing. */
.rodata : ALIGN(4)
{
*(.rodata .rodata.*)
}
.data : ALIGN(4)
{
__data_start = .;
*(.data .data.*)
*(.sdata .sdata.*)
__data_end = .;
}
.bss : ALIGN(4)
{
__bss_start = .;
*(.bss .bss.* COMMON)
*(.sbss .sbss.*)
__bss_end = .;
}
/* Conservative default (good enough for this ASM-only demo). */
__global_pointer$ = __data_start + 0x800;
_end = .;
PROVIDE(end = .);
}
+275
View File
@@ -0,0 +1,275 @@
/* Script for -z combreloc */
/* Copyright (C) 2014-2025 Free Software Foundation, Inc.
Copying and distribution of this script, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. */
OUTPUT_FORMAT("elf32-littleriscv", "elf32-littleriscv", "elf32-littleriscv")
OUTPUT_ARCH(riscv)
ENTRY(_start)
SEARCH_DIR("/opt/riscv/gcc15/riscv32-unknown-elf/lib");
SECTIONS
{
. = 0x80000000;
PROVIDE(__stack_top = 0x80100000);
/* Place the build-id as close to the ELF headers as possible. This
maximises the chance the build-id will be present in core files,
which GDB can then use to locate the associated debuginfo file. */
.interp : { *(.interp) }
.hash : { *(.hash) }
.gnu.hash : { *(.gnu.hash) }
.dynsym : { *(.dynsym) }
.dynstr : { *(.dynstr) }
.gnu.version : { *(.gnu.version) }
.gnu.version_d : { *(.gnu.version_d) }
.gnu.version_r : { *(.gnu.version_r) }
.rela.dyn :
{
*(.rela.init)
*(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
*(.rela.fini)
*(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*)
*(.rela.data .rela.data.* .rela.gnu.linkonce.d.*)
*(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*)
*(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*)
*(.rela.ctors)
*(.rela.dtors)
*(.rela.got)
*(.rela.sdata .rela.sdata.* .rela.gnu.linkonce.s.*)
*(.rela.sbss .rela.sbss.* .rela.gnu.linkonce.sb.*)
*(.rela.sdata2 .rela.sdata2.* .rela.gnu.linkonce.s2.*)
*(.rela.sbss2 .rela.sbss2.* .rela.gnu.linkonce.sb2.*)
*(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*)
*(.rela.ifunc)
}
.rela.plt :
{
*(.rela.plt)
PROVIDE_HIDDEN (__rela_iplt_start = .);
*(.rela.iplt)
PROVIDE_HIDDEN (__rela_iplt_end = .);
}
/* Start of the executable code region. */
.init :
{
KEEP (*(SORT_NONE(.init)))
}
.plt : { *(.plt) *(.iplt) }
.text :
{
KEEP(*(.vectors))
*(.text.unlikely .text.*_unlikely .text.unlikely.*)
*(.text.exit .text.exit.*)
*(.text.startup .text.startup.*)
*(.text.hot .text.hot.*)
*(SORT(.text.sorted.*))
*(.text .stub .text.* .gnu.linkonce.t.*)
/* .gnu.warning sections are handled specially by elf.em. */
*(.gnu.warning)
}
.fini :
{
KEEP (*(SORT_NONE(.fini)))
}
PROVIDE (__etext = .);
PROVIDE (_etext = .);
PROVIDE (etext = .);
/* Start of the Read Only Data region. */
.rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
.rodata1 : { *(.rodata1) }
.sdata2 :
{
*(.sdata2 .sdata2.* .gnu.linkonce.s2.*)
}
.sbss2 : { *(.sbss2 .sbss2.* .gnu.linkonce.sb2.*) }
.eh_frame_hdr : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) }
.eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) }
.sframe : ONLY_IF_RO { *(.sframe) *(.sframe.*) }
.gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) }
.gnu_extab : ONLY_IF_RO { *(.gnu_extab*) }
/* These sections are generated by the Sun/Oracle C++ compiler. */
.exception_ranges : ONLY_IF_RO { *(.exception_ranges*) }
/* Various note sections. Placed here so that they are always included
in the read-only segment and not treated as orphan sections. The
current orphan handling algorithm does place note sections after R/O
data, but this is not guaranteed to always be the case. */
.note.build-id : { *(.note.build-id) }
.note.GNU-stack : { *(.note.GNU-stack) }
.note.gnu-property : { *(.note.gnu-property) }
.note.ABI-tag : { *(.note.ABI-tag) }
.note.package : { *(.note.package) }
.note.dlopen : { *(.note.dlopen) }
.note.netbsd.ident : { *(.note.netbsd.ident) }
.note.openbsd.ident : { *(.note.openbsd.ident) }
/* Start of the Read Write Data region. */
/* Adjust the address for the data segment. We want to adjust up to
the same address within the page on the next page up. */
. = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
/* Exception handling. */
.eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) }
.sframe : ONLY_IF_RW { *(.sframe) *(.sframe.*) }
.gnu_extab : ONLY_IF_RW { *(.gnu_extab) }
.gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
.exception_ranges : ONLY_IF_RW { *(.exception_ranges*) }
/* Thread Local Storage sections. */
.tdata :
{
PROVIDE_HIDDEN (__tdata_start = .);
*(.tdata .tdata.* .gnu.linkonce.td.*)
}
.tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
}
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
PROVIDE_HIDDEN (__init_array_end = .);
}
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
PROVIDE_HIDDEN (__fini_array_end = .);
}
.ctors :
{
/* gcc uses crtbegin.o to find the start of
the constructors, so we make sure it is
first. Because this is a wildcard, it
doesn't matter if the user does not
actually link against crtbegin.o; the
linker won't look for a file to match a
wildcard. The wildcard also means that it
doesn't matter which directory crtbegin.o
is in. */
KEEP (*crtbegin.o(.ctors))
KEEP (*crtbegin?.o(.ctors))
/* We don't want to include the .ctor section from
the crtend.o file until after the sorted ctors.
The .ctor section from the crtend file contains the
end of ctors marker and it must be last */
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
}
.dtors :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*crtbegin?.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
}
.jcr : { KEEP (*(.jcr)) }
.data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) }
.dynamic : { *(.dynamic) }
.got : { *(.got) *(.igot) }
. = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 8 ? 8 : 0, .);
.got.plt : { *(.got.plt) *(.igot.plt) }
.data :
{
__DATA_BEGIN__ = .;
*(.data .data.* .gnu.linkonce.d.*)
SORT(CONSTRUCTORS)
}
.data1 : { *(.data1) }
/* We want the small data sections together, so single-instruction offsets
can access them all, and initialized data all before uninitialized, so
we can shorten the on-disk segment size. */
.sdata :
{
__SDATA_BEGIN__ = .;
*(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2) *(.srodata .srodata.*)
*(.sdata .sdata.* .gnu.linkonce.s.*)
}
_edata = .;
PROVIDE (edata = .);
. = ALIGN(ALIGNOF(NEXT_SECTION));
__bss_start = .;
.sbss :
{
*(.dynsbss)
*(.sbss .sbss.* .gnu.linkonce.sb.*)
*(.scommon)
}
.bss :
{
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
/* Align here to ensure that in the common case of there only being one
type of .bss section, the section occupies space up to _end.
Align after .bss to ensure correct alignment even if the
.bss section disappears because there are no input sections.
FIXME: Why do we need it? When there is no .bss section, we do not
pad the .data section. */
. = ALIGN(. != 0 ? 32 / 8 : 1);
}
. = ALIGN(32 / 8);
/* Start of the Large Data region. */
. = SEGMENT_START("ldata-segment", .);
. = ALIGN(32 / 8);
__BSS_END__ = .;
__global_pointer$ = MIN(__SDATA_BEGIN__ + 0x800,
MAX(__DATA_BEGIN__ + 0x800, __BSS_END__ - 0x800));
_end = .;
PROVIDE (end = .);
. = DATA_SEGMENT_END (.);
/* Start of the Tiny Data region. */
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 (INFO) : { *(.comment); LINKER_VERSION; }
.gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1. */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions. */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2. */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2. */
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions. */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
/* DWARF 3. */
.debug_pubtypes 0 : { *(.debug_pubtypes) }
.debug_ranges 0 : { *(.debug_ranges) }
/* DWARF 5. */
.debug_addr 0 : { *(.debug_addr) }
.debug_line_str 0 : { *(.debug_line_str) }
.debug_loclists 0 : { *(.debug_loclists) }
.debug_macro 0 : { *(.debug_macro) }
.debug_names 0 : { *(.debug_names) }
.debug_rnglists 0 : { *(.debug_rnglists) }
.debug_str_offsets 0 : { *(.debug_str_offsets) }
.debug_sup 0 : { *(.debug_sup) }
.gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) *(.gnu_object_only) }
}
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
# Generate a multilib configure line for riscv-gnu-toolchain with useful
# combinations of extensions supported by both Hazard3 and mainline GCC
# (currently GCC 14). Use as:
# ./configure ... --with-multilib-generator="$(path/to/multilib-gen-gen.py)"
base = "rv32i"
abi = "-ilp32--"
options = [
"m",
"a",
"c",
"zba",
"zbb",
"zbc",
"zbs",
"zbkb",
"zbkx",
"zca",
"zcb",
"zcmp",
"zmmul"
]
# Do not build for LHS except when *all of* RHS is also present. This cuts
# down on the number of configurations. A leading "!" means antidependency,
# i.e. an incompatibility.
depends_on = {
"m": ["!zmmul" ],
"zmmul": ["!m" ],
"zbb": ["m", "zba", "zbs" ],
"zba": ["m", "zbb", "zbs" ],
"zbs": ["m", "zba", "zbb" ],
"zbkb": ["zbb" ],
"zbc": ["zba", "zbb", "zbs", "zbkb"],
"zbkx": ["zba", "zbb", "zbs", "zbkb"],
"zifencei": ["zicsr" ],
"c": ["!zca" ],
"zca": ["!c" ],
"zcb": ["zca" ],
"zcmp": ["zca", "zcb", ],
}
l = []
for i in range(2 ** len(options)):
isa = base
violates_dependencies = False
for j in (j for j in range(len(options)) if i & (1 << j)):
opt = options[j]
if opt in depends_on:
for dep in depends_on[opt]:
inverted_dep = dep.startswith("!")
if inverted_dep: dep = dep[1:]
if inverted_dep == bool(i & (1 << options.index(dep))):
violates_dependencies = True
break
if violates_dependencies:
break
if len(opt) > 1:
isa += "_"
isa += opt
isa += "_zicsr_zifencei"
if not violates_dependencies:
l.append(isa + abi)
# Bonus RV32E configs:
l.append("rv32e_zicsr_zifencei-ilp32e--")
l.append("rv32ema_zicsr_zifencei-ilp32e--")
l.append("rv32emac_zicsr_zifencei-ilp32e--")
l.append("rv32ema_zicsr_zifencei_zba_zbb_zbc_zbkb_zbkx_zbs_zca_zcb_zcmp-ilp32e--")
print(";".join(l))
print(len(l))
+55
View File
@@ -0,0 +1,55 @@
ifndef SRCS
$(error Must define list of test sources as SRCS)
endif
ifndef APP
$(error Must define application name as APP)
endif
DOTF ?= tb.f
CCFLAGS ?=
LDSCRIPT ?= ../common/memmap.ld
CROSS_PREFIX ?= riscv32-unknown-elf-
TBEXEC ?= ../tb_cxxrtl/tb
TBDIR := $(dir $(abspath $(TBEXEC)))
INCDIR ?= ../common
MAX_CYCLES ?= 100000
TMP_PREFIX ?= tmp/
# Useless:
override CCFLAGS += -Wl,--no-warn-rwx-segments
###############################################################################
.SUFFIXES:
.PHONY: all run view tb clean clean_tb
all: run
run: $(TMP_PREFIX)$(APP).bin
$(TBEXEC) --bin $(TMP_PREFIX)$(APP).bin --vcd $(TMP_PREFIX)$(APP)_run.vcd --cycles $(MAX_CYCLES)
view: run
gtkwave $(TMP_PREFIX)$(APP)_run.vcd
bin: $(TMP_PREFIX)$(APP).bin
tb:
$(MAKE) -C $(TBDIR) DOTF=$(DOTF)
clean:
rm -rf $(TMP_PREFIX)
clean_tb: clean
$(MAKE) -C $(TBDIR) clean
###############################################################################
$(TMP_PREFIX)$(APP).bin: $(TMP_PREFIX)$(APP).elf
$(CROSS_PREFIX)objcopy -O binary $^ $@
$(CROSS_PREFIX)objdump -h $^ > $(TMP_PREFIX)$(APP).dis
$(CROSS_PREFIX)objdump -d $^ >> $(TMP_PREFIX)$(APP).dis
$(TMP_PREFIX)$(APP).elf: $(SRCS) $(wildcard %.h)
mkdir -p $(TMP_PREFIX)
$(CROSS_PREFIX)gcc $(CCFLAGS) $(SRCS) -T $(LDSCRIPT) $(addprefix -I,$(INCDIR)) -o $@
+117
View File
@@ -0,0 +1,117 @@
#ifndef _TB_CXXRTL_IO_H
#define _TB_CXXRTL_IO_H
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
// ----------------------------------------------------------------------------
// Testbench IO hardware layout
#define IO_BASE 0xc0000000
typedef struct {
volatile uint32_t print_char;
volatile uint32_t print_u32;
volatile uint32_t exit;
uint32_t _pad0;
volatile uint32_t set_softirq;
volatile uint32_t clr_softirq;
volatile uint32_t globmon_en;
volatile uint32_t poison_addr;
volatile uint32_t set_irq;
uint32_t _pad2[3];
volatile uint32_t clr_irq;
uint32_t _pad3[3];
} io_hw_t;
#define mm_io ((io_hw_t *const)IO_BASE)
typedef struct {
volatile uint32_t mtime;
volatile uint32_t mtimeh;
volatile uint32_t mtimecmp;
volatile uint32_t mtimecmph;
} timer_hw_t;
#define mm_timer ((timer_hw_t *const)(IO_BASE + 0x100))
// ----------------------------------------------------------------------------
// Testbench IO convenience functions
static inline void tb_putc(char c) {
mm_io->print_char = (uint32_t)c;
}
static inline void tb_puts(const char *s) {
while (*s)
tb_putc(*s++);
}
static inline void tb_put_u32(uint32_t x) {
mm_io->print_u32 = x;
}
static inline void tb_exit(uint32_t ret) {
mm_io->exit = ret;
}
#ifndef PRINTF_BUF_SIZE
#define PRINTF_BUF_SIZE 256
#endif
static inline void tb_printf(const char *fmt, ...) {
char buf[PRINTF_BUF_SIZE];
va_list args;
va_start(args, fmt);
vsnprintf(buf, PRINTF_BUF_SIZE, fmt, args);
tb_puts(buf);
va_end(args);
}
#define tb_assert(cond, ...) if (!(cond)) {tb_printf(__VA_ARGS__); tb_exit(-1);}
static inline void tb_set_softirq(int idx) {
mm_io->set_softirq = 1u << idx;
}
static inline void tb_clr_softirq(int idx) {
mm_io->clr_softirq = 1u << idx;
}
static inline bool tb_get_softirq(int idx) {
return (bool)(mm_io->set_softirq & (1u << idx));
}
static inline void tb_enable_global_monitor(bool en) {
mm_io->globmon_en = en;
}
// Set an address to generate faults on any access
static inline void tb_set_poison_addr(uint32_t addr) {
asm volatile ("fence" : : : "memory");
mm_io->poison_addr = addr;
asm volatile ("fence" : : : "memory");
}
static inline void tb_set_irq_masked(uint32_t mask) {
mm_io->set_irq = mask;
}
static inline void tb_clr_irq_masked(uint32_t mask) {
mm_io->clr_irq = mask;
}
static inline uint32_t tb_get_irq_mask() {
return mm_io->set_irq;
}
extern volatile uintptr_t core1_entry_vector;
static inline void tb_launch_core1(void (*entry)(void)) {
core1_entry_vector = (uintptr_t)entry;
tb_set_softirq(1);
}
#endif
+34
View File
@@ -0,0 +1,34 @@
#ifndef _TB_UART_HPP
#define _TB_UART_HPP
#include <cstdint>
extern "C" {
#include "tb_uart_io.h"
}
class TbUart {
public:
explicit constexpr TbUart(unsigned index) : index_(index) {}
void write(char c) const { tb_uart_write(index_, static_cast<std::uint8_t>(c)); }
void write(const char *str) const {
while (*str)
write(*str++);
}
bool connected() const { return tb_uart_connected(index_); }
bool canRead() const { return tb_uart_can_read(index_); }
// Returns [0..255] on success, or -1 if RX FIFO is empty.
int tryRead() const { return tb_uart_try_read(index_); }
void clearOverrun() const { tb_uart_clear_overrun(index_); }
private:
unsigned index_;
};
#endif
+61
View File
@@ -0,0 +1,61 @@
#ifndef _TB_UART_IO_H
#define _TB_UART_IO_H
#include <stdint.h>
#include <stdbool.h>
// Testbench UART-over-TCP MMIO (see tb_common/include/tb_constants.h).
//
// Each UART is exposed as a raw TCP byte stream (one client at a time).
// Software should poll STATUS.RX_AVAIL before reading DATA.
#ifndef TB_IO_BASE
#define TB_IO_BASE 0xC0000000u
#endif
#define TB_UART_BASE 0x200u
#define TB_UART_STRIDE 0x20u
#define TB_UART_DATA 0x00u
#define TB_UART_STATUS 0x04u
#define TB_UART_CTRL 0x08u
#define TB_UART_STATUS_RX_AVAIL (1u << 0)
#define TB_UART_STATUS_TX_READY (1u << 1)
#define TB_UART_STATUS_CONNECTED (1u << 2)
#define TB_UART_STATUS_OVERRUN (1u << 3)
#define TB_UART_CTRL_CLR_OVERRUN (1u << 0)
static inline volatile uint32_t *tb_uart_reg(unsigned uart_idx, unsigned reg_off) {
return (volatile uint32_t *)(TB_IO_BASE + TB_UART_BASE + (uart_idx * TB_UART_STRIDE) + reg_off);
}
static inline uint32_t tb_uart_status(unsigned uart_idx) {
return *tb_uart_reg(uart_idx, TB_UART_STATUS);
}
static inline bool tb_uart_connected(unsigned uart_idx) {
return (tb_uart_status(uart_idx) & TB_UART_STATUS_CONNECTED) != 0;
}
static inline bool tb_uart_can_read(unsigned uart_idx) {
return (tb_uart_status(uart_idx) & TB_UART_STATUS_RX_AVAIL) != 0;
}
static inline int tb_uart_try_read(unsigned uart_idx) {
if (!tb_uart_can_read(uart_idx))
return -1;
return (int)(*tb_uart_reg(uart_idx, TB_UART_DATA) & 0xffu);
}
static inline void tb_uart_write(unsigned uart_idx, uint8_t byte) {
*tb_uart_reg(uart_idx, TB_UART_DATA) = (uint32_t)byte;
}
static inline void tb_uart_clear_overrun(unsigned uart_idx) {
*tb_uart_reg(uart_idx, TB_UART_CTRL) = TB_UART_CTRL_CLR_OVERRUN;
}
#endif