feat: publish FreeRTOS C FC04 card
This commit is contained in:
+66
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user