feat: publish FreeRTOS C FC02 card
This commit is contained in:
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
# Link up to project root
|
||||
include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../project_paths.mk
|
||||
+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
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
# Link up to project root
|
||||
include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../project_paths.mk
|
||||
@@ -0,0 +1,50 @@
|
||||
// Default Hazard3 config for testbench: all ISA features
|
||||
|
||||
localparam RESET_VECTOR = 32'h80000040;
|
||||
localparam MTVEC_INIT = 32'h80000000;
|
||||
localparam EXTENSION_A = 1;
|
||||
localparam EXTENSION_C = 1;
|
||||
localparam EXTENSION_E = 0;
|
||||
localparam EXTENSION_M = 1;
|
||||
localparam EXTENSION_ZBA = 1;
|
||||
localparam EXTENSION_ZBB = 1;
|
||||
localparam EXTENSION_ZBC = 1;
|
||||
localparam EXTENSION_ZBKB = 1;
|
||||
localparam EXTENSION_ZBKX = 1;
|
||||
localparam EXTENSION_ZBS = 1;
|
||||
localparam EXTENSION_ZCB = 1;
|
||||
localparam EXTENSION_ZCLSD = 1;
|
||||
localparam EXTENSION_ZCMP = 1;
|
||||
localparam EXTENSION_ZIFENCEI = 1;
|
||||
localparam EXTENSION_ZILSD = 1;
|
||||
localparam EXTENSION_XH3BEXTM = 1;
|
||||
localparam EXTENSION_XH3IRQ = 1;
|
||||
localparam EXTENSION_XH3PMPM = 1;
|
||||
localparam EXTENSION_XH3POWER = 1;
|
||||
localparam CSR_M_MANDATORY = 1;
|
||||
localparam CSR_M_TRAP = 1;
|
||||
localparam CSR_COUNTER = 1;
|
||||
localparam U_MODE = 1;
|
||||
localparam PMP_REGIONS = 4;
|
||||
localparam PMP_GRAIN = 0;
|
||||
localparam PMP_MATCH_NAPOT = 1;
|
||||
localparam PMP_MATCH_TOR = 1;
|
||||
localparam PMP_HARDWIRED = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){1'b0}};
|
||||
localparam PMP_HARDWIRED_ADDR = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){32'h0}};
|
||||
localparam PMP_HARDWIRED_CFG = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){8'h00}};
|
||||
localparam DEBUG_SUPPORT = 1;
|
||||
localparam BREAKPOINT_TRIGGERS = 4;
|
||||
localparam NUM_IRQS = 32;
|
||||
localparam IRQ_PRIORITY_BITS = 4;
|
||||
localparam IRQ_INPUT_BYPASS = {NUM_IRQS{1'b0}};
|
||||
localparam MVENDORID_VAL = 32'hdeadbeef;
|
||||
localparam MCONFIGPTR_VAL = 32'h9abcdef0;
|
||||
localparam REDUCED_BYPASS = 0;
|
||||
localparam MULDIV_UNROLL = 2;
|
||||
localparam MUL_FAST = 1;
|
||||
localparam MUL_FASTER = 1;
|
||||
localparam MULH_FAST = 1;
|
||||
localparam FAST_BRANCHCMP = 1;
|
||||
localparam RESET_REGFILE = 1;
|
||||
localparam BRANCH_PREDICTOR = 1;
|
||||
localparam MTVEC_WMASK = 32'hfffffffd;
|
||||
@@ -0,0 +1,50 @@
|
||||
// All ISA features (but RVE)
|
||||
|
||||
localparam RESET_VECTOR = 32'h80000040;
|
||||
localparam MTVEC_INIT = 32'h80000000;
|
||||
localparam EXTENSION_A = 1;
|
||||
localparam EXTENSION_C = 1;
|
||||
localparam EXTENSION_E = 1;
|
||||
localparam EXTENSION_M = 1;
|
||||
localparam EXTENSION_ZBA = 1;
|
||||
localparam EXTENSION_ZBB = 1;
|
||||
localparam EXTENSION_ZBC = 1;
|
||||
localparam EXTENSION_ZBKB = 1;
|
||||
localparam EXTENSION_ZBKX = 1;
|
||||
localparam EXTENSION_ZBS = 1;
|
||||
localparam EXTENSION_ZCB = 1;
|
||||
localparam EXTENSION_ZCLSD = 1;
|
||||
localparam EXTENSION_ZCMP = 1;
|
||||
localparam EXTENSION_ZIFENCEI = 1;
|
||||
localparam EXTENSION_ZILSD = 1;
|
||||
localparam EXTENSION_XH3BEXTM = 1;
|
||||
localparam EXTENSION_XH3IRQ = 1;
|
||||
localparam EXTENSION_XH3PMPM = 1;
|
||||
localparam EXTENSION_XH3POWER = 1;
|
||||
localparam CSR_M_MANDATORY = 1;
|
||||
localparam CSR_M_TRAP = 1;
|
||||
localparam CSR_COUNTER = 1;
|
||||
localparam U_MODE = 1;
|
||||
localparam PMP_REGIONS = 4;
|
||||
localparam PMP_GRAIN = 0;
|
||||
localparam PMP_MATCH_NAPOT = 1;
|
||||
localparam PMP_MATCH_TOR = 1;
|
||||
localparam PMP_HARDWIRED = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){1'b0}};
|
||||
localparam PMP_HARDWIRED_ADDR = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){32'h0}};
|
||||
localparam PMP_HARDWIRED_CFG = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){8'h00}};
|
||||
localparam DEBUG_SUPPORT = 1;
|
||||
localparam BREAKPOINT_TRIGGERS = 4;
|
||||
localparam NUM_IRQS = 32;
|
||||
localparam IRQ_PRIORITY_BITS = 4;
|
||||
localparam IRQ_INPUT_BYPASS = {NUM_IRQS{1'b0}};
|
||||
localparam MVENDORID_VAL = 32'hdeadbeef;
|
||||
localparam MCONFIGPTR_VAL = 32'h9abcdef0;
|
||||
localparam REDUCED_BYPASS = 0;
|
||||
localparam MULDIV_UNROLL = 2;
|
||||
localparam MUL_FAST = 1;
|
||||
localparam MUL_FASTER = 1;
|
||||
localparam MULH_FAST = 1;
|
||||
localparam FAST_BRANCHCMP = 1;
|
||||
localparam RESET_REGFILE = 1;
|
||||
localparam BRANCH_PREDICTOR = 1;
|
||||
localparam MTVEC_WMASK = 32'hfffffffd;
|
||||
@@ -0,0 +1,50 @@
|
||||
// Default Hazard3 config for testbench: all ISA features
|
||||
|
||||
localparam RESET_VECTOR = 32'h80000040;
|
||||
localparam MTVEC_INIT = 32'h80000000;
|
||||
localparam EXTENSION_A = 1;
|
||||
localparam EXTENSION_C = 1;
|
||||
localparam EXTENSION_E = 0;
|
||||
localparam EXTENSION_M = 1;
|
||||
localparam EXTENSION_ZBA = 1;
|
||||
localparam EXTENSION_ZBB = 1;
|
||||
localparam EXTENSION_ZBC = 1;
|
||||
localparam EXTENSION_ZBKB = 1;
|
||||
localparam EXTENSION_ZBKX = 1;
|
||||
localparam EXTENSION_ZBS = 1;
|
||||
localparam EXTENSION_ZCB = 1;
|
||||
localparam EXTENSION_ZCLSD = 1;
|
||||
localparam EXTENSION_ZCMP = 1;
|
||||
localparam EXTENSION_ZIFENCEI = 1;
|
||||
localparam EXTENSION_ZILSD = 1;
|
||||
localparam EXTENSION_XH3BEXTM = 1;
|
||||
localparam EXTENSION_XH3IRQ = 1;
|
||||
localparam EXTENSION_XH3PMPM = 1;
|
||||
localparam EXTENSION_XH3POWER = 1;
|
||||
localparam CSR_M_MANDATORY = 1;
|
||||
localparam CSR_M_TRAP = 1;
|
||||
localparam CSR_COUNTER = 1;
|
||||
localparam U_MODE = 1;
|
||||
localparam PMP_REGIONS = 4;
|
||||
localparam PMP_GRAIN = 0;
|
||||
localparam PMP_MATCH_NAPOT = 1;
|
||||
localparam PMP_MATCH_TOR = 1;
|
||||
localparam PMP_HARDWIRED = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){1'b0}};
|
||||
localparam PMP_HARDWIRED_ADDR = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){32'h0}};
|
||||
localparam PMP_HARDWIRED_CFG = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){8'h00}};
|
||||
localparam DEBUG_SUPPORT = 1;
|
||||
localparam BREAKPOINT_TRIGGERS = 4;
|
||||
localparam NUM_IRQS = 32;
|
||||
localparam IRQ_PRIORITY_BITS = 4;
|
||||
localparam IRQ_INPUT_BYPASS = {NUM_IRQS{1'b0}};
|
||||
localparam MVENDORID_VAL = 32'hdeadbeef;
|
||||
localparam MCONFIGPTR_VAL = 32'h9abcdef0;
|
||||
localparam REDUCED_BYPASS = 0;
|
||||
localparam MULDIV_UNROLL = 1;
|
||||
localparam MUL_FAST = 1;
|
||||
localparam MUL_FASTER = 0;
|
||||
localparam MULH_FAST = 0;
|
||||
localparam FAST_BRANCHCMP = 1;
|
||||
localparam RESET_REGFILE = 1;
|
||||
localparam BRANCH_PREDICTOR = 1;
|
||||
localparam MTVEC_WMASK = 32'hfffffffd;
|
||||
@@ -0,0 +1,51 @@
|
||||
// True minimum configuration -- enough to run hello world but no support for
|
||||
// traps, debug, or any non-mandatory ISA extensions.
|
||||
|
||||
localparam RESET_VECTOR = 32'h80000040;
|
||||
localparam MTVEC_INIT = 32'h80000000;
|
||||
localparam EXTENSION_A = 0;
|
||||
localparam EXTENSION_C = 0;
|
||||
localparam EXTENSION_E = 0;
|
||||
localparam EXTENSION_M = 0;
|
||||
localparam EXTENSION_ZBA = 0;
|
||||
localparam EXTENSION_ZBB = 0;
|
||||
localparam EXTENSION_ZBC = 0;
|
||||
localparam EXTENSION_ZBKB = 0;
|
||||
localparam EXTENSION_ZBKX = 0;
|
||||
localparam EXTENSION_ZBS = 0;
|
||||
localparam EXTENSION_ZCB = 0;
|
||||
localparam EXTENSION_ZCLSD = 0;
|
||||
localparam EXTENSION_ZCMP = 0;
|
||||
localparam EXTENSION_ZIFENCEI = 0;
|
||||
localparam EXTENSION_ZILSD = 0;
|
||||
localparam EXTENSION_XH3BEXTM = 0;
|
||||
localparam EXTENSION_XH3IRQ = 0;
|
||||
localparam EXTENSION_XH3PMPM = 0;
|
||||
localparam EXTENSION_XH3POWER = 0;
|
||||
localparam CSR_M_MANDATORY = 0;
|
||||
localparam CSR_M_TRAP = 0;
|
||||
localparam CSR_COUNTER = 0;
|
||||
localparam U_MODE = 0;
|
||||
localparam PMP_REGIONS = 0;
|
||||
localparam PMP_GRAIN = 0;
|
||||
localparam PMP_MATCH_NAPOT = 1;
|
||||
localparam PMP_MATCH_TOR = 0;
|
||||
localparam PMP_HARDWIRED = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){1'b0}};
|
||||
localparam PMP_HARDWIRED_ADDR = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){32'h0}};
|
||||
localparam PMP_HARDWIRED_CFG = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){8'h00}};
|
||||
localparam DEBUG_SUPPORT = 0;
|
||||
localparam BREAKPOINT_TRIGGERS = 4;
|
||||
localparam NUM_IRQS = 32;
|
||||
localparam IRQ_PRIORITY_BITS = 0;
|
||||
localparam IRQ_INPUT_BYPASS = {NUM_IRQS{1'b0}};
|
||||
localparam MVENDORID_VAL = 32'hdeadbeef;
|
||||
localparam MCONFIGPTR_VAL = 32'h9abcdef0;
|
||||
localparam REDUCED_BYPASS = 1;
|
||||
localparam MULDIV_UNROLL = 1;
|
||||
localparam MUL_FAST = 0;
|
||||
localparam MUL_FASTER = 0;
|
||||
localparam MULH_FAST = 0;
|
||||
localparam FAST_BRANCHCMP = 0;
|
||||
localparam RESET_REGFILE = 1;
|
||||
localparam BRANCH_PREDICTOR = 0;
|
||||
localparam MTVEC_WMASK = 32'hfffffffd;
|
||||
@@ -0,0 +1,51 @@
|
||||
// Minimum performance and unprivileged ISA feature set, but enough privileged
|
||||
// ISA and debug support to run a more interesting test suite.
|
||||
|
||||
localparam RESET_VECTOR = 32'h80000040;
|
||||
localparam MTVEC_INIT = 32'h80000000;
|
||||
localparam EXTENSION_A = 0;
|
||||
localparam EXTENSION_C = 0;
|
||||
localparam EXTENSION_E = 0;
|
||||
localparam EXTENSION_M = 0;
|
||||
localparam EXTENSION_ZBA = 0;
|
||||
localparam EXTENSION_ZBB = 0;
|
||||
localparam EXTENSION_ZBC = 0;
|
||||
localparam EXTENSION_ZBKB = 0;
|
||||
localparam EXTENSION_ZBKX = 0;
|
||||
localparam EXTENSION_ZBS = 0;
|
||||
localparam EXTENSION_ZCB = 0;
|
||||
localparam EXTENSION_ZCLSD = 0;
|
||||
localparam EXTENSION_ZCMP = 0;
|
||||
localparam EXTENSION_ZIFENCEI = 0;
|
||||
localparam EXTENSION_ZILSD = 0;
|
||||
localparam EXTENSION_XH3BEXTM = 0;
|
||||
localparam EXTENSION_XH3IRQ = 0;
|
||||
localparam EXTENSION_XH3PMPM = 0;
|
||||
localparam EXTENSION_XH3POWER = 0;
|
||||
localparam CSR_M_MANDATORY = 1;
|
||||
localparam CSR_M_TRAP = 1;
|
||||
localparam CSR_COUNTER = 0;
|
||||
localparam U_MODE = 1;
|
||||
localparam PMP_REGIONS = 4;
|
||||
localparam PMP_GRAIN = 0;
|
||||
localparam PMP_MATCH_NAPOT = 1;
|
||||
localparam PMP_MATCH_TOR = 0;
|
||||
localparam PMP_HARDWIRED = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){1'b0}};
|
||||
localparam PMP_HARDWIRED_ADDR = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){32'h0}};
|
||||
localparam PMP_HARDWIRED_CFG = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){8'h00}};
|
||||
localparam DEBUG_SUPPORT = 1;
|
||||
localparam BREAKPOINT_TRIGGERS = 0;
|
||||
localparam NUM_IRQS = 32;
|
||||
localparam IRQ_PRIORITY_BITS = 0;
|
||||
localparam IRQ_INPUT_BYPASS = {NUM_IRQS{1'b0}};
|
||||
localparam MVENDORID_VAL = 32'hdeadbeef;
|
||||
localparam MCONFIGPTR_VAL = 32'h9abcdef0;
|
||||
localparam REDUCED_BYPASS = 1;
|
||||
localparam MULDIV_UNROLL = 1;
|
||||
localparam MUL_FAST = 0;
|
||||
localparam MUL_FASTER = 0;
|
||||
localparam MULH_FAST = 0;
|
||||
localparam FAST_BRANCHCMP = 0;
|
||||
localparam RESET_REGFILE = 1;
|
||||
localparam BRANCH_PREDICTOR = 0;
|
||||
localparam MTVEC_WMASK = 32'hfffffffd;
|
||||
@@ -0,0 +1,50 @@
|
||||
// Default Hazard3 config for testbench: all ISA features
|
||||
|
||||
localparam RESET_VECTOR = 32'h80000040;
|
||||
localparam MTVEC_INIT = 32'h80000000;
|
||||
localparam EXTENSION_A = 1;
|
||||
localparam EXTENSION_C = 1;
|
||||
localparam EXTENSION_E = 0;
|
||||
localparam EXTENSION_M = 1;
|
||||
localparam EXTENSION_ZBA = 1;
|
||||
localparam EXTENSION_ZBB = 1;
|
||||
localparam EXTENSION_ZBC = 1;
|
||||
localparam EXTENSION_ZBKB = 1;
|
||||
localparam EXTENSION_ZBKX = 1;
|
||||
localparam EXTENSION_ZBS = 1;
|
||||
localparam EXTENSION_ZCB = 1;
|
||||
localparam EXTENSION_ZCLSD = 1;
|
||||
localparam EXTENSION_ZCMP = 1;
|
||||
localparam EXTENSION_ZIFENCEI = 1;
|
||||
localparam EXTENSION_ZILSD = 1;
|
||||
localparam EXTENSION_XH3BEXTM = 1;
|
||||
localparam EXTENSION_XH3IRQ = 1;
|
||||
localparam EXTENSION_XH3PMPM = 1;
|
||||
localparam EXTENSION_XH3POWER = 1;
|
||||
localparam CSR_M_MANDATORY = 1;
|
||||
localparam CSR_M_TRAP = 1;
|
||||
localparam CSR_COUNTER = 1;
|
||||
localparam U_MODE = 1;
|
||||
localparam PMP_REGIONS = 16;
|
||||
localparam PMP_GRAIN = 0;
|
||||
localparam PMP_MATCH_NAPOT = 1;
|
||||
localparam PMP_MATCH_TOR = 1;
|
||||
localparam PMP_HARDWIRED = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){1'b0}};
|
||||
localparam PMP_HARDWIRED_ADDR = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){32'h0}};
|
||||
localparam PMP_HARDWIRED_CFG = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){8'h00}};
|
||||
localparam DEBUG_SUPPORT = 1;
|
||||
localparam BREAKPOINT_TRIGGERS = 4;
|
||||
localparam NUM_IRQS = 32;
|
||||
localparam IRQ_PRIORITY_BITS = 4;
|
||||
localparam IRQ_INPUT_BYPASS = {NUM_IRQS{1'b0}};
|
||||
localparam MVENDORID_VAL = 32'hdeadbeef;
|
||||
localparam MCONFIGPTR_VAL = 32'h9abcdef0;
|
||||
localparam REDUCED_BYPASS = 0;
|
||||
localparam MULDIV_UNROLL = 2;
|
||||
localparam MUL_FAST = 1;
|
||||
localparam MUL_FASTER = 1;
|
||||
localparam MULH_FAST = 1;
|
||||
localparam FAST_BRANCHCMP = 1;
|
||||
localparam RESET_REGFILE = 1;
|
||||
localparam BRANCH_PREDICTOR = 1;
|
||||
localparam MTVEC_WMASK = 32'hfffffffd;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
file tb.v
|
||||
list tb_common.f
|
||||
+341
@@ -0,0 +1,341 @@
|
||||
// An integration of JTAG-DTM + DM + CPU for openocd to poke at over a remote
|
||||
// bitbang socket
|
||||
|
||||
`default_nettype none
|
||||
|
||||
module tb #(
|
||||
parameter W_DATA = 32, // do not modify
|
||||
parameter W_ADDR = 32 // do not modify
|
||||
) (
|
||||
// Global signals
|
||||
input wire clk,
|
||||
input wire rst_n,
|
||||
|
||||
// JTAG port
|
||||
input wire tck,
|
||||
input wire trst_n,
|
||||
input wire tms,
|
||||
input wire tdi,
|
||||
output wire tdo,
|
||||
|
||||
// Instruction fetch port
|
||||
output wire [W_ADDR-1:0] i_haddr,
|
||||
output wire i_hwrite,
|
||||
output wire [1:0] i_htrans,
|
||||
output wire i_hexcl,
|
||||
output wire [2:0] i_hsize,
|
||||
output wire [2:0] i_hburst,
|
||||
output wire [3:0] i_hprot,
|
||||
output wire i_hmastlock,
|
||||
output wire [7:0] i_hmaster,
|
||||
input wire i_hready,
|
||||
input wire i_hresp,
|
||||
input wire i_hexokay,
|
||||
output wire [W_DATA-1:0] i_hwdata,
|
||||
input wire [W_DATA-1:0] i_hrdata,
|
||||
|
||||
// Load/store port
|
||||
output wire [W_ADDR-1:0] d_haddr,
|
||||
output wire d_hwrite,
|
||||
output wire [1:0] d_htrans,
|
||||
output wire d_hexcl,
|
||||
output wire [2:0] d_hsize,
|
||||
output wire [2:0] d_hburst,
|
||||
output wire [3:0] d_hprot,
|
||||
output wire d_hmastlock,
|
||||
output wire [7:0] d_hmaster,
|
||||
input wire d_hready,
|
||||
input wire d_hresp,
|
||||
input wire d_hexokay,
|
||||
output wire [W_DATA-1:0] d_hwdata,
|
||||
input wire [W_DATA-1:0] d_hrdata,
|
||||
|
||||
// Level-sensitive interrupt sources
|
||||
input wire [NUM_IRQS-1:0] irq, // -> mip.meip
|
||||
input wire [1:0] soft_irq, // -> mip.msip
|
||||
input wire [1:0] timer_irq // -> mip.mtip
|
||||
);
|
||||
|
||||
// JTAG-DTM IDCODE, selected after TAP reset, would normally be a
|
||||
// JEP106-compliant ID
|
||||
localparam IDCODE = 32'hdeadbeef;
|
||||
|
||||
wire dmi_psel;
|
||||
wire dmi_penable;
|
||||
wire dmi_pwrite;
|
||||
wire [8:0] dmi_paddr;
|
||||
wire [31:0] dmi_pwdata;
|
||||
reg [31:0] dmi_prdata;
|
||||
wire dmi_pready;
|
||||
wire dmi_pslverr;
|
||||
|
||||
wire dmihardreset_req;
|
||||
wire assert_dmi_reset = !rst_n || dmihardreset_req;
|
||||
wire rst_n_dmi;
|
||||
|
||||
hazard3_reset_sync dmi_reset_sync_u (
|
||||
.clk (clk),
|
||||
.rst_n_in (!assert_dmi_reset),
|
||||
.rst_n_out (rst_n_dmi)
|
||||
);
|
||||
|
||||
// Note the idle hint of 8 cycles was empirically found to be the correct
|
||||
// value for a 1:2 TCK:clk_dmi ratio. OpenOCD doesn't particularly care
|
||||
// because it will just increase idle cycles until it stops seeing BUSY.
|
||||
|
||||
hazard3_jtag_dtm #(
|
||||
.IDCODE (IDCODE),
|
||||
.DTMCS_IDLE_HINT (8)
|
||||
) inst_hazard3_jtag_dtm (
|
||||
.tck (tck),
|
||||
.trst_n (trst_n),
|
||||
.tms (tms),
|
||||
.tdi (tdi),
|
||||
.tdo (tdo),
|
||||
|
||||
.dmihardreset_req (dmihardreset_req),
|
||||
|
||||
.clk_dmi (clk),
|
||||
.rst_n_dmi (rst_n_dmi),
|
||||
|
||||
.dmi_psel (dmi_psel),
|
||||
.dmi_penable (dmi_penable),
|
||||
.dmi_pwrite (dmi_pwrite),
|
||||
.dmi_paddr (dmi_paddr),
|
||||
.dmi_pwdata (dmi_pwdata),
|
||||
.dmi_prdata (dmi_prdata),
|
||||
.dmi_pready (dmi_pready),
|
||||
.dmi_pslverr (dmi_pslverr)
|
||||
);
|
||||
|
||||
localparam N_HARTS = 1;
|
||||
localparam XLEN = 32;
|
||||
|
||||
wire sys_reset_req;
|
||||
wire sys_reset_done;
|
||||
wire [N_HARTS-1:0] hart_reset_req;
|
||||
wire [N_HARTS-1:0] hart_reset_done;
|
||||
|
||||
wire [N_HARTS-1:0] hart_req_halt;
|
||||
wire [N_HARTS-1:0] hart_req_halt_on_reset;
|
||||
wire [N_HARTS-1:0] hart_req_resume;
|
||||
wire [N_HARTS-1:0] hart_halted;
|
||||
wire [N_HARTS-1:0] hart_running;
|
||||
|
||||
wire [N_HARTS*XLEN-1:0] hart_data0_rdata;
|
||||
wire [N_HARTS*XLEN-1:0] hart_data0_wdata;
|
||||
wire [N_HARTS-1:0] hart_data0_wen;
|
||||
|
||||
wire [N_HARTS*XLEN-1:0] hart_instr_data;
|
||||
wire [N_HARTS-1:0] hart_instr_data_vld;
|
||||
wire [N_HARTS-1:0] hart_instr_data_rdy;
|
||||
wire [N_HARTS-1:0] hart_instr_caught_exception;
|
||||
wire [N_HARTS-1:0] hart_instr_caught_ebreak;
|
||||
|
||||
wire [31:0] sbus_addr;
|
||||
wire sbus_write;
|
||||
wire [1:0] sbus_size;
|
||||
wire sbus_vld;
|
||||
wire sbus_rdy;
|
||||
wire sbus_err;
|
||||
wire [31:0] sbus_wdata;
|
||||
wire [31:0] sbus_rdata;
|
||||
|
||||
hazard3_dm #(
|
||||
.N_HARTS (N_HARTS),
|
||||
.HAVE_SBA (1),
|
||||
.NEXT_DM_ADDR (0)
|
||||
) dm (
|
||||
.clk (clk),
|
||||
.rst_n (rst_n),
|
||||
|
||||
.dmi_psel (dmi_psel),
|
||||
.dmi_penable (dmi_penable),
|
||||
.dmi_pwrite (dmi_pwrite),
|
||||
.dmi_paddr (dmi_paddr),
|
||||
.dmi_pwdata (dmi_pwdata),
|
||||
.dmi_prdata (dmi_prdata),
|
||||
.dmi_pready (dmi_pready),
|
||||
.dmi_pslverr (dmi_pslverr),
|
||||
|
||||
.sys_reset_req (sys_reset_req),
|
||||
.sys_reset_done (sys_reset_done),
|
||||
.hart_reset_req (hart_reset_req),
|
||||
.hart_reset_done (hart_reset_done),
|
||||
|
||||
.hart_req_halt (hart_req_halt),
|
||||
.hart_req_halt_on_reset (hart_req_halt_on_reset),
|
||||
.hart_req_resume (hart_req_resume),
|
||||
.hart_halted (hart_halted),
|
||||
.hart_running (hart_running),
|
||||
|
||||
.hart_data0_rdata (hart_data0_rdata),
|
||||
.hart_data0_wdata (hart_data0_wdata),
|
||||
.hart_data0_wen (hart_data0_wen),
|
||||
|
||||
.hart_instr_data (hart_instr_data),
|
||||
.hart_instr_data_vld (hart_instr_data_vld),
|
||||
.hart_instr_data_rdy (hart_instr_data_rdy),
|
||||
.hart_instr_caught_exception (hart_instr_caught_exception),
|
||||
.hart_instr_caught_ebreak (hart_instr_caught_ebreak),
|
||||
|
||||
.sbus_addr (sbus_addr),
|
||||
.sbus_write (sbus_write),
|
||||
.sbus_size (sbus_size),
|
||||
.sbus_vld (sbus_vld),
|
||||
.sbus_rdy (sbus_rdy),
|
||||
.sbus_err (sbus_err),
|
||||
.sbus_wdata (sbus_wdata),
|
||||
.sbus_rdata (sbus_rdata)
|
||||
);
|
||||
|
||||
|
||||
// Generate resynchronised reset for CPU based on upstream reset and
|
||||
// on reset requests from DM.
|
||||
|
||||
wire assert_cpu_reset = !rst_n || sys_reset_req || hart_reset_req[0];
|
||||
wire rst_n_cpu;
|
||||
|
||||
hazard3_reset_sync cpu_reset_sync (
|
||||
.clk (clk),
|
||||
.rst_n_in (!assert_cpu_reset),
|
||||
.rst_n_out (rst_n_cpu)
|
||||
);
|
||||
|
||||
// Still some work to be done on the reset handshake -- this ought to be
|
||||
// resynchronised to DM's reset domain here, and the DM should wait for a
|
||||
// rising edge after it has asserted the reset pulse, to make sure the tail
|
||||
// of the previous "done" is not passed on.
|
||||
assign sys_reset_done = rst_n_cpu;
|
||||
assign hart_reset_done = rst_n_cpu;
|
||||
|
||||
|
||||
wire pwrup_req;
|
||||
reg pwrup_ack;
|
||||
wire clk_en;
|
||||
wire unblock_out;
|
||||
wire unblock_in = unblock_out;
|
||||
|
||||
always @ (posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
pwrup_ack <= 1'b1;
|
||||
end else begin
|
||||
pwrup_ack <= pwrup_req;
|
||||
end
|
||||
end
|
||||
|
||||
wire fence_i_vld;
|
||||
wire fence_d_vld;
|
||||
reg [3:0] fence_rdy_delay_ctr;
|
||||
wire fence_rdy = &fence_rdy_delay_ctr;
|
||||
always @ (posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
fence_rdy_delay_ctr <= 4'h0;
|
||||
end else if (fence_i_vld || fence_d_vld) begin
|
||||
fence_rdy_delay_ctr <= fence_rdy_delay_ctr + 4'h1;
|
||||
end else begin
|
||||
fence_rdy_delay_ctr <= 4'h0;
|
||||
end
|
||||
end
|
||||
|
||||
// Clock gate is disabled, as CXXRTL currently can't simulated gated clocks
|
||||
// due to a limitation of the scheduler design
|
||||
|
||||
// // Latching clock gate. Does not insert an NBA delay on the gated clock, so
|
||||
// // safe to exchange data between NBAs on the gated and non-gated clock. Does
|
||||
// // not glitch as long as clk_en is driven from an NBA on the posedge of clk
|
||||
// // (e.g. a normal RTL register). The clock stops *high*.
|
||||
|
||||
// reg clk_gated;
|
||||
|
||||
// always @ (*) begin
|
||||
// if (clk_en)
|
||||
// clk_gated = clk;
|
||||
// end
|
||||
|
||||
`ifndef CONFIG_HEADER
|
||||
`define CONFIG_HEADER "config_default.vh"
|
||||
`endif
|
||||
`include `CONFIG_HEADER
|
||||
|
||||
hazard3_cpu_2port #(
|
||||
`include "hazard3_config_inst.vh"
|
||||
) cpu (
|
||||
.clk (clk),
|
||||
.clk_always_on (clk),
|
||||
.rst_n (rst_n_cpu),
|
||||
|
||||
.pwrup_req (pwrup_req),
|
||||
.pwrup_ack (pwrup_ack),
|
||||
.clk_en (clk_en),
|
||||
.unblock_out (unblock_out),
|
||||
.unblock_in (unblock_in),
|
||||
|
||||
.i_haddr (i_haddr),
|
||||
.i_hwrite (i_hwrite),
|
||||
.i_htrans (i_htrans),
|
||||
.i_hsize (i_hsize),
|
||||
.i_hburst (i_hburst),
|
||||
.i_hprot (i_hprot),
|
||||
.i_hmastlock (i_hmastlock),
|
||||
.i_hmaster (i_hmaster),
|
||||
.i_hready (i_hready),
|
||||
.i_hresp (i_hresp),
|
||||
.i_hwdata (i_hwdata),
|
||||
.i_hrdata (i_hrdata),
|
||||
|
||||
.d_haddr (d_haddr),
|
||||
.d_hexcl (d_hexcl),
|
||||
.d_hwrite (d_hwrite),
|
||||
.d_htrans (d_htrans),
|
||||
.d_hsize (d_hsize),
|
||||
.d_hburst (d_hburst),
|
||||
.d_hprot (d_hprot),
|
||||
.d_hmastlock (d_hmastlock),
|
||||
.d_hmaster (d_hmaster),
|
||||
.d_hready (d_hready),
|
||||
.d_hresp (d_hresp),
|
||||
.d_hexokay (d_hexokay),
|
||||
.d_hwdata (d_hwdata),
|
||||
.d_hrdata (d_hrdata),
|
||||
|
||||
.fence_i_vld (fence_i_vld),
|
||||
.fence_d_vld (fence_d_vld),
|
||||
.fence_rdy (fence_rdy),
|
||||
|
||||
.dbg_req_halt (hart_req_halt),
|
||||
.dbg_req_halt_on_reset (hart_req_halt_on_reset),
|
||||
.dbg_req_resume (hart_req_resume),
|
||||
.dbg_halted (hart_halted),
|
||||
.dbg_running (hart_running),
|
||||
|
||||
.dbg_data0_rdata (hart_data0_rdata),
|
||||
.dbg_data0_wdata (hart_data0_wdata),
|
||||
.dbg_data0_wen (hart_data0_wen),
|
||||
|
||||
.dbg_instr_data (hart_instr_data),
|
||||
.dbg_instr_data_vld (hart_instr_data_vld),
|
||||
.dbg_instr_data_rdy (hart_instr_data_rdy),
|
||||
.dbg_instr_caught_exception (hart_instr_caught_exception),
|
||||
.dbg_instr_caught_ebreak (hart_instr_caught_ebreak),
|
||||
|
||||
.dbg_sbus_addr (sbus_addr),
|
||||
.dbg_sbus_write (sbus_write),
|
||||
.dbg_sbus_size (sbus_size),
|
||||
.dbg_sbus_vld (sbus_vld),
|
||||
.dbg_sbus_rdy (sbus_rdy),
|
||||
.dbg_sbus_err (sbus_err),
|
||||
.dbg_sbus_wdata (sbus_wdata),
|
||||
.dbg_sbus_rdata (sbus_rdata),
|
||||
|
||||
.mhartid_val (32'd0),
|
||||
.eco_version (4'ha),
|
||||
|
||||
.irq (irq),
|
||||
.soft_irq (soft_irq[0]),
|
||||
.timer_irq (timer_irq[0])
|
||||
);
|
||||
|
||||
assign i_hexcl = 1'b0;
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,7 @@
|
||||
file $HDL/debug/cdc/hazard3_reset_sync.v
|
||||
|
||||
list $HDL/hazard3.f
|
||||
list $HDL/debug/dm/hazard3_dm.f
|
||||
list $HDL/debug/dtm/hazard3_jtag_dtm.f
|
||||
|
||||
include .
|
||||
@@ -0,0 +1,2 @@
|
||||
file tb_multicore.v
|
||||
list tb_common.f
|
||||
@@ -0,0 +1,358 @@
|
||||
// An integration of JTAG-DTM + DM + 2 single-ported CPUs for openocd to poke
|
||||
// at over a remote bitbang socket
|
||||
|
||||
`default_nettype none
|
||||
|
||||
module tb #(
|
||||
parameter W_ADDR = 32, // do not modify
|
||||
parameter W_DATA = 32 // do not modify
|
||||
) (
|
||||
// Global signals
|
||||
input wire clk,
|
||||
input wire rst_n,
|
||||
|
||||
// JTAG port
|
||||
input wire tck,
|
||||
input wire trst_n,
|
||||
input wire tms,
|
||||
input wire tdi,
|
||||
output wire tdo,
|
||||
|
||||
// Core 0 bus (named I for consistency with 1-core 2-port tb)
|
||||
output wire [W_ADDR-1:0] i_haddr,
|
||||
output wire i_hwrite,
|
||||
output wire [1:0] i_htrans,
|
||||
output wire i_hexcl,
|
||||
output wire [2:0] i_hsize,
|
||||
output wire [2:0] i_hburst,
|
||||
output wire [3:0] i_hprot,
|
||||
output wire i_hmastlock,
|
||||
output wire [7:0] i_hmaster,
|
||||
input wire i_hready,
|
||||
input wire i_hresp,
|
||||
input wire i_hexokay,
|
||||
output wire [W_DATA-1:0] i_hwdata,
|
||||
input wire [W_DATA-1:0] i_hrdata,
|
||||
|
||||
// Core 1 bus (named D for consistency with 1-core 2-port tb)
|
||||
output wire [W_ADDR-1:0] d_haddr,
|
||||
output wire d_hwrite,
|
||||
output wire [1:0] d_htrans,
|
||||
output wire d_hexcl,
|
||||
output wire [2:0] d_hsize,
|
||||
output wire [2:0] d_hburst,
|
||||
output wire [3:0] d_hprot,
|
||||
output wire d_hmastlock,
|
||||
output wire [7:0] d_hmaster,
|
||||
input wire d_hready,
|
||||
input wire d_hresp,
|
||||
input wire d_hexokay,
|
||||
output wire [W_DATA-1:0] d_hwdata,
|
||||
input wire [W_DATA-1:0] d_hrdata,
|
||||
|
||||
// Level-sensitive interrupt sources
|
||||
input wire [NUM_IRQS-1:0] irq, // -> mip.meip
|
||||
input wire [1:0] soft_irq, // -> mip.msip
|
||||
input wire [1:0] timer_irq // -> mip.mtip
|
||||
);
|
||||
|
||||
// JTAG-DTM IDCODE, selected after TAP reset, would normally be a
|
||||
// JEP106-compliant ID
|
||||
localparam IDCODE = 32'hdeadbeef;
|
||||
|
||||
wire dmi_psel;
|
||||
wire dmi_penable;
|
||||
wire dmi_pwrite;
|
||||
wire [8:0] dmi_paddr;
|
||||
wire [31:0] dmi_pwdata;
|
||||
reg [31:0] dmi_prdata;
|
||||
wire dmi_pready;
|
||||
wire dmi_pslverr;
|
||||
|
||||
wire dmihardreset_req;
|
||||
wire assert_dmi_reset = !rst_n || dmihardreset_req;
|
||||
wire rst_n_dmi;
|
||||
|
||||
hazard3_reset_sync dmi_reset_sync_u (
|
||||
.clk (clk),
|
||||
.rst_n_in (!assert_dmi_reset),
|
||||
.rst_n_out (rst_n_dmi)
|
||||
);
|
||||
|
||||
hazard3_jtag_dtm #(
|
||||
.IDCODE (IDCODE),
|
||||
.DTMCS_IDLE_HINT (8)
|
||||
) inst_hazard3_jtag_dtm (
|
||||
.tck (tck),
|
||||
.trst_n (trst_n),
|
||||
.tms (tms),
|
||||
.tdi (tdi),
|
||||
.tdo (tdo),
|
||||
|
||||
.dmihardreset_req (dmihardreset_req),
|
||||
|
||||
.clk_dmi (clk),
|
||||
.rst_n_dmi (rst_n_dmi),
|
||||
|
||||
.dmi_psel (dmi_psel),
|
||||
.dmi_penable (dmi_penable),
|
||||
.dmi_pwrite (dmi_pwrite),
|
||||
.dmi_paddr (dmi_paddr),
|
||||
.dmi_pwdata (dmi_pwdata),
|
||||
.dmi_prdata (dmi_prdata),
|
||||
.dmi_pready (dmi_pready),
|
||||
.dmi_pslverr (dmi_pslverr)
|
||||
);
|
||||
|
||||
localparam N_HARTS = 2;
|
||||
localparam XLEN = 32;
|
||||
|
||||
wire sys_reset_req;
|
||||
wire sys_reset_done;
|
||||
wire [N_HARTS-1:0] hart_reset_req;
|
||||
wire [N_HARTS-1:0] hart_reset_done;
|
||||
|
||||
wire [N_HARTS-1:0] hart_req_halt;
|
||||
wire [N_HARTS-1:0] hart_req_halt_on_reset;
|
||||
wire [N_HARTS-1:0] hart_req_resume;
|
||||
wire [N_HARTS-1:0] hart_halted;
|
||||
wire [N_HARTS-1:0] hart_running;
|
||||
|
||||
wire [N_HARTS*XLEN-1:0] hart_data0_rdata;
|
||||
wire [N_HARTS*XLEN-1:0] hart_data0_wdata;
|
||||
wire [N_HARTS-1:0] hart_data0_wen;
|
||||
|
||||
wire [N_HARTS*XLEN-1:0] hart_instr_data;
|
||||
wire [N_HARTS-1:0] hart_instr_data_vld;
|
||||
wire [N_HARTS-1:0] hart_instr_data_rdy;
|
||||
wire [N_HARTS-1:0] hart_instr_caught_exception;
|
||||
wire [N_HARTS-1:0] hart_instr_caught_ebreak;
|
||||
|
||||
wire [31:0] sbus_addr;
|
||||
wire sbus_write;
|
||||
wire [1:0] sbus_size;
|
||||
wire sbus_vld;
|
||||
wire sbus_rdy;
|
||||
wire sbus_err;
|
||||
wire [31:0] sbus_wdata;
|
||||
wire [31:0] sbus_rdata;
|
||||
|
||||
hazard3_dm #(
|
||||
.N_HARTS (N_HARTS),
|
||||
.HAVE_SBA (1),
|
||||
.NEXT_DM_ADDR (0)
|
||||
) dm (
|
||||
.clk (clk),
|
||||
.rst_n (rst_n),
|
||||
|
||||
.dmi_psel (dmi_psel),
|
||||
.dmi_penable (dmi_penable),
|
||||
.dmi_pwrite (dmi_pwrite),
|
||||
.dmi_paddr (dmi_paddr),
|
||||
.dmi_pwdata (dmi_pwdata),
|
||||
.dmi_prdata (dmi_prdata),
|
||||
.dmi_pready (dmi_pready),
|
||||
.dmi_pslverr (dmi_pslverr),
|
||||
|
||||
.sys_reset_req (sys_reset_req),
|
||||
.sys_reset_done (sys_reset_done),
|
||||
.hart_reset_req (hart_reset_req),
|
||||
.hart_reset_done (hart_reset_done),
|
||||
|
||||
.hart_req_halt (hart_req_halt),
|
||||
.hart_req_halt_on_reset (hart_req_halt_on_reset),
|
||||
.hart_req_resume (hart_req_resume),
|
||||
.hart_halted (hart_halted),
|
||||
.hart_running (hart_running),
|
||||
|
||||
.hart_data0_rdata (hart_data0_rdata),
|
||||
.hart_data0_wdata (hart_data0_wdata),
|
||||
.hart_data0_wen (hart_data0_wen),
|
||||
|
||||
.hart_instr_data (hart_instr_data),
|
||||
.hart_instr_data_vld (hart_instr_data_vld),
|
||||
.hart_instr_data_rdy (hart_instr_data_rdy),
|
||||
.hart_instr_caught_exception (hart_instr_caught_exception),
|
||||
.hart_instr_caught_ebreak (hart_instr_caught_ebreak),
|
||||
|
||||
.sbus_addr (sbus_addr),
|
||||
.sbus_write (sbus_write),
|
||||
.sbus_size (sbus_size),
|
||||
.sbus_vld (sbus_vld),
|
||||
.sbus_rdy (sbus_rdy),
|
||||
.sbus_err (sbus_err),
|
||||
.sbus_wdata (sbus_wdata),
|
||||
.sbus_rdata (sbus_rdata)
|
||||
|
||||
);
|
||||
|
||||
// Generate resynchronised reset for CPU based on upstream reset and
|
||||
// on reset requests from DM.
|
||||
|
||||
wire assert_cpu_reset0 = !rst_n || sys_reset_req || hart_reset_req[0];
|
||||
wire assert_cpu_reset1 = !rst_n || sys_reset_req || hart_reset_req[1];
|
||||
wire rst_n_cpu0;
|
||||
wire rst_n_cpu1;
|
||||
|
||||
hazard3_reset_sync cpu0_reset_sync (
|
||||
.clk (clk),
|
||||
.rst_n_in (!assert_cpu_reset0),
|
||||
.rst_n_out (rst_n_cpu0)
|
||||
);
|
||||
|
||||
hazard3_reset_sync cpu1_reset_sync (
|
||||
.clk (clk),
|
||||
.rst_n_in (!assert_cpu_reset1),
|
||||
.rst_n_out (rst_n_cpu1)
|
||||
);
|
||||
|
||||
// Still some work to be done on the reset handshake -- this ought to be
|
||||
// resynchronised to DM's reset domain here, and the DM should wait for a
|
||||
// rising edge after it has asserted the reset pulse, to make sure the tail
|
||||
// of the previous "done" is not passed on.
|
||||
assign sys_reset_done = rst_n_cpu0 && rst_n_cpu1;
|
||||
assign hart_reset_done = {rst_n_cpu1, rst_n_cpu0};
|
||||
|
||||
`ifndef CONFIG_HEADER
|
||||
`define CONFIG_HEADER "config_default.vh"
|
||||
`endif
|
||||
`include `CONFIG_HEADER
|
||||
|
||||
wire pwrup_req_cpu0;
|
||||
wire pwrup_req_cpu1;
|
||||
wire unblock_out_cpu0;
|
||||
wire unblock_out_cpu1;
|
||||
|
||||
hazard3_cpu_1port #(
|
||||
`include "hazard3_config_inst.vh"
|
||||
) cpu0 (
|
||||
.clk (clk),
|
||||
.clk_always_on (clk),
|
||||
.rst_n (rst_n_cpu0),
|
||||
|
||||
.pwrup_req (pwrup_req_cpu0),
|
||||
.pwrup_ack (pwrup_req_cpu0),
|
||||
.clk_en (),
|
||||
.unblock_out (unblock_out_cpu0),
|
||||
.unblock_in (unblock_out_cpu1),
|
||||
|
||||
.haddr (i_haddr),
|
||||
.hexcl (i_hexcl),
|
||||
.hwrite (i_hwrite),
|
||||
.htrans (i_htrans),
|
||||
.hsize (i_hsize),
|
||||
.hburst (i_hburst),
|
||||
.hprot (i_hprot),
|
||||
.hmastlock (i_hmastlock),
|
||||
.hmaster (i_hmaster),
|
||||
.hready (i_hready),
|
||||
.hresp (i_hresp),
|
||||
.hexokay (i_hexokay),
|
||||
.hwdata (i_hwdata),
|
||||
.hrdata (i_hrdata),
|
||||
|
||||
.fence_i_vld (),
|
||||
.fence_d_vld (),
|
||||
.fence_rdy (1'b1),
|
||||
|
||||
.dbg_req_halt (hart_req_halt [0]),
|
||||
.dbg_req_halt_on_reset (hart_req_halt_on_reset [0]),
|
||||
.dbg_req_resume (hart_req_resume [0]),
|
||||
.dbg_halted (hart_halted [0]),
|
||||
.dbg_running (hart_running [0]),
|
||||
|
||||
.dbg_data0_rdata (hart_data0_rdata [0 * XLEN +: XLEN]),
|
||||
.dbg_data0_wdata (hart_data0_wdata [0 * XLEN +: XLEN]),
|
||||
.dbg_data0_wen (hart_data0_wen [0]),
|
||||
|
||||
.dbg_instr_data (hart_instr_data [0 * XLEN +: XLEN]),
|
||||
.dbg_instr_data_vld (hart_instr_data_vld [0]),
|
||||
.dbg_instr_data_rdy (hart_instr_data_rdy [0]),
|
||||
.dbg_instr_caught_exception (hart_instr_caught_exception[0]),
|
||||
.dbg_instr_caught_ebreak (hart_instr_caught_ebreak [0]),
|
||||
|
||||
// SBA is routed through core 1, so tie off on core 0
|
||||
.dbg_sbus_addr (32'h0),
|
||||
.dbg_sbus_write (1'b0),
|
||||
.dbg_sbus_size (2'h0),
|
||||
.dbg_sbus_vld (1'b0),
|
||||
.dbg_sbus_rdy (),
|
||||
.dbg_sbus_err (),
|
||||
.dbg_sbus_wdata (32'h0),
|
||||
.dbg_sbus_rdata (),
|
||||
|
||||
.mhartid_val (32'd0),
|
||||
.eco_version (4'ha),
|
||||
|
||||
.irq (irq),
|
||||
.soft_irq (soft_irq[0]),
|
||||
.timer_irq (timer_irq[0])
|
||||
);
|
||||
|
||||
hazard3_cpu_1port #(
|
||||
`include "hazard3_config_inst.vh"
|
||||
) cpu1 (
|
||||
.clk (clk),
|
||||
.clk_always_on (clk),
|
||||
.rst_n (rst_n_cpu1),
|
||||
|
||||
.pwrup_req (pwrup_req_cpu1),
|
||||
.pwrup_ack (pwrup_req_cpu1),
|
||||
.clk_en (),
|
||||
.unblock_out (unblock_out_cpu1),
|
||||
.unblock_in (unblock_out_cpu0),
|
||||
|
||||
.haddr (d_haddr),
|
||||
.hexcl (d_hexcl),
|
||||
.hwrite (d_hwrite),
|
||||
.htrans (d_htrans),
|
||||
.hsize (d_hsize),
|
||||
.hburst (d_hburst),
|
||||
.hprot (d_hprot),
|
||||
.hmastlock (d_hmastlock),
|
||||
.hmaster (d_hmaster),
|
||||
.hready (d_hready),
|
||||
.hresp (d_hresp),
|
||||
.hexokay (d_hexokay),
|
||||
.hwdata (d_hwdata),
|
||||
.hrdata (d_hrdata),
|
||||
|
||||
.fence_i_vld (),
|
||||
.fence_d_vld (),
|
||||
.fence_rdy (1'b1),
|
||||
|
||||
.dbg_req_halt (hart_req_halt [1]),
|
||||
.dbg_req_halt_on_reset (hart_req_halt_on_reset [1]),
|
||||
.dbg_req_resume (hart_req_resume [1]),
|
||||
.dbg_halted (hart_halted [1]),
|
||||
.dbg_running (hart_running [1]),
|
||||
|
||||
.dbg_data0_rdata (hart_data0_rdata [1 * XLEN +: XLEN]),
|
||||
.dbg_data0_wdata (hart_data0_wdata [1 * XLEN +: XLEN]),
|
||||
.dbg_data0_wen (hart_data0_wen [1]),
|
||||
|
||||
.dbg_instr_data (hart_instr_data [1 * XLEN +: XLEN]),
|
||||
.dbg_instr_data_vld (hart_instr_data_vld [1]),
|
||||
.dbg_instr_data_rdy (hart_instr_data_rdy [1]),
|
||||
.dbg_instr_caught_exception (hart_instr_caught_exception[1]),
|
||||
.dbg_instr_caught_ebreak (hart_instr_caught_ebreak [1]),
|
||||
|
||||
.dbg_sbus_addr (sbus_addr),
|
||||
.dbg_sbus_write (sbus_write),
|
||||
.dbg_sbus_size (sbus_size),
|
||||
.dbg_sbus_vld (sbus_vld),
|
||||
.dbg_sbus_rdy (sbus_rdy),
|
||||
.dbg_sbus_err (sbus_err),
|
||||
.dbg_sbus_wdata (sbus_wdata),
|
||||
.dbg_sbus_rdata (sbus_rdata),
|
||||
|
||||
.mhartid_val (32'd1),
|
||||
.eco_version (4'ha),
|
||||
|
||||
.irq (irq),
|
||||
.soft_irq (soft_irq[1]),
|
||||
.timer_irq (timer_irq[1])
|
||||
);
|
||||
|
||||
|
||||
endmodule
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include "tb_cli.h"
|
||||
#include "tb_constants.h"
|
||||
#include "tb_uart.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
struct mem_io_state;
|
||||
class tb_top;
|
||||
|
||||
struct bus_request {
|
||||
uint32_t addr;
|
||||
bus_size_t size;
|
||||
bool write;
|
||||
bool excl;
|
||||
uint32_t wdata;
|
||||
int reservation_id;
|
||||
bus_request(): addr(0), size(SIZE_BYTE), write(0), excl(0), wdata(0), reservation_id(0) {}
|
||||
};
|
||||
|
||||
struct bus_response {
|
||||
uint32_t rdata;
|
||||
int stall_cycles;
|
||||
bool err;
|
||||
bool exokay;
|
||||
bus_response(): rdata(0), stall_cycles(0), err(false), exokay(true) {}
|
||||
};
|
||||
|
||||
typedef bus_response (*mem_access_callback_t)(tb_top &tb, mem_io_state &memio, bus_request req);
|
||||
|
||||
// Default callback:
|
||||
bus_response tb_mem_access(tb_top &tb, mem_io_state &memio, bus_request req);
|
||||
|
||||
// Abstract test harness class. Concrete implementations of this class contain
|
||||
// the actual C++ cycle model as well as the glue for this interface.
|
||||
class tb_top {
|
||||
protected:
|
||||
mem_access_callback_t mem_callback_i;
|
||||
mem_access_callback_t mem_callback_d;
|
||||
uint64_t rand_state[4];
|
||||
public:
|
||||
FILE *logfile;
|
||||
void set_mem_callback_i(mem_access_callback_t cb) {mem_callback_i = cb;}
|
||||
void set_mem_callback_d(mem_access_callback_t cb) {mem_callback_d = cb;}
|
||||
|
||||
tb_top(const tb_cli_args &args) {
|
||||
mem_callback_i = tb_mem_access;
|
||||
mem_callback_d = tb_mem_access;
|
||||
seed_rand((const uint8_t*)"looks random to me", 18);
|
||||
if (args.log_path != "") {
|
||||
logfile = fopen(args.log_path.c_str(), "wb");
|
||||
} else {
|
||||
logfile = stdout;
|
||||
}
|
||||
}
|
||||
|
||||
void seed_rand(const uint8_t *data, size_t len);
|
||||
uint32_t rand();
|
||||
|
||||
virtual void step(const tb_cli_args &args, mem_io_state &memio) = 0;
|
||||
// Evaluate DUT at current signal values without advancing the core clock.
|
||||
virtual void eval() = 0;
|
||||
|
||||
virtual void set_trst_n(bool trst_n) = 0;
|
||||
virtual void set_tck(bool tck) = 0;
|
||||
virtual void set_tdi(bool tdi) = 0;
|
||||
virtual void set_tms(bool tms) = 0;
|
||||
virtual bool get_tdo() = 0;
|
||||
|
||||
virtual void set_irq(uint32_t mask) = 0;
|
||||
virtual void set_soft_irq(uint8_t mask) = 0;
|
||||
virtual void set_timer_irq(uint8_t mask) = 0;
|
||||
};
|
||||
|
||||
struct mem_io_state {
|
||||
uint64_t mtime;
|
||||
uint64_t mtimecmp[2];
|
||||
|
||||
bool exit_req;
|
||||
uint32_t exit_code;
|
||||
|
||||
uint8_t *mem;
|
||||
|
||||
bool monitor_enabled;
|
||||
bool reservation_valid[2];
|
||||
uint32_t reservation_addr[2];
|
||||
uint32_t poison_addr;
|
||||
|
||||
uint8_t soft_irq_state;
|
||||
uint32_t irq_state;
|
||||
|
||||
tb_uart_state uart;
|
||||
|
||||
mem_io_state(const tb_cli_args &args);
|
||||
|
||||
~mem_io_state() {
|
||||
delete[] mem;
|
||||
}
|
||||
|
||||
void step(tb_top &tb);
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct tb_cli_args {
|
||||
bool load_bin;
|
||||
std::string bin_path;
|
||||
bool dump_waves;
|
||||
std::string waves_path;
|
||||
std::vector<std::pair<uint32_t, uint32_t>> dump_ranges;
|
||||
int64_t max_cycles;
|
||||
bool propagate_return_code;
|
||||
uint16_t port;
|
||||
uint16_t vpi_port;
|
||||
uint16_t gdb_port;
|
||||
uint16_t uart0_port;
|
||||
uint16_t uart1_port;
|
||||
// JTAG_VPI socket polling backoff in core cycles (0 = poll every cycle).
|
||||
// When idle, the testbench ramps up to this maximum backoff to reduce
|
||||
// syscall overhead without adding huge latency between back-to-back packets.
|
||||
uint32_t vpi_poll_cycles;
|
||||
// When using --vpi-port, run N core clock cycles per JTAG TCK cycle during
|
||||
// OpenOCD TMS sequences (helps DMI/APB CDC make progress and avoids BUSY).
|
||||
// 0 disables this coupling (legacy behaviour).
|
||||
uint32_t vpi_clk_per_tck;
|
||||
bool dump_jtag;
|
||||
std::string jtag_dump_path;
|
||||
bool replay_jtag;
|
||||
std::string jtag_replay_path;
|
||||
std::string log_path;
|
||||
std::string sig_path;
|
||||
#ifdef CXXRTL_DEBUG_AGENT
|
||||
bool run_agent;
|
||||
#endif
|
||||
tb_cli_args() {
|
||||
load_bin = false;
|
||||
dump_waves = false;
|
||||
max_cycles = 0;
|
||||
propagate_return_code = false;
|
||||
port = 0;
|
||||
vpi_port = 0;
|
||||
gdb_port = 0;
|
||||
uart0_port = 0;
|
||||
uart1_port = 0;
|
||||
vpi_poll_cycles = 256;
|
||||
vpi_clk_per_tck = 2;
|
||||
dump_jtag = false;
|
||||
replay_jtag = false;
|
||||
#ifdef CXXRTL_DEBUG_AGENT
|
||||
run_agent = false;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
void tb_parse_args(int argc, char **argv, tb_cli_args &args);
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __x86_64__
|
||||
#define I64_FMT "%ld"
|
||||
#else
|
||||
#define I64_FMT "%lld"
|
||||
#endif
|
||||
|
||||
#define MEM_BASE 0x80000000
|
||||
#define MEM_SIZE (16 * 1024 * 1024)
|
||||
#define N_RESERVATIONS (2)
|
||||
#define RESERVATION_ADDR_MASK (0xfffffff8u)
|
||||
|
||||
static const unsigned int IO_BASE = 0xc0000000;
|
||||
enum {
|
||||
IO_PRINT_CHAR = 0x000,
|
||||
IO_PRINT_U32 = 0x004,
|
||||
IO_EXIT = 0x008,
|
||||
IO_SET_SOFTIRQ = 0x010,
|
||||
IO_CLR_SOFTIRQ = 0x014,
|
||||
IO_GLOBMON_EN = 0x018,
|
||||
IO_POISON_ADDR = 0x01c,
|
||||
IO_SET_IRQ = 0x020,
|
||||
IO_CLR_IRQ = 0x030,
|
||||
IO_MTIME = 0x100,
|
||||
IO_MTIMEH = 0x104,
|
||||
IO_MTIMECMP0 = 0x108,
|
||||
IO_MTIMECMP0H = 0x10c,
|
||||
IO_MTIMECMP1 = 0x110,
|
||||
IO_MTIMECMP1H = 0x114
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Testbench UART-over-TCP MMIO
|
||||
//
|
||||
// Each UART is exposed as a raw TCP byte stream (one client at a time).
|
||||
// Software should poll STATUS.RX_AVAIL before reading DATA.
|
||||
|
||||
static constexpr uint32_t IO_UART_BASE = 0x200;
|
||||
static constexpr uint32_t IO_UART_STRIDE = 0x20;
|
||||
static constexpr uint32_t IO_UART_DATA = 0x00;
|
||||
static constexpr uint32_t IO_UART_STATUS = 0x04;
|
||||
static constexpr uint32_t IO_UART_CTRL = 0x08;
|
||||
static constexpr uint32_t IO_UART_N = 2;
|
||||
|
||||
static constexpr uint32_t TB_UART_STATUS_RX_AVAIL = 1u << 0;
|
||||
static constexpr uint32_t TB_UART_STATUS_TX_READY = 1u << 1;
|
||||
static constexpr uint32_t TB_UART_STATUS_CONNECTED = 1u << 2;
|
||||
static constexpr uint32_t TB_UART_STATUS_OVERRUN = 1u << 3;
|
||||
|
||||
static constexpr uint32_t TB_UART_CTRL_CLR_OVERRUN = 1u << 0;
|
||||
|
||||
typedef enum {
|
||||
SIZE_BYTE = 0,
|
||||
SIZE_HWORD = 1,
|
||||
SIZE_WORD = 2
|
||||
} bus_size_t;
|
||||
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
enum class tb_gdb_run_result {
|
||||
ok = 0,
|
||||
error,
|
||||
exited,
|
||||
timed_out
|
||||
};
|
||||
|
||||
struct tb_gdb_target {
|
||||
virtual ~tb_gdb_target() = default;
|
||||
|
||||
// Register numbering follows the provided target.xml:
|
||||
// x0..x31 = 0..31, pc = 32.
|
||||
virtual uint32_t read_reg(uint32_t regno) = 0;
|
||||
virtual void write_reg(uint32_t regno, uint32_t value) = 0;
|
||||
|
||||
virtual bool read_mem(uint32_t addr, uint8_t *dst, size_t len) = 0;
|
||||
virtual bool write_mem(uint32_t addr, const uint8_t *src, size_t len) = 0;
|
||||
|
||||
// Advance execution until one instruction retires.
|
||||
virtual tb_gdb_run_result step_instruction() = 0;
|
||||
|
||||
virtual uint32_t exit_code() const = 0;
|
||||
|
||||
// Optional: handle GDB "monitor" commands (qRcmd). Return true if handled.
|
||||
// If handled, out_console is printed on the GDB console (can be empty).
|
||||
virtual bool monitor_cmd(const std::string &cmd, std::string &out_console) {
|
||||
(void)cmd;
|
||||
out_console.clear();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class tb_gdb_server {
|
||||
public:
|
||||
tb_gdb_server(uint16_t port, tb_gdb_target &target);
|
||||
~tb_gdb_server();
|
||||
|
||||
// Blocks until the session ends (disconnect/kill) or the target exits.
|
||||
tb_gdb_run_result serve();
|
||||
|
||||
private:
|
||||
uint16_t port_;
|
||||
tb_gdb_target &target_;
|
||||
|
||||
int server_fd_ = -1;
|
||||
int client_fd_ = -1;
|
||||
bool no_ack_mode_ = false;
|
||||
|
||||
std::string target_xml_;
|
||||
std::string memory_map_xml_;
|
||||
|
||||
// Execute breakpoints (RSP Z0/Z1): stop before executing instruction at PC.
|
||||
// When resuming from a breakpoint, ignore a match at the current PC once.
|
||||
std::unordered_set<uint32_t> breakpoints_;
|
||||
uint32_t ignore_breakpoint_pc_ = 0xffffffffu;
|
||||
|
||||
// Socket helpers
|
||||
bool open_listen_socket_();
|
||||
bool accept_client_();
|
||||
void close_client_();
|
||||
|
||||
bool recv_packet_(std::string &out_payload, bool &got_interrupt);
|
||||
bool send_packet_(const std::string &payload);
|
||||
bool maybe_send_ack_(bool ok);
|
||||
bool check_interrupt_();
|
||||
|
||||
// RSP helpers
|
||||
static uint8_t checksum_(const std::string &s);
|
||||
static int hex_val_(char c);
|
||||
static std::string to_hex_bytes_(const uint8_t *data, size_t len);
|
||||
static bool from_hex_bytes_(const std::string &hex, std::string &out_bytes);
|
||||
static void append_u32_le_hex_(std::string &out, uint32_t v);
|
||||
static bool parse_u32_hex_(const std::string &s, uint32_t &out);
|
||||
|
||||
std::string handle_command_(const std::string &cmd, bool &run_command_consumed);
|
||||
std::string stop_reply_(int signo) const;
|
||||
std::string stop_reply_exit_() const;
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
#include <fstream>
|
||||
#include <cstdint>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include "tb.h"
|
||||
#include "tb_cli.h"
|
||||
|
||||
#define TCP_BUF_SIZE 256
|
||||
|
||||
struct tb_jtag_state {
|
||||
enum class transport_t {
|
||||
none,
|
||||
remote_bitbang,
|
||||
jtag_vpi
|
||||
};
|
||||
|
||||
tb_cli_args args;
|
||||
transport_t transport;
|
||||
|
||||
int server_fd;
|
||||
int sock_fd;
|
||||
struct sockaddr_in sock_addr;
|
||||
int sock_opt;
|
||||
socklen_t sock_addr_len;
|
||||
char txbuf[TCP_BUF_SIZE], rxbuf[TCP_BUF_SIZE];
|
||||
int rx_ptr;
|
||||
int rx_remaining;
|
||||
int tx_ptr;
|
||||
|
||||
static constexpr int VPI_XFERT_MAX_SIZE = 512;
|
||||
static constexpr int VPI_PKT_SIZE = 4 + VPI_XFERT_MAX_SIZE + VPI_XFERT_MAX_SIZE + 4 + 4;
|
||||
uint8_t vpi_rxbuf[VPI_PKT_SIZE];
|
||||
int vpi_rx_count;
|
||||
uint32_t vpi_poll_ctr;
|
||||
uint32_t vpi_poll_backoff;
|
||||
|
||||
std::ofstream jtag_dump_fd;
|
||||
std::ifstream jtag_replay_fd;
|
||||
|
||||
tb_jtag_state(const tb_cli_args &_args);
|
||||
|
||||
// Returns true if an exit command was received from the JTAG socket
|
||||
// If memio/cycle_count/timed_out are provided, the testbench may advance
|
||||
// the core clock while processing JTAG_VPI TMS sequences (idle/wait cycles),
|
||||
// so the DMI/APB CDC can make progress and OpenOCD doesn't spin on BUSY.
|
||||
bool step(
|
||||
tb_top &tb,
|
||||
mem_io_state *memio = nullptr,
|
||||
int64_t *cycle_count = nullptr,
|
||||
bool *timed_out = nullptr,
|
||||
uint32_t *core_cycles_advanced = nullptr
|
||||
);
|
||||
|
||||
void close();
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
struct tb_cli_args;
|
||||
|
||||
// Simple UART-over-TCP bridge used by the Verilator/CXXRTL testbenches.
|
||||
//
|
||||
// Each UART is exposed as a raw TCP byte stream (one client at a time).
|
||||
// - CPU TX: MMIO write -> queued -> non-blocking send() to connected client.
|
||||
// - CPU RX: client -> queued -> MMIO read pops one byte (poll STATUS first).
|
||||
struct tb_uart_state {
|
||||
static constexpr uint32_t N_UARTS = 2;
|
||||
|
||||
struct uart {
|
||||
uint16_t port = 0;
|
||||
int server_fd = -1;
|
||||
int client_fd = -1;
|
||||
bool overrun = false;
|
||||
|
||||
// Bounded FIFOs to avoid unbounded memory growth if the CPU or client
|
||||
// isn't keeping up. When full, RX drops new data and TX drops old data.
|
||||
std::deque<uint8_t> rx_fifo;
|
||||
std::deque<uint8_t> tx_fifo;
|
||||
size_t rx_capacity = 4096;
|
||||
size_t tx_capacity = 4096;
|
||||
|
||||
sockaddr_in bind_addr {};
|
||||
|
||||
void init(uint16_t port_);
|
||||
void close();
|
||||
void poll_accept(uint32_t uart_idx);
|
||||
void poll_rx(uint32_t uart_idx);
|
||||
void poll_tx(uint32_t uart_idx);
|
||||
bool connected() const { return client_fd >= 0; }
|
||||
};
|
||||
|
||||
uart uarts[N_UARTS];
|
||||
|
||||
tb_uart_state() = default;
|
||||
explicit tb_uart_state(const tb_cli_args &args);
|
||||
~tb_uart_state();
|
||||
|
||||
void step();
|
||||
|
||||
uint32_t read_status(uint32_t uart_idx);
|
||||
uint32_t read_data(uint32_t uart_idx);
|
||||
void write_data(uint32_t uart_idx, uint8_t byte);
|
||||
void write_ctrl(uint32_t uart_idx, uint32_t value);
|
||||
};
|
||||
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
#include "tb_cli.h"
|
||||
#include "tb_constants.h"
|
||||
#include <iostream>
|
||||
|
||||
static const char *help_str =
|
||||
"Usage: tb [--bin x.bin] [--port n] [--vpi-port n] [--vcd x.vcd] [--dump start end] \\\n"
|
||||
" [--cycles n] [--cpuret] [--jtagdump x] [--jtagreplay x] [--vpi-poll n] \\\n"
|
||||
" [--gdb-port n] [--uart0-port n] [--uart1-port n]\n"
|
||||
" [--vpi-clk-per-tck n]\n"
|
||||
"\n"
|
||||
" --bin x.bin : Flat binary file loaded to address 0x0 in RAM\n"
|
||||
" --vcd x.vcd : Path to dump waveforms to\n"
|
||||
" --dump start end : Print out memory contents from start to end (exclusive)\n"
|
||||
" after execution finishes. Can be passed multiple times.\n"
|
||||
" --cycles n : Maximum number of cycles to run before exiting.\n"
|
||||
" Default is 0 (no maximum).\n"
|
||||
" --port n : Port number to listen for openocd remote bitbang. Sim\n"
|
||||
" runs in lockstep with JTAG bitbang, not free-running.\n"
|
||||
" --vpi-port n : Port number to listen for openocd jtag_vpi. Faster than\n"
|
||||
" remote bitbang, and sim is free-running.\n"
|
||||
" --vpi-poll n : When using --vpi-port, back off up to n core cycles\n"
|
||||
" between socket polls when OpenOCD is idle (0 = every cycle).\n"
|
||||
" --gdb-port n : Listen for GDB RSP directly (no OpenOCD/JTAG).\n"
|
||||
" --uart0-port n : Expose testbench UART0 as a TCP stream.\n"
|
||||
" --uart1-port n : Expose testbench UART1 as a TCP stream.\n"
|
||||
" --vpi-clk-per-tck n : When using --vpi-port, run n core clock cycles per\n"
|
||||
" JTAG TCK cycle during OpenOCD TMS sequences, so DMI/APB\n"
|
||||
" CDC can make progress without huge BUSY delays.\n"
|
||||
" --cpuret : Testbench's return code is the return code written to\n"
|
||||
" IO_EXIT by the CPU, or -1 if timed out.\n"
|
||||
" --jtagdump : Dump OpenOCD JTAG bitbang commands to a file so they\n"
|
||||
" can be replayed. (Lower perf impact than VCD dumping)\n"
|
||||
" --jtagreplay : Play back some dumped OpenOCD JTAG bitbang commands\n"
|
||||
" --logfile path : File to write testbench stdout\n"
|
||||
" --sigfile path : File to write only the data from --dump commands\n"
|
||||
" (hex, 32 bits per line, same as riscv-arch-test)\n"
|
||||
#ifdef CXXRTL_DEBUG_AGENT
|
||||
" --debug : Run CXXRTL debugger\n"
|
||||
#endif
|
||||
;
|
||||
|
||||
static void exit_help(std::string errtext = "") {
|
||||
std::cerr << errtext << help_str;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
void tb_parse_args(int argc, char **argv, tb_cli_args &args) {
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string s(argv[i]);
|
||||
if (s.substr(0, 11) == "+verilator+") {
|
||||
// Skip arguments passed directly to verilator context
|
||||
i += 1;
|
||||
} else if (s.rfind("--", 0) != 0) {
|
||||
std::cerr << "Unexpected positional argument " << s << "\n";
|
||||
exit_help("");
|
||||
} else if (s == "--bin") {
|
||||
if (argc - i < 2)
|
||||
exit_help("Option --bin requires an argument\n");
|
||||
args.load_bin = true;
|
||||
args.bin_path = argv[i + 1];
|
||||
i += 1;
|
||||
} else if (s == "--vcd") {
|
||||
if (argc - i < 2)
|
||||
exit_help("Option --vcd requires an argument\n");
|
||||
args.dump_waves = true;
|
||||
args.waves_path = argv[i + 1];
|
||||
i += 1;
|
||||
} else if (s == "--logfile") {
|
||||
if (argc - i < 2)
|
||||
exit_help("Option --logfile requires an argument\n");
|
||||
args.log_path = argv[i + 1];
|
||||
i += 1;
|
||||
} else if (s == "--sigfile") {
|
||||
if (argc - i < 2)
|
||||
exit_help("Option --sigfile requires an argument\n");
|
||||
args.sig_path = argv[i + 1];
|
||||
i += 1;
|
||||
} else if (s == "--jtagdump") {
|
||||
if (argc - i < 2)
|
||||
exit_help("Option --jtagdump requires an argument\n");
|
||||
args.dump_jtag = true;
|
||||
args.jtag_dump_path = argv[i + 1];
|
||||
i += 1;
|
||||
} else if (s == "--jtagreplay") {
|
||||
if (argc - i < 2)
|
||||
exit_help("Option --jtagreplay requires an argument\n");
|
||||
args.replay_jtag = true;
|
||||
args.jtag_replay_path = argv[i + 1];
|
||||
i += 1;
|
||||
} else if (s == "--dump") {
|
||||
if (argc - i < 3)
|
||||
exit_help("Option --dump requires 2 arguments\n");
|
||||
uint32_t first = std::stoul(argv[i + 1], 0, 0);
|
||||
uint32_t last = std::stoul(argv[i + 2], 0, 0);
|
||||
if (first < MEM_BASE || last > MEM_BASE + MEM_SIZE || first > last) {
|
||||
std::cerr << "Invalid memory range\n";
|
||||
exit(-1);
|
||||
}
|
||||
args.dump_ranges.push_back(std::pair<uint32_t, uint32_t>(
|
||||
first, last
|
||||
));
|
||||
i += 2;
|
||||
} else if (s == "--cycles") {
|
||||
if (argc - i < 2)
|
||||
exit_help("Option --cycles requires an argument\n");
|
||||
args.max_cycles = std::stol(argv[i + 1], 0, 0);
|
||||
i += 1;
|
||||
} else if (s == "--port") {
|
||||
if (argc - i < 2)
|
||||
exit_help("Option --port requires an argument\n");
|
||||
args.port = std::stol(argv[i + 1], 0, 0);
|
||||
i += 1;
|
||||
} else if (s == "--vpi-port") {
|
||||
if (argc - i < 2)
|
||||
exit_help("Option --vpi-port requires an argument\n");
|
||||
args.vpi_port = std::stol(argv[i + 1], 0, 0);
|
||||
i += 1;
|
||||
} else if (s == "--gdb-port") {
|
||||
if (argc - i < 2)
|
||||
exit_help("Option --gdb-port requires an argument\n");
|
||||
args.gdb_port = std::stol(argv[i + 1], 0, 0);
|
||||
i += 1;
|
||||
} else if (s == "--uart0-port") {
|
||||
if (argc - i < 2)
|
||||
exit_help("Option --uart0-port requires an argument\n");
|
||||
args.uart0_port = std::stol(argv[i + 1], 0, 0);
|
||||
i += 1;
|
||||
} else if (s == "--uart1-port") {
|
||||
if (argc - i < 2)
|
||||
exit_help("Option --uart1-port requires an argument\n");
|
||||
args.uart1_port = std::stol(argv[i + 1], 0, 0);
|
||||
i += 1;
|
||||
} else if (s == "--vpi-poll") {
|
||||
if (argc - i < 2)
|
||||
exit_help("Option --vpi-poll requires an argument\n");
|
||||
args.vpi_poll_cycles = (uint32_t)std::stoul(argv[i + 1], 0, 0);
|
||||
i += 1;
|
||||
} else if (s == "--vpi-clk-per-tck") {
|
||||
if (argc - i < 2)
|
||||
exit_help("Option --vpi-clk-per-tck requires an argument\n");
|
||||
args.vpi_clk_per_tck = (uint32_t)std::stoul(argv[i + 1], 0, 0);
|
||||
i += 1;
|
||||
} else if (s == "--cpuret") {
|
||||
args.propagate_return_code = true;
|
||||
#ifdef CXXRTL_DEBUG_AGENT
|
||||
} else if (s == "--debug") {
|
||||
args.run_agent = true;
|
||||
#endif
|
||||
} else {
|
||||
std::cerr << "Unrecognised argument " << s << "\n";
|
||||
exit_help("");
|
||||
}
|
||||
}
|
||||
if (!(args.load_bin || args.port != 0 || args.vpi_port != 0 || args.gdb_port != 0 || args.replay_jtag))
|
||||
exit_help("At least one of --bin, --port, --vpi-port, --gdb-port or --jtagreplay must be specified.\n");
|
||||
if ((args.port != 0) + (args.vpi_port != 0) + (args.gdb_port != 0) > 1)
|
||||
exit_help("Can't combine --port/--vpi-port/--gdb-port\n");
|
||||
if (args.dump_jtag && args.port == 0)
|
||||
exit_help("--jtagdump is only supported with --port (remote bitbang)\n");
|
||||
if (args.replay_jtag && (args.port != 0 || args.vpi_port != 0 || args.gdb_port != 0))
|
||||
exit_help("Can't specify --jtagreplay together with --port/--vpi-port/--gdb-port\n");
|
||||
}
|
||||
+679
@@ -0,0 +1,679 @@
|
||||
#include "tb_gdb.h"
|
||||
#include "tb_constants.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static bool send_all(int fd, const uint8_t *buf, size_t len) {
|
||||
size_t sent = 0;
|
||||
while (sent < len) {
|
||||
#ifdef MSG_NOSIGNAL
|
||||
ssize_t n = send(fd, buf + sent, len - sent, MSG_NOSIGNAL);
|
||||
#else
|
||||
ssize_t n = send(fd, buf + sent, len - sent, 0);
|
||||
#endif
|
||||
if (n < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
sent += (size_t)n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
tb_gdb_server::tb_gdb_server(uint16_t port, tb_gdb_target &target)
|
||||
: port_{port}, target_{target} {
|
||||
target_xml_ =
|
||||
"<?xml version=\"1.0\"?>\n"
|
||||
"<!DOCTYPE target SYSTEM \"gdb-target.dtd\">\n"
|
||||
"<target>\n"
|
||||
" <architecture>riscv:rv32</architecture>\n"
|
||||
" <feature name=\"org.gnu.gdb.riscv.cpu\">\n"
|
||||
" <reg name=\"x0\" bitsize=\"32\" type=\"int\" regnum=\"0\"/>\n"
|
||||
" <reg name=\"x1\" bitsize=\"32\" type=\"int\" regnum=\"1\"/>\n"
|
||||
" <reg name=\"x2\" bitsize=\"32\" type=\"int\" regnum=\"2\"/>\n"
|
||||
" <reg name=\"x3\" bitsize=\"32\" type=\"int\" regnum=\"3\"/>\n"
|
||||
" <reg name=\"x4\" bitsize=\"32\" type=\"int\" regnum=\"4\"/>\n"
|
||||
" <reg name=\"x5\" bitsize=\"32\" type=\"int\" regnum=\"5\"/>\n"
|
||||
" <reg name=\"x6\" bitsize=\"32\" type=\"int\" regnum=\"6\"/>\n"
|
||||
" <reg name=\"x7\" bitsize=\"32\" type=\"int\" regnum=\"7\"/>\n"
|
||||
" <reg name=\"x8\" bitsize=\"32\" type=\"int\" regnum=\"8\"/>\n"
|
||||
" <reg name=\"x9\" bitsize=\"32\" type=\"int\" regnum=\"9\"/>\n"
|
||||
" <reg name=\"x10\" bitsize=\"32\" type=\"int\" regnum=\"10\"/>\n"
|
||||
" <reg name=\"x11\" bitsize=\"32\" type=\"int\" regnum=\"11\"/>\n"
|
||||
" <reg name=\"x12\" bitsize=\"32\" type=\"int\" regnum=\"12\"/>\n"
|
||||
" <reg name=\"x13\" bitsize=\"32\" type=\"int\" regnum=\"13\"/>\n"
|
||||
" <reg name=\"x14\" bitsize=\"32\" type=\"int\" regnum=\"14\"/>\n"
|
||||
" <reg name=\"x15\" bitsize=\"32\" type=\"int\" regnum=\"15\"/>\n"
|
||||
" <reg name=\"x16\" bitsize=\"32\" type=\"int\" regnum=\"16\"/>\n"
|
||||
" <reg name=\"x17\" bitsize=\"32\" type=\"int\" regnum=\"17\"/>\n"
|
||||
" <reg name=\"x18\" bitsize=\"32\" type=\"int\" regnum=\"18\"/>\n"
|
||||
" <reg name=\"x19\" bitsize=\"32\" type=\"int\" regnum=\"19\"/>\n"
|
||||
" <reg name=\"x20\" bitsize=\"32\" type=\"int\" regnum=\"20\"/>\n"
|
||||
" <reg name=\"x21\" bitsize=\"32\" type=\"int\" regnum=\"21\"/>\n"
|
||||
" <reg name=\"x22\" bitsize=\"32\" type=\"int\" regnum=\"22\"/>\n"
|
||||
" <reg name=\"x23\" bitsize=\"32\" type=\"int\" regnum=\"23\"/>\n"
|
||||
" <reg name=\"x24\" bitsize=\"32\" type=\"int\" regnum=\"24\"/>\n"
|
||||
" <reg name=\"x25\" bitsize=\"32\" type=\"int\" regnum=\"25\"/>\n"
|
||||
" <reg name=\"x26\" bitsize=\"32\" type=\"int\" regnum=\"26\"/>\n"
|
||||
" <reg name=\"x27\" bitsize=\"32\" type=\"int\" regnum=\"27\"/>\n"
|
||||
" <reg name=\"x28\" bitsize=\"32\" type=\"int\" regnum=\"28\"/>\n"
|
||||
" <reg name=\"x29\" bitsize=\"32\" type=\"int\" regnum=\"29\"/>\n"
|
||||
" <reg name=\"x30\" bitsize=\"32\" type=\"int\" regnum=\"30\"/>\n"
|
||||
" <reg name=\"x31\" bitsize=\"32\" type=\"int\" regnum=\"31\"/>\n"
|
||||
" <reg name=\"pc\" bitsize=\"32\" type=\"code_ptr\" regnum=\"32\"/>\n"
|
||||
" </feature>\n"
|
||||
"</target>\n";
|
||||
|
||||
char mm_line[128];
|
||||
snprintf(
|
||||
mm_line,
|
||||
sizeof(mm_line),
|
||||
" <memory type=\"ram\" start=\"0x%08x\" length=\"0x%08x\"/>\n",
|
||||
(unsigned)MEM_BASE,
|
||||
(unsigned)MEM_SIZE
|
||||
);
|
||||
|
||||
memory_map_xml_ =
|
||||
"<?xml version=\"1.0\"?>\n"
|
||||
"<!DOCTYPE memory-map PUBLIC \"+//IDN gnu.org//DTD GDB Memory Map V1.0//EN\" "
|
||||
"\"http://sourceware.org/gdb/gdb-memory-map.dtd\">\n"
|
||||
"<memory-map>\n" +
|
||||
std::string(mm_line) +
|
||||
"</memory-map>\n";
|
||||
}
|
||||
|
||||
tb_gdb_server::~tb_gdb_server() {
|
||||
close_client_();
|
||||
if (server_fd_ >= 0) {
|
||||
::close(server_fd_);
|
||||
server_fd_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool tb_gdb_server::open_listen_socket_() {
|
||||
server_fd_ = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (server_fd_ < 0) {
|
||||
fprintf(stderr, "tb_gdb: socket() failed: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
int opt = 1;
|
||||
if (setsockopt(server_fd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
|
||||
fprintf(stderr, "tb_gdb: setsockopt(SO_REUSEADDR) failed: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
#ifdef SO_REUSEPORT
|
||||
(void)setsockopt(server_fd_, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
|
||||
#endif
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
// Debug services are local by default. Remote access belongs behind an
|
||||
// explicit SSH tunnel, never an unauthenticated wildcard listener.
|
||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
addr.sin_port = htons(port_);
|
||||
if (bind(server_fd_, (sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
fprintf(stderr, "tb_gdb: bind(127.0.0.1:%u) failed: %s\n", (unsigned)port_, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
if (listen(server_fd_, 1) < 0) {
|
||||
fprintf(stderr, "tb_gdb: listen() failed: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tb_gdb_server::accept_client_() {
|
||||
sockaddr_in addr{};
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
client_fd_ = accept(server_fd_, (sockaddr *)&addr, &addr_len);
|
||||
if (client_fd_ < 0) {
|
||||
fprintf(stderr, "tb_gdb: accept() failed: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
int flag = 1;
|
||||
(void)setsockopt(client_fd_, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag));
|
||||
return true;
|
||||
}
|
||||
|
||||
void tb_gdb_server::close_client_() {
|
||||
if (client_fd_ >= 0) {
|
||||
::close(client_fd_);
|
||||
client_fd_ = -1;
|
||||
}
|
||||
no_ack_mode_ = false;
|
||||
breakpoints_.clear();
|
||||
ignore_breakpoint_pc_ = 0xffffffffu;
|
||||
}
|
||||
|
||||
uint8_t tb_gdb_server::checksum_(const std::string &s) {
|
||||
uint8_t sum = 0;
|
||||
for (unsigned char c : s)
|
||||
sum = (uint8_t)(sum + c);
|
||||
return sum;
|
||||
}
|
||||
|
||||
int tb_gdb_server::hex_val_(char c) {
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return 10 + (c - 'a');
|
||||
if (c >= 'A' && c <= 'F')
|
||||
return 10 + (c - 'A');
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string tb_gdb_server::to_hex_bytes_(const uint8_t *data, size_t len) {
|
||||
static const char *hex = "0123456789abcdef";
|
||||
std::string out;
|
||||
out.reserve(len * 2);
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
out.push_back(hex[(data[i] >> 4) & 0xf]);
|
||||
out.push_back(hex[data[i] & 0xf]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool tb_gdb_server::from_hex_bytes_(const std::string &hex, std::string &out_bytes) {
|
||||
out_bytes.clear();
|
||||
if (hex.size() % 2 != 0)
|
||||
return false;
|
||||
out_bytes.reserve(hex.size() / 2);
|
||||
for (size_t i = 0; i < hex.size(); i += 2) {
|
||||
int hi = hex_val_(hex[i]);
|
||||
int lo = hex_val_(hex[i + 1]);
|
||||
if (hi < 0 || lo < 0)
|
||||
return false;
|
||||
out_bytes.push_back((char)((hi << 4) | lo));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void tb_gdb_server::append_u32_le_hex_(std::string &out, uint32_t v) {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
const uint8_t b = (uint8_t)((v >> (8 * i)) & 0xffu);
|
||||
static const char *hex = "0123456789abcdef";
|
||||
out.push_back(hex[(b >> 4) & 0xf]);
|
||||
out.push_back(hex[b & 0xf]);
|
||||
}
|
||||
}
|
||||
|
||||
bool tb_gdb_server::parse_u32_hex_(const std::string &s, uint32_t &out) {
|
||||
if (s.empty())
|
||||
return false;
|
||||
uint32_t v = 0;
|
||||
for (char c : s) {
|
||||
int h = hex_val_(c);
|
||||
if (h < 0)
|
||||
return false;
|
||||
v = (v << 4) | (uint32_t)h;
|
||||
}
|
||||
out = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tb_gdb_server::maybe_send_ack_(bool ok) {
|
||||
if (no_ack_mode_)
|
||||
return true;
|
||||
const char c = ok ? '+' : '-';
|
||||
return send_all(client_fd_, (const uint8_t *)&c, 1);
|
||||
}
|
||||
|
||||
bool tb_gdb_server::send_packet_(const std::string &payload) {
|
||||
std::string pkt;
|
||||
pkt.reserve(payload.size() + 4);
|
||||
pkt.push_back('$');
|
||||
pkt += payload;
|
||||
pkt.push_back('#');
|
||||
uint8_t cksum = checksum_(payload);
|
||||
static const char *hex = "0123456789abcdef";
|
||||
pkt.push_back(hex[(cksum >> 4) & 0xf]);
|
||||
pkt.push_back(hex[cksum & 0xf]);
|
||||
return send_all(client_fd_, (const uint8_t *)pkt.data(), pkt.size());
|
||||
}
|
||||
|
||||
bool tb_gdb_server::check_interrupt_() {
|
||||
uint8_t c = 0;
|
||||
ssize_t n = recv(client_fd_, &c, 1, MSG_DONTWAIT | MSG_PEEK);
|
||||
if (n < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
if (n == 0)
|
||||
return true;
|
||||
if (c != 0x03)
|
||||
return false;
|
||||
(void)recv(client_fd_, &c, 1, MSG_DONTWAIT);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tb_gdb_server::recv_packet_(std::string &out_payload, bool &got_interrupt) {
|
||||
out_payload.clear();
|
||||
got_interrupt = false;
|
||||
while (true) {
|
||||
uint8_t c = 0;
|
||||
ssize_t n = recv(client_fd_, &c, 1, 0);
|
||||
if (n == 0)
|
||||
return false;
|
||||
if (n < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c == 0x03) {
|
||||
got_interrupt = true;
|
||||
return true;
|
||||
}
|
||||
if (c == '+' || c == '-') {
|
||||
continue;
|
||||
}
|
||||
if (c != '$') {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string payload;
|
||||
while (true) {
|
||||
n = recv(client_fd_, &c, 1, 0);
|
||||
if (n == 0)
|
||||
return false;
|
||||
if (n < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
if (c == '#')
|
||||
break;
|
||||
payload.push_back((char)c);
|
||||
}
|
||||
|
||||
char ck[2];
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
n = recv(client_fd_, &c, 1, 0);
|
||||
if (n == 0)
|
||||
return false;
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) {
|
||||
--i;
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
ck[i] = (char)c;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
const int hi = hex_val_(ck[0]);
|
||||
const int lo = hex_val_(ck[1]);
|
||||
if (hi < 0 || lo < 0) {
|
||||
ok = false;
|
||||
} else {
|
||||
const uint8_t expected = (uint8_t)((hi << 4) | lo);
|
||||
ok = expected == checksum_(payload);
|
||||
}
|
||||
|
||||
if (!maybe_send_ack_(ok))
|
||||
return false;
|
||||
if (!ok)
|
||||
continue;
|
||||
|
||||
out_payload = std::move(payload);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string tb_gdb_server::stop_reply_(int signo) const {
|
||||
char buf[8];
|
||||
snprintf(buf, sizeof(buf), "S%02x", signo & 0xff);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
std::string tb_gdb_server::stop_reply_exit_() const {
|
||||
char buf[8];
|
||||
snprintf(buf, sizeof(buf), "W%02x", (unsigned)(target_.exit_code() & 0xffu));
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
std::string tb_gdb_server::handle_command_(const std::string &cmd, bool &should_close) {
|
||||
should_close = false;
|
||||
|
||||
if (cmd.empty())
|
||||
return "";
|
||||
|
||||
// General queries
|
||||
if (cmd == "?")
|
||||
return stop_reply_(5); // SIGTRAP
|
||||
|
||||
if (cmd.rfind("qSupported", 0) == 0) {
|
||||
return "PacketSize=4000;qXfer:features:read+;qXfer:memory-map:read+;QStartNoAckMode+;swbreak+;hwbreak+;vContSupported+;qRcmd+";
|
||||
}
|
||||
if (cmd == "QStartNoAckMode") {
|
||||
no_ack_mode_ = true;
|
||||
return "OK";
|
||||
}
|
||||
if (cmd == "qAttached")
|
||||
return "1";
|
||||
if (cmd == "qC")
|
||||
return "QC1";
|
||||
if (cmd == "qfThreadInfo")
|
||||
return "m1";
|
||||
if (cmd == "qsThreadInfo")
|
||||
return "l";
|
||||
if (cmd.rfind("H", 0) == 0)
|
||||
return "OK";
|
||||
if (cmd.rfind("qThreadExtraInfo", 0) == 0) {
|
||||
const std::string s = "hart0";
|
||||
return to_hex_bytes_((const uint8_t *)s.data(), s.size());
|
||||
}
|
||||
if (cmd.rfind("qSymbol", 0) == 0)
|
||||
return "OK";
|
||||
if (cmd == "qTStatus")
|
||||
return "";
|
||||
|
||||
// GDB "monitor" commands (OpenOCD-style). Payload is hex-encoded bytes.
|
||||
if (cmd.rfind("qRcmd,", 0) == 0) {
|
||||
const std::string hex = cmd.substr(strlen("qRcmd,"));
|
||||
std::string decoded;
|
||||
if (!from_hex_bytes_(hex, decoded))
|
||||
return "E01";
|
||||
|
||||
std::string console;
|
||||
if (!target_.monitor_cmd(decoded, console))
|
||||
return "";
|
||||
|
||||
if (!console.empty()) {
|
||||
const std::string payload = "O" + to_hex_bytes_((const uint8_t *)console.data(), console.size());
|
||||
(void)send_packet_(payload);
|
||||
}
|
||||
return "OK";
|
||||
}
|
||||
|
||||
// Extended-remote mode request
|
||||
if (cmd == "!")
|
||||
return "OK";
|
||||
|
||||
// target.xml for riscv32 (x0..x31 + pc)
|
||||
if (cmd.rfind("qXfer:features:read:target.xml:", 0) == 0) {
|
||||
const std::string args = cmd.substr(strlen("qXfer:features:read:target.xml:"));
|
||||
const size_t comma = args.find(',');
|
||||
if (comma == std::string::npos)
|
||||
return "E01";
|
||||
uint32_t off = 0, len = 0;
|
||||
if (!parse_u32_hex_(args.substr(0, comma), off) || !parse_u32_hex_(args.substr(comma + 1), len))
|
||||
return "E01";
|
||||
if (off >= target_xml_.size())
|
||||
return "l";
|
||||
const size_t end = std::min<size_t>(target_xml_.size(), (size_t)off + (size_t)len);
|
||||
const bool more = end < target_xml_.size();
|
||||
std::string out;
|
||||
out.reserve(1 + (end - off));
|
||||
out.push_back(more ? 'm' : 'l');
|
||||
out.append(target_xml_, off, end - off);
|
||||
return out;
|
||||
}
|
||||
|
||||
// memory-map.xml (RAM region for disassembly / memory accessibility)
|
||||
if (cmd.rfind("qXfer:memory-map:read::", 0) == 0) {
|
||||
const std::string args = cmd.substr(strlen("qXfer:memory-map:read::"));
|
||||
const size_t comma = args.find(',');
|
||||
if (comma == std::string::npos)
|
||||
return "E01";
|
||||
uint32_t off = 0, len = 0;
|
||||
if (!parse_u32_hex_(args.substr(0, comma), off) || !parse_u32_hex_(args.substr(comma + 1), len))
|
||||
return "E01";
|
||||
if (off >= memory_map_xml_.size())
|
||||
return "l";
|
||||
const size_t end = std::min<size_t>(memory_map_xml_.size(), (size_t)off + (size_t)len);
|
||||
const bool more = end < memory_map_xml_.size();
|
||||
std::string out;
|
||||
out.reserve(1 + (end - off));
|
||||
out.push_back(more ? 'm' : 'l');
|
||||
out.append(memory_map_xml_, off, end - off);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Registers
|
||||
if (cmd == "g") {
|
||||
std::string out;
|
||||
out.reserve(33 * 8);
|
||||
for (uint32_t r = 0; r < 33; ++r)
|
||||
append_u32_le_hex_(out, target_.read_reg(r));
|
||||
return out;
|
||||
}
|
||||
if (cmd.size() >= 2 && cmd[0] == 'p') {
|
||||
uint32_t regno = 0;
|
||||
if (!parse_u32_hex_(cmd.substr(1), regno))
|
||||
return "E01";
|
||||
std::string out;
|
||||
out.reserve(8);
|
||||
append_u32_le_hex_(out, target_.read_reg(regno));
|
||||
return out;
|
||||
}
|
||||
if (cmd.size() >= 3 && cmd[0] == 'P') {
|
||||
const size_t eq = cmd.find('=');
|
||||
if (eq == std::string::npos)
|
||||
return "E01";
|
||||
uint32_t regno = 0;
|
||||
if (!parse_u32_hex_(cmd.substr(1, eq - 1), regno))
|
||||
return "E01";
|
||||
std::string bytes;
|
||||
if (!from_hex_bytes_(cmd.substr(eq + 1), bytes))
|
||||
return "E01";
|
||||
if (bytes.size() < 4)
|
||||
return "E01";
|
||||
uint32_t v = (uint8_t)bytes[0] | ((uint32_t)(uint8_t)bytes[1] << 8) | ((uint32_t)(uint8_t)bytes[2] << 16) |
|
||||
((uint32_t)(uint8_t)bytes[3] << 24);
|
||||
target_.write_reg(regno, v);
|
||||
return "OK";
|
||||
}
|
||||
if (cmd.size() >= 1 && cmd[0] == 'G') {
|
||||
std::string bytes;
|
||||
if (!from_hex_bytes_(cmd.substr(1), bytes))
|
||||
return "E01";
|
||||
if (bytes.size() < 33 * 4)
|
||||
return "E01";
|
||||
for (uint32_t r = 0; r < 33; ++r) {
|
||||
const size_t i = r * 4;
|
||||
uint32_t v = (uint8_t)bytes[i + 0] | ((uint32_t)(uint8_t)bytes[i + 1] << 8) |
|
||||
((uint32_t)(uint8_t)bytes[i + 2] << 16) | ((uint32_t)(uint8_t)bytes[i + 3] << 24);
|
||||
target_.write_reg(r, v);
|
||||
}
|
||||
return "OK";
|
||||
}
|
||||
|
||||
// Memory
|
||||
if (cmd.size() >= 2 && cmd[0] == 'm') {
|
||||
const size_t comma = cmd.find(',');
|
||||
if (comma == std::string::npos)
|
||||
return "E01";
|
||||
uint32_t addr = 0, len = 0;
|
||||
if (!parse_u32_hex_(cmd.substr(1, comma - 1), addr) || !parse_u32_hex_(cmd.substr(comma + 1), len))
|
||||
return "E01";
|
||||
if (len == 0)
|
||||
return "";
|
||||
std::string buf(len, '\0');
|
||||
if (!target_.read_mem(addr, (uint8_t *)&buf[0], (size_t)len))
|
||||
return "E01";
|
||||
return to_hex_bytes_((const uint8_t *)buf.data(), buf.size());
|
||||
}
|
||||
if (cmd.size() >= 2 && cmd[0] == 'M') {
|
||||
const size_t comma = cmd.find(',');
|
||||
const size_t colon = cmd.find(':');
|
||||
if (comma == std::string::npos || colon == std::string::npos || comma > colon)
|
||||
return "E01";
|
||||
uint32_t addr = 0, len = 0;
|
||||
if (!parse_u32_hex_(cmd.substr(1, comma - 1), addr) || !parse_u32_hex_(cmd.substr(comma + 1, colon - comma - 1), len))
|
||||
return "E01";
|
||||
if (len == 0)
|
||||
return "OK";
|
||||
std::string bytes;
|
||||
if (!from_hex_bytes_(cmd.substr(colon + 1), bytes))
|
||||
return "E01";
|
||||
if (bytes.size() < len)
|
||||
return "E01";
|
||||
if (!target_.write_mem(addr, (const uint8_t *)bytes.data(), len))
|
||||
return "E01";
|
||||
return "OK";
|
||||
}
|
||||
|
||||
// Execute breakpoints.
|
||||
//
|
||||
// GDB's stock stepi implementation may place a temporary software
|
||||
// breakpoint (Z0) or hardware breakpoint (Z1) at the predicted next PC,
|
||||
// for example when stepping across an indirect jalr into a read-only text
|
||||
// section. This testbench doesn't patch target memory, so both packet types
|
||||
// are handled identically as execute breakpoints checked in the run loop.
|
||||
if (cmd.rfind("Z0,", 0) == 0 || cmd.rfind("z0,", 0) == 0 ||
|
||||
cmd.rfind("Z1,", 0) == 0 || cmd.rfind("z1,", 0) == 0) {
|
||||
const bool set = cmd[0] == 'Z';
|
||||
const size_t comma1 = cmd.find(',');
|
||||
const size_t comma2 = cmd.find(',', comma1 + 1);
|
||||
if (comma1 == std::string::npos || comma2 == std::string::npos)
|
||||
return "E01";
|
||||
uint32_t addr = 0;
|
||||
if (!parse_u32_hex_(cmd.substr(comma1 + 1, comma2 - comma1 - 1), addr))
|
||||
return "E01";
|
||||
if (set)
|
||||
breakpoints_.insert(addr);
|
||||
else
|
||||
breakpoints_.erase(addr);
|
||||
return "OK";
|
||||
}
|
||||
|
||||
// vCont
|
||||
if (cmd == "vCont?")
|
||||
return "vCont;c;s";
|
||||
|
||||
auto do_step = [&](bool single_step) -> std::string {
|
||||
const uint32_t start_pc = target_.read_reg(32);
|
||||
const bool ignore_start_break = single_step || start_pc == ignore_breakpoint_pc_;
|
||||
const bool consume_ignore_pc = start_pc == ignore_breakpoint_pc_;
|
||||
if (consume_ignore_pc)
|
||||
ignore_breakpoint_pc_ = 0xffffffffu;
|
||||
|
||||
if (single_step) {
|
||||
const tb_gdb_run_result r = target_.step_instruction();
|
||||
if (r == tb_gdb_run_result::exited) {
|
||||
// In extended-remote mode keep the connection alive after target exit,
|
||||
// so the user can issue monitor commands such as "reset halt".
|
||||
return stop_reply_exit_();
|
||||
}
|
||||
if (r == tb_gdb_run_result::timed_out) {
|
||||
should_close = true;
|
||||
return stop_reply_(14); // SIGALRM
|
||||
}
|
||||
ignore_breakpoint_pc_ = 0xffffffffu;
|
||||
return stop_reply_(5); // SIGTRAP
|
||||
}
|
||||
|
||||
bool first_iter = true;
|
||||
while (true) {
|
||||
if (check_interrupt_()) {
|
||||
ignore_breakpoint_pc_ = 0xffffffffu;
|
||||
return stop_reply_(2); // SIGINT
|
||||
}
|
||||
|
||||
const uint32_t pc = target_.read_reg(32);
|
||||
if (breakpoints_.count(pc) && !(first_iter && ignore_start_break && pc == start_pc)) {
|
||||
ignore_breakpoint_pc_ = pc;
|
||||
return stop_reply_(5); // SIGTRAP
|
||||
}
|
||||
|
||||
const tb_gdb_run_result r = target_.step_instruction();
|
||||
ignore_breakpoint_pc_ = 0xffffffffu;
|
||||
if (r == tb_gdb_run_result::exited) {
|
||||
// In extended-remote mode keep the connection alive after target exit,
|
||||
// so the user can issue monitor commands such as "reset halt".
|
||||
return stop_reply_exit_();
|
||||
}
|
||||
if (r == tb_gdb_run_result::timed_out) {
|
||||
should_close = true;
|
||||
return stop_reply_(14); // SIGALRM
|
||||
}
|
||||
first_iter = false;
|
||||
}
|
||||
};
|
||||
|
||||
if (cmd.size() >= 1 && (cmd[0] == 'c' || cmd[0] == 's')) {
|
||||
// Optional resume address
|
||||
if (cmd.size() > 1) {
|
||||
uint32_t addr = 0;
|
||||
if (!parse_u32_hex_(cmd.substr(1), addr))
|
||||
return "E01";
|
||||
target_.write_reg(32, addr);
|
||||
}
|
||||
return do_step(cmd[0] == 's');
|
||||
}
|
||||
if (cmd.rfind("vCont;", 0) == 0) {
|
||||
if (cmd == "vCont;c")
|
||||
return do_step(false);
|
||||
if (cmd == "vCont;s")
|
||||
return do_step(true);
|
||||
return "";
|
||||
}
|
||||
|
||||
// Detach / kill
|
||||
if (cmd == "D") {
|
||||
should_close = true;
|
||||
return "OK";
|
||||
}
|
||||
if (cmd == "k") {
|
||||
should_close = true;
|
||||
return "OK";
|
||||
}
|
||||
|
||||
// Unknown/unsupported -> empty response
|
||||
return "";
|
||||
}
|
||||
|
||||
tb_gdb_run_result tb_gdb_server::serve() {
|
||||
if (!open_listen_socket_())
|
||||
return tb_gdb_run_result::error;
|
||||
|
||||
fprintf(stderr, "tb_gdb: listening on 127.0.0.1:%u\n", (unsigned)port_);
|
||||
if (!accept_client_())
|
||||
return tb_gdb_run_result::error;
|
||||
|
||||
fprintf(stderr, "tb_gdb: client connected\n");
|
||||
if (!send_packet_(stop_reply_(5)))
|
||||
return tb_gdb_run_result::error;
|
||||
|
||||
while (true) {
|
||||
std::string cmd;
|
||||
bool got_interrupt = false;
|
||||
if (!recv_packet_(cmd, got_interrupt))
|
||||
break;
|
||||
if (got_interrupt) {
|
||||
if (!send_packet_(stop_reply_(2)))
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool should_close = false;
|
||||
std::string reply = handle_command_(cmd, should_close);
|
||||
if (!send_packet_(reply))
|
||||
break;
|
||||
|
||||
if (should_close) {
|
||||
if (!reply.empty() && reply[0] == 'W')
|
||||
return tb_gdb_run_result::exited;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
close_client_();
|
||||
return tb_gdb_run_result::ok;
|
||||
}
|
||||
+481
@@ -0,0 +1,481 @@
|
||||
#include "tb_cli.h"
|
||||
#include "tb_jtag.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <stdio.h>
|
||||
#include <iostream>
|
||||
#include <netinet/tcp.h>
|
||||
|
||||
// This file contains socket management logic and parsing of OpenOCD JTAG
|
||||
// protocols:
|
||||
// - remote_bitbang (simple, lockstep with CPU clock)
|
||||
// - jtag_vpi (batched, higher throughput)
|
||||
|
||||
|
||||
static int wait_for_connection_bitbang(int server_fd, uint16_t port, struct sockaddr *sock_addr, socklen_t *sock_addr_len) {
|
||||
int sock_fd;
|
||||
printf("Waiting for connection on port %u\n", port);
|
||||
if (listen(server_fd, 3) < 0) {
|
||||
fprintf(stderr, "listen failed\n");
|
||||
exit(-1);
|
||||
}
|
||||
sock_fd = accept(server_fd, sock_addr, sock_addr_len);
|
||||
if (sock_fd < 0) {
|
||||
fprintf(stderr, "accept failed\n");
|
||||
exit(-1);
|
||||
}
|
||||
printf("Connected\n");
|
||||
return sock_fd;
|
||||
}
|
||||
|
||||
static int wait_for_connection_vpi(int server_fd, uint16_t port, struct sockaddr *sock_addr, socklen_t *sock_addr_len) {
|
||||
int sock_fd;
|
||||
printf("Listening on port %u\n", port);
|
||||
if (listen(server_fd, 3) < 0) {
|
||||
fprintf(stderr, "listen failed\n");
|
||||
exit(-1);
|
||||
}
|
||||
sock_fd = accept(server_fd, sock_addr, sock_addr_len);
|
||||
if (sock_fd < 0) {
|
||||
fprintf(stderr, "accept failed\n");
|
||||
exit(-1);
|
||||
}
|
||||
printf("Connected\n");
|
||||
return sock_fd;
|
||||
}
|
||||
|
||||
static uint32_t load_le32(const uint8_t *p) {
|
||||
return (uint32_t)p[0]
|
||||
| ((uint32_t)p[1] << 8)
|
||||
| ((uint32_t)p[2] << 16)
|
||||
| ((uint32_t)p[3] << 24);
|
||||
}
|
||||
|
||||
static void store_le32(uint8_t *p, uint32_t v) {
|
||||
p[0] = v & 0xffu;
|
||||
p[1] = (v >> 8) & 0xffu;
|
||||
p[2] = (v >> 16) & 0xffu;
|
||||
p[3] = (v >> 24) & 0xffu;
|
||||
}
|
||||
|
||||
static bool get_bit_lsb0(const uint8_t *buf, uint32_t bit_idx) {
|
||||
return (buf[bit_idx / 8] >> (bit_idx % 8)) & 0x1;
|
||||
}
|
||||
|
||||
static void set_bit_lsb0(uint8_t *buf, uint32_t bit_idx, bool value) {
|
||||
const uint8_t mask = 1u << (bit_idx % 8);
|
||||
if (value) {
|
||||
buf[bit_idx / 8] |= mask;
|
||||
} else {
|
||||
buf[bit_idx / 8] &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
// Perform one JTAG clock cycle (TCK low -> high -> low) and return the TDO bit
|
||||
// sampled *before* the rising edge. This matches OpenOCD's scan semantics.
|
||||
static bool jtag_clock(tb_top &tb, bool tms, bool tdi) {
|
||||
// Sample TDO (stable between the previous negedge and the next posedge),
|
||||
// then generate a posedge+negedge pair with the new TMS/TDI values.
|
||||
const bool tdo = tb.get_tdo();
|
||||
tb.set_tms(tms);
|
||||
tb.set_tdi(tdi);
|
||||
tb.set_tck(true);
|
||||
tb.eval();
|
||||
|
||||
tb.set_tck(false);
|
||||
tb.eval();
|
||||
|
||||
return tdo;
|
||||
}
|
||||
|
||||
static bool send_all(int fd, const uint8_t *buf, size_t len) {
|
||||
size_t sent = 0;
|
||||
while (sent < len) {
|
||||
ssize_t n = send(fd, buf + sent, len - sent, 0);
|
||||
if (n < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
sent += (size_t)n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
tb_jtag_state::tb_jtag_state(const tb_cli_args &_args) {
|
||||
args = _args;
|
||||
transport = transport_t::none;
|
||||
server_fd = -1;
|
||||
sock_fd = -1;
|
||||
sock_opt = 1;
|
||||
sock_addr_len = sizeof(sock_addr);
|
||||
rx_ptr = 0;
|
||||
rx_remaining = 0;
|
||||
tx_ptr = 0;
|
||||
vpi_rx_count = 0;
|
||||
vpi_poll_ctr = 0;
|
||||
vpi_poll_backoff = 0;
|
||||
|
||||
if (args.vpi_port != 0) {
|
||||
transport = transport_t::jtag_vpi;
|
||||
server_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (server_fd < 0) {
|
||||
fprintf(stderr, "socket creation failed: %s\n", strerror(errno));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &sock_opt, sizeof(sock_opt)) < 0) {
|
||||
fprintf(stderr, "setsockopt(SO_REUSEADDR) failed: %s\n", strerror(errno));
|
||||
exit(-1);
|
||||
}
|
||||
#ifdef SO_REUSEPORT
|
||||
// Best-effort: can fail on some kernels/configs, but is not required.
|
||||
(void)setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, &sock_opt, sizeof(sock_opt));
|
||||
#endif
|
||||
|
||||
sock_addr.sin_family = AF_INET;
|
||||
sock_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
sock_addr.sin_port = htons(args.vpi_port);
|
||||
if (bind(server_fd, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) < 0) {
|
||||
fprintf(stderr, "bind failed: %s\n", strerror(errno));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
sock_fd = wait_for_connection_vpi(server_fd, args.vpi_port, (struct sockaddr *)&sock_addr, &sock_addr_len);
|
||||
|
||||
// Low-latency local socket traffic helps performance a lot.
|
||||
int flag = 1;
|
||||
setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag));
|
||||
} else if (args.port != 0) {
|
||||
transport = transport_t::remote_bitbang;
|
||||
server_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (server_fd < 0) {
|
||||
fprintf(stderr, "socket creation failed: %s\n", strerror(errno));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &sock_opt, sizeof(sock_opt)) < 0) {
|
||||
fprintf(stderr, "setsockopt(SO_REUSEADDR) failed: %s\n", strerror(errno));
|
||||
exit(-1);
|
||||
}
|
||||
#ifdef SO_REUSEPORT
|
||||
// Best-effort: can fail on some kernels/configs, but is not required.
|
||||
(void)setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, &sock_opt, sizeof(sock_opt));
|
||||
#endif
|
||||
|
||||
sock_addr.sin_family = AF_INET;
|
||||
sock_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
sock_addr.sin_port = htons(args.port);
|
||||
if (bind(server_fd, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) < 0) {
|
||||
fprintf(stderr, "bind failed: %s\n", strerror(errno));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
sock_fd = wait_for_connection_bitbang(server_fd, args.port, (struct sockaddr *)&sock_addr, &sock_addr_len);
|
||||
} else if (args.replay_jtag) {
|
||||
transport = transport_t::remote_bitbang;
|
||||
}
|
||||
|
||||
if (args.dump_jtag) {
|
||||
jtag_dump_fd.open(args.jtag_dump_path);
|
||||
if (!jtag_dump_fd.is_open()) {
|
||||
std::cerr << "Failed to open \"" << args.jtag_dump_path << "\"\n";
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
if (args.replay_jtag) {
|
||||
jtag_replay_fd.open(args.jtag_replay_path);
|
||||
if (!jtag_replay_fd.is_open()) {
|
||||
std::cerr << "Failed to open \"" << args.jtag_replay_path << "\"\n";
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Return true if an exit command was received
|
||||
bool tb_jtag_state::step(tb_top &tb, mem_io_state *memio, int64_t *cycle_count, bool *timed_out, uint32_t *core_cycles_advanced) {
|
||||
if (transport == transport_t::none) {
|
||||
if (core_cycles_advanced)
|
||||
*core_cycles_advanced = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (core_cycles_advanced)
|
||||
*core_cycles_advanced = 0;
|
||||
|
||||
const bool can_advance_core = transport == transport_t::jtag_vpi
|
||||
&& memio != nullptr
|
||||
&& cycle_count != nullptr
|
||||
&& timed_out != nullptr
|
||||
&& args.vpi_clk_per_tck != 0;
|
||||
|
||||
auto advance_one_core_cycle = [&]() -> bool {
|
||||
if (!can_advance_core)
|
||||
return false;
|
||||
if (args.max_cycles != 0 && *cycle_count >= args.max_cycles) {
|
||||
*timed_out = true;
|
||||
return true;
|
||||
}
|
||||
memio->step(tb);
|
||||
tb.step(args, *memio);
|
||||
++(*cycle_count);
|
||||
if (core_cycles_advanced)
|
||||
++(*core_cycles_advanced);
|
||||
if (memio->exit_req)
|
||||
return true;
|
||||
if (args.max_cycles != 0 && *cycle_count >= args.max_cycles) {
|
||||
*timed_out = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (transport == transport_t::jtag_vpi) {
|
||||
// Socket is blocking, but reads are non-blocking via MSG_DONTWAIT so the
|
||||
// CPU can free-run when openocd is idle.
|
||||
//
|
||||
// Important: a recv() syscall every core cycle is very expensive. When
|
||||
// OpenOCD is idle (no data available), back off for N core cycles
|
||||
// (configurable). When data is available, process immediately (no
|
||||
// throughput throttling between packets).
|
||||
if (vpi_rx_count == 0 && args.vpi_poll_cycles != 0 && vpi_poll_ctr != 0) {
|
||||
--vpi_poll_ctr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Protocol constants must match OpenOCD's jtag_vpi driver.
|
||||
static constexpr uint32_t CMD_RESET = 0;
|
||||
static constexpr uint32_t CMD_TMS_SEQ = 1;
|
||||
static constexpr uint32_t CMD_SCAN_CHAIN = 2;
|
||||
static constexpr uint32_t CMD_SCAN_CHAIN_FLIP_TMS = 3;
|
||||
static constexpr uint32_t CMD_STOP_SIMU = 4;
|
||||
|
||||
static constexpr int OFF_CMD = 0;
|
||||
static constexpr int OFF_OUT = 4;
|
||||
static constexpr int OFF_IN = 4 + VPI_XFERT_MAX_SIZE;
|
||||
static constexpr int OFF_LEN = 4 + VPI_XFERT_MAX_SIZE + VPI_XFERT_MAX_SIZE;
|
||||
static constexpr int OFF_NB_BITS = OFF_LEN + 4;
|
||||
|
||||
while (vpi_rx_count < VPI_PKT_SIZE) {
|
||||
ssize_t n = recv(sock_fd, vpi_rxbuf + vpi_rx_count, VPI_PKT_SIZE - vpi_rx_count, MSG_DONTWAIT);
|
||||
if (n < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
// No data available right now. If we don't have a partial
|
||||
// packet, back off for a while to reduce syscall overhead.
|
||||
//
|
||||
// Use an exponential backoff up to vpi_poll_cycles. This
|
||||
// avoids large fixed delays between back-to-back OpenOCD
|
||||
// packets (e.g. GDB single-step), while still reducing
|
||||
// syscall rate when OpenOCD is truly idle.
|
||||
if (vpi_rx_count == 0 && args.vpi_poll_cycles != 0) {
|
||||
if (vpi_poll_backoff == 0) {
|
||||
vpi_poll_backoff = 1;
|
||||
} else if (vpi_poll_backoff < args.vpi_poll_cycles) {
|
||||
uint32_t next = vpi_poll_backoff * 2;
|
||||
if (next < vpi_poll_backoff)
|
||||
next = args.vpi_poll_cycles;
|
||||
if (next > args.vpi_poll_cycles)
|
||||
next = args.vpi_poll_cycles;
|
||||
vpi_poll_backoff = next;
|
||||
}
|
||||
vpi_poll_ctr = vpi_poll_backoff;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
fprintf(stderr, "jtag_vpi recv failed: %s\n", strerror(errno));
|
||||
return true;
|
||||
}
|
||||
if (n == 0) {
|
||||
printf("jtag_vpi connection closed\n");
|
||||
::close(sock_fd);
|
||||
sock_fd = wait_for_connection_vpi(server_fd, args.vpi_port, (struct sockaddr *)&sock_addr, &sock_addr_len);
|
||||
int flag = 1;
|
||||
setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag));
|
||||
vpi_rx_count = 0;
|
||||
vpi_poll_ctr = 0;
|
||||
vpi_poll_backoff = 0;
|
||||
return false;
|
||||
}
|
||||
vpi_rx_count += (int)n;
|
||||
// Any received data means OpenOCD is active again.
|
||||
vpi_poll_backoff = 0;
|
||||
}
|
||||
vpi_poll_ctr = 0;
|
||||
vpi_poll_backoff = 0;
|
||||
|
||||
const uint32_t cmd = load_le32(vpi_rxbuf + OFF_CMD);
|
||||
const uint32_t length = load_le32(vpi_rxbuf + OFF_LEN);
|
||||
const uint32_t nb_bits = load_le32(vpi_rxbuf + OFF_NB_BITS);
|
||||
const uint8_t *buf_out = vpi_rxbuf + OFF_OUT;
|
||||
|
||||
bool got_exit_cmd = false;
|
||||
bool core_stop = false;
|
||||
switch (cmd) {
|
||||
case CMD_RESET: {
|
||||
// Best-effort: reset the TAP via TRST, and also provide 5 TMS=1 clocks
|
||||
// to force Test-Logic-Reset on designs without TRST.
|
||||
tb.set_trst_n(false);
|
||||
tb.eval();
|
||||
tb.set_trst_n(true);
|
||||
tb.eval();
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
jtag_clock(tb, true, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CMD_TMS_SEQ: {
|
||||
if (length > (uint32_t)VPI_XFERT_MAX_SIZE || nb_bits > (uint32_t)VPI_XFERT_MAX_SIZE * 8 || length * 8 < nb_bits) {
|
||||
fprintf(stderr, "jtag_vpi: invalid tms_seq length=%u nb_bits=%u\n", length, nb_bits);
|
||||
got_exit_cmd = true;
|
||||
break;
|
||||
}
|
||||
bool stop_due_to_core = false;
|
||||
for (uint32_t i = 0; i < nb_bits; ++i) {
|
||||
const bool tms = get_bit_lsb0(buf_out, i);
|
||||
jtag_clock(tb, tms, false);
|
||||
if (can_advance_core) {
|
||||
for (uint32_t j = 0; j < args.vpi_clk_per_tck; ++j) {
|
||||
if (advance_one_core_cycle()) {
|
||||
stop_due_to_core = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stop_due_to_core)
|
||||
break;
|
||||
}
|
||||
if (stop_due_to_core) {
|
||||
// Simulation requested stop (timeout/exit); caller will handle.
|
||||
core_stop = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CMD_SCAN_CHAIN:
|
||||
case CMD_SCAN_CHAIN_FLIP_TMS: {
|
||||
if (length > (uint32_t)VPI_XFERT_MAX_SIZE || nb_bits > (uint32_t)VPI_XFERT_MAX_SIZE * 8 || length * 8 < nb_bits) {
|
||||
fprintf(stderr, "jtag_vpi: invalid scan length=%u nb_bits=%u\n", length, nb_bits);
|
||||
got_exit_cmd = true;
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t resp[VPI_PKT_SIZE];
|
||||
memset(resp, 0, sizeof(resp));
|
||||
store_le32(resp + OFF_CMD, cmd);
|
||||
store_le32(resp + OFF_LEN, length);
|
||||
store_le32(resp + OFF_NB_BITS, nb_bits);
|
||||
|
||||
uint8_t *buf_in = resp + OFF_IN;
|
||||
for (uint32_t i = 0; i < nb_bits; ++i) {
|
||||
const bool tdi = get_bit_lsb0(buf_out, i);
|
||||
const bool tms = (cmd == CMD_SCAN_CHAIN_FLIP_TMS) && (i + 1 == nb_bits);
|
||||
const bool tdo = jtag_clock(tb, tms, tdi);
|
||||
set_bit_lsb0(buf_in, i, tdo);
|
||||
}
|
||||
|
||||
if (!send_all(sock_fd, resp, sizeof(resp))) {
|
||||
fprintf(stderr, "jtag_vpi send failed: %s\n", strerror(errno));
|
||||
got_exit_cmd = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CMD_STOP_SIMU:
|
||||
printf("OpenOCD requested stop simulation\n");
|
||||
got_exit_cmd = true;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "jtag_vpi: unknown cmd=%u\n", cmd);
|
||||
got_exit_cmd = true;
|
||||
break;
|
||||
}
|
||||
|
||||
vpi_rx_count = 0;
|
||||
if (core_stop)
|
||||
return false;
|
||||
return got_exit_cmd;
|
||||
}
|
||||
|
||||
// If JTAG is enabled, we run the simulator in lockstep with the remote
|
||||
// bitbang commands, to get more consistent simulation traces. This slows
|
||||
// down simulation quite a bit compared with normal free-running.
|
||||
//
|
||||
// Most bitbang commands complete in one cycle (e.g. TCK/TMS/TDI writes)
|
||||
// but reads take 0 cycles, step=false.
|
||||
bool got_exit_cmd = false;
|
||||
bool step = false;
|
||||
while (!step) {
|
||||
if (rx_remaining > 0) {
|
||||
char c = rxbuf[rx_ptr++];
|
||||
--rx_remaining;
|
||||
|
||||
if (c == 'r' || c == 's') {
|
||||
tb.set_trst_n(true);
|
||||
step = true;
|
||||
} else if (c == 't' || c == 'u') {
|
||||
tb.set_trst_n(false);
|
||||
} else if (c >= '0' && c <= '7') {
|
||||
int mask = c - '0';
|
||||
tb.set_tck(mask & 0x4);
|
||||
tb.set_tms(mask & 0x2);
|
||||
tb.set_tdi(mask & 0x1);
|
||||
step = true;
|
||||
} else if (c == 'R') {
|
||||
if (!args.replay_jtag) {
|
||||
txbuf[tx_ptr++] = tb.get_tdo() ? '1' : '0';
|
||||
if (tx_ptr >= TCP_BUF_SIZE || rx_remaining == 0) {
|
||||
send(sock_fd, txbuf, tx_ptr, 0);
|
||||
tx_ptr = 0;
|
||||
}
|
||||
}
|
||||
} else if (c == 'Q') {
|
||||
printf("OpenOCD sent quit command\n");
|
||||
got_exit_cmd = true;
|
||||
step = true;
|
||||
}
|
||||
} else {
|
||||
// Potentially the last command was not a read command, but
|
||||
// OpenOCD is still waiting for a last response from its
|
||||
// last command packet before it sends us any more, so now is
|
||||
// the time to flush TX.
|
||||
if (tx_ptr > 0) {
|
||||
if (!args.replay_jtag)
|
||||
send(sock_fd, txbuf, tx_ptr, 0);
|
||||
tx_ptr = 0;
|
||||
}
|
||||
rx_ptr = 0;
|
||||
if (args.replay_jtag) {
|
||||
rx_remaining = jtag_replay_fd.readsome(rxbuf, TCP_BUF_SIZE);
|
||||
} else {
|
||||
rx_remaining = read(sock_fd, &rxbuf, TCP_BUF_SIZE);
|
||||
}
|
||||
if (args.dump_jtag && rx_remaining > 0) {
|
||||
jtag_dump_fd.write(rxbuf, rx_remaining);
|
||||
}
|
||||
if (rx_remaining == 0) {
|
||||
if (args.port == 0) {
|
||||
// Presumably EOF, so quit.
|
||||
got_exit_cmd = true;
|
||||
} else {
|
||||
// The socket is closed. Wait for another connection.
|
||||
sock_fd = wait_for_connection_bitbang(server_fd, args.port, (struct sockaddr *)&sock_addr, &sock_addr_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return got_exit_cmd;
|
||||
}
|
||||
|
||||
void tb_jtag_state::close() {
|
||||
if (sock_fd >= 0)
|
||||
::close(sock_fd);
|
||||
if (server_fd >= 0)
|
||||
::close(server_fd);
|
||||
if (args.dump_jtag) {
|
||||
jtag_dump_fd.close();
|
||||
}
|
||||
if (args.replay_jtag) {
|
||||
jtag_replay_fd.close();
|
||||
}
|
||||
}
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
#include "tb.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
mem_io_state::mem_io_state(const tb_cli_args &args) : uart(args) {
|
||||
mtime = 0;
|
||||
mtimecmp[0] = 0;
|
||||
mtimecmp[1] = 0;
|
||||
exit_req = false;
|
||||
exit_code = 0;
|
||||
monitor_enabled = false;
|
||||
soft_irq_state = 0;
|
||||
irq_state = 0;
|
||||
for (int i = 0; i < N_RESERVATIONS; ++i) {
|
||||
reservation_valid[i] = false;
|
||||
reservation_addr[i] = 0;
|
||||
}
|
||||
poison_addr = -4u;
|
||||
mem = new uint8_t[MEM_SIZE];
|
||||
for (size_t i = 0; i < MEM_SIZE; ++i)
|
||||
mem[i] = 0;
|
||||
|
||||
if (args.load_bin) {
|
||||
std::ifstream fd(args.bin_path, std::ios::binary | std::ios::ate);
|
||||
if (!fd){
|
||||
std::cerr << "Failed to open \"" << args.bin_path << "\"\n";
|
||||
exit(-1);
|
||||
}
|
||||
std::streamsize bin_size = fd.tellg();
|
||||
if (bin_size > MEM_SIZE) {
|
||||
std::cerr << "Binary file (" << bin_size << " bytes) is larger than memory (" << MEM_SIZE << " bytes)\n";
|
||||
exit(-1);
|
||||
}
|
||||
fd.seekg(0, std::ios::beg);
|
||||
fd.read((char*)mem, bin_size);
|
||||
}
|
||||
}
|
||||
|
||||
bus_response tb_mem_access(tb_top &tb, mem_io_state &memio, bus_request req) {
|
||||
bus_response resp;
|
||||
|
||||
// Global monitor. When monitor is not enabled, HEXOKAY is tied high
|
||||
if (memio.monitor_enabled) {
|
||||
if (req.excl) {
|
||||
// Always set reservation on read. Always clear reservation on
|
||||
// write. On successful write, clear others' matching reservations.
|
||||
if (req.write) {
|
||||
resp.exokay = memio.reservation_valid[req.reservation_id] &&
|
||||
memio.reservation_addr[req.reservation_id] == (req.addr & RESERVATION_ADDR_MASK);
|
||||
memio.reservation_valid[req.reservation_id] = false;
|
||||
if (resp.exokay) {
|
||||
for (int i = 0; i < N_RESERVATIONS; ++i) {
|
||||
if (i == req.reservation_id)
|
||||
continue;
|
||||
if (memio.reservation_addr[i] == (req.addr & RESERVATION_ADDR_MASK))
|
||||
memio.reservation_valid[i] = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resp.exokay = true;
|
||||
memio.reservation_valid[req.reservation_id] = true;
|
||||
memio.reservation_addr[req.reservation_id] = req.addr & RESERVATION_ADDR_MASK;
|
||||
}
|
||||
} else {
|
||||
resp.exokay = false;
|
||||
// Non-exclusive write still clears others' reservations
|
||||
if (req.write) {
|
||||
for (int i = 0; i < N_RESERVATIONS; ++i) {
|
||||
if (i == req.reservation_id)
|
||||
continue;
|
||||
if (memio.reservation_addr[i] == (req.addr & RESERVATION_ADDR_MASK))
|
||||
memio.reservation_valid[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (req.write) {
|
||||
if (memio.monitor_enabled && req.excl && !resp.exokay) {
|
||||
// Failed exclusive write; do nothing
|
||||
} else if ((req.addr & -4u) == memio.poison_addr) {
|
||||
resp.err = true;
|
||||
} else if (req.addr >= MEM_BASE && req.addr <= MEM_BASE + MEM_SIZE - (1u << (int)req.size)) {
|
||||
unsigned int n_bytes = 1u << (int)req.size;
|
||||
// Note we are relying on hazard3's byte lane replication
|
||||
for (unsigned int i = 0; i < n_bytes; ++i) {
|
||||
memio.mem[req.addr + i - MEM_BASE] = req.wdata >> (8 * i) & 0xffu;
|
||||
}
|
||||
} else if (req.addr == IO_BASE + IO_PRINT_CHAR) {
|
||||
const uint8_t ch = (uint8_t)(req.wdata & 0xffu);
|
||||
fprintf(tb.logfile, "%c", (char)ch);
|
||||
memio.uart.write_data(0, ch);
|
||||
} else if (req.addr == IO_BASE + IO_PRINT_U32) {
|
||||
fprintf(tb.logfile, "%08x\n", req.wdata);
|
||||
} else if (req.addr == IO_BASE + IO_EXIT) {
|
||||
if (!memio.exit_req) {
|
||||
memio.exit_req = true;
|
||||
memio.exit_code = req.wdata;
|
||||
}
|
||||
} else if (req.addr == IO_BASE + IO_SET_SOFTIRQ) {
|
||||
memio.soft_irq_state |= req.wdata;
|
||||
tb.set_soft_irq(memio.soft_irq_state);
|
||||
} else if (req.addr == IO_BASE + IO_CLR_SOFTIRQ) {
|
||||
memio.soft_irq_state &= ~req.wdata;
|
||||
tb.set_soft_irq(memio.soft_irq_state);
|
||||
} else if (req.addr == IO_BASE + IO_GLOBMON_EN) {
|
||||
memio.monitor_enabled = req.wdata;
|
||||
} else if (req.addr == IO_BASE + IO_POISON_ADDR) {
|
||||
memio.poison_addr = req.wdata & -4u;
|
||||
} else if (req.addr == IO_BASE + IO_SET_IRQ) {
|
||||
memio.irq_state |= req.wdata;
|
||||
tb.set_irq(memio.irq_state);
|
||||
} else if (req.addr == IO_BASE + IO_CLR_IRQ) {
|
||||
memio.irq_state &= ~req.wdata;
|
||||
tb.set_irq(memio.irq_state);
|
||||
} else if (req.addr == IO_BASE + IO_MTIME) {
|
||||
memio.mtime = (memio.mtime & 0xffffffff00000000u) | req.wdata;
|
||||
} else if (req.addr == IO_BASE + IO_MTIMEH) {
|
||||
memio.mtime = (memio.mtime & 0x00000000ffffffffu) | ((uint64_t)req.wdata << 32);
|
||||
} else if (req.addr == IO_BASE + IO_MTIMECMP0) {
|
||||
memio.mtimecmp[0] = (memio.mtimecmp[0] & 0xffffffff00000000u) | req.wdata;
|
||||
} else if (req.addr == IO_BASE + IO_MTIMECMP0H) {
|
||||
memio.mtimecmp[0] = (memio.mtimecmp[0] & 0x00000000ffffffffu) | ((uint64_t)req.wdata << 32);
|
||||
} else if (req.addr == IO_BASE + IO_MTIMECMP1) {
|
||||
memio.mtimecmp[1] = (memio.mtimecmp[1] & 0xffffffff00000000u) | req.wdata;
|
||||
} else if (req.addr == IO_BASE + IO_MTIMECMP1H) {
|
||||
memio.mtimecmp[1] = (memio.mtimecmp[1] & 0x00000000ffffffffu) | ((uint64_t)req.wdata << 32);
|
||||
} else if (req.addr >= IO_BASE + IO_UART_BASE && req.addr < IO_BASE + IO_UART_BASE + IO_UART_N * IO_UART_STRIDE) {
|
||||
const uint32_t rel = req.addr - (IO_BASE + IO_UART_BASE);
|
||||
const uint32_t uart_idx = rel / IO_UART_STRIDE;
|
||||
const uint32_t reg_off = rel % IO_UART_STRIDE;
|
||||
if (reg_off == IO_UART_DATA) {
|
||||
const uint8_t ch = (uint8_t)(req.wdata & 0xffu);
|
||||
if (uart_idx == 0)
|
||||
fprintf(tb.logfile, "%c", (char)ch);
|
||||
memio.uart.write_data(uart_idx, ch);
|
||||
} else if (reg_off == IO_UART_CTRL) {
|
||||
memio.uart.write_ctrl(uart_idx, req.wdata);
|
||||
} else {
|
||||
resp.err = true;
|
||||
}
|
||||
} else {
|
||||
resp.err = true;
|
||||
}
|
||||
} else {
|
||||
if (req.addr == (memio.poison_addr & -4u)) {
|
||||
resp.err = true;
|
||||
} else if (req.addr >= MEM_BASE && req.addr <= MEM_BASE + MEM_SIZE - (1u << (int)req.size)) {
|
||||
req.addr &= ~0x3u;
|
||||
req.addr -= MEM_BASE;
|
||||
resp.rdata =
|
||||
(uint32_t)memio.mem[req.addr] |
|
||||
memio.mem[req.addr + 1] << 8 |
|
||||
memio.mem[req.addr + 2] << 16 |
|
||||
memio.mem[req.addr + 3] << 24;
|
||||
} else if (req.addr >= IO_BASE + IO_UART_BASE && req.addr < IO_BASE + IO_UART_BASE + IO_UART_N * IO_UART_STRIDE) {
|
||||
const uint32_t rel = req.addr - (IO_BASE + IO_UART_BASE);
|
||||
const uint32_t uart_idx = rel / IO_UART_STRIDE;
|
||||
const uint32_t reg_off = rel % IO_UART_STRIDE;
|
||||
if (reg_off == IO_UART_STATUS) {
|
||||
resp.rdata = memio.uart.read_status(uart_idx);
|
||||
} else if (reg_off == IO_UART_DATA) {
|
||||
resp.rdata = memio.uart.read_data(uart_idx);
|
||||
} else {
|
||||
resp.err = true;
|
||||
}
|
||||
} else if (req.addr == IO_BASE + IO_SET_SOFTIRQ || req.addr == IO_BASE + IO_CLR_SOFTIRQ) {
|
||||
resp.rdata = memio.soft_irq_state;
|
||||
} else if (req.addr == IO_BASE + IO_SET_IRQ || req.addr == IO_BASE + IO_CLR_IRQ) {
|
||||
resp.rdata = memio.irq_state;
|
||||
} else if (req.addr == IO_BASE + IO_MTIME) {
|
||||
resp.rdata = memio.mtime;
|
||||
} else if (req.addr == IO_BASE + IO_MTIMEH) {
|
||||
resp.rdata = memio.mtime >> 32;
|
||||
} else if (req.addr == IO_BASE + IO_MTIMECMP0) {
|
||||
resp.rdata = memio.mtimecmp[0];
|
||||
} else if (req.addr == IO_BASE + IO_MTIMECMP0H) {
|
||||
resp.rdata = memio.mtimecmp[0] >> 32;
|
||||
} else if (req.addr == IO_BASE + IO_MTIMECMP1) {
|
||||
resp.rdata = memio.mtimecmp[1];
|
||||
} else if (req.addr == IO_BASE + IO_MTIMECMP1H) {
|
||||
resp.rdata = memio.mtimecmp[1] >> 32;
|
||||
} else {
|
||||
resp.err = true;
|
||||
}
|
||||
}
|
||||
if (resp.err) {
|
||||
resp.exokay = false;
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
void mem_io_state::step(tb_top &tb) {
|
||||
// Default update logic for mtime, mtimecmp
|
||||
++mtime;
|
||||
tb.set_timer_irq((uint8_t)((mtime >= mtimecmp[0]) | (mtime >= mtimecmp[1]) << 1));
|
||||
uart.step();
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
#include "tb.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// TB pseudorandom number generator, using xoroshiro256++ -- original
|
||||
// copyright notice follows.
|
||||
|
||||
/* Written in 2019 by David Blackman and Sebastiano Vigna (vigna@acm.org)
|
||||
|
||||
To the extent possible under law, the author has dedicated all copyright
|
||||
and related and neighboring rights to this software to the public domain
|
||||
worldwide.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
|
||||
|
||||
/* This is xoshiro256++ 1.0, one of our all-purpose, rock-solid generators.
|
||||
It has excellent (sub-ns) speed, a state (256 bits) that is large
|
||||
enough for any parallel application, and it passes all tests we are
|
||||
aware of.
|
||||
|
||||
For generating just floating-point numbers, xoshiro256+ is even faster.
|
||||
|
||||
The state must be seeded so that it is not everywhere zero. If you have
|
||||
a 64-bit seed, we suggest to seed a splitmix64 generator and use its
|
||||
output to fill s. */
|
||||
|
||||
static inline uint64_t rotl(const uint64_t x, int k) {
|
||||
return (x << k) | (x >> (64 - k));
|
||||
}
|
||||
|
||||
uint32_t tb_top::rand(void) {
|
||||
const uint64_t result = rotl(rand_state[0] + rand_state[3], 23) + rand_state[0];
|
||||
|
||||
const uint64_t t = rand_state[1] << 17;
|
||||
|
||||
rand_state[2] ^= rand_state[0];
|
||||
rand_state[3] ^= rand_state[1];
|
||||
rand_state[1] ^= rand_state[2];
|
||||
rand_state[0] ^= rand_state[3];
|
||||
|
||||
rand_state[2] ^= t;
|
||||
|
||||
rand_state[3] = rotl(rand_state[3], 45);
|
||||
|
||||
return result >> 32;
|
||||
}
|
||||
|
||||
void tb_top::seed_rand(const uint8_t *data, size_t len) {
|
||||
// Initial state must not be all-zeroes
|
||||
for (unsigned int i = 0; i < 4; ++i) {
|
||||
rand_state[i] = 0xf005ba11u + i;
|
||||
}
|
||||
// Pour + stir method: XOR data in one bit at a time, with a xoroshiro
|
||||
// permutation between each.
|
||||
for (size_t i = 0; i < 8u * len; ++i) {
|
||||
if (data[i / 8u] & (1u << (i % 8u))) {
|
||||
rand_state[0] ^= 1u;
|
||||
}
|
||||
(void)rand();
|
||||
}
|
||||
}
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
#include "tb_uart.h"
|
||||
|
||||
#include "tb_cli.h"
|
||||
#include "tb_constants.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace {
|
||||
|
||||
void close_fd(int &fd) {
|
||||
if (fd < 0)
|
||||
return;
|
||||
(void)::close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
|
||||
bool set_nonblocking(int fd) {
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags < 0)
|
||||
return false;
|
||||
return fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0;
|
||||
}
|
||||
|
||||
int send_flags() {
|
||||
int flags = 0;
|
||||
#ifdef MSG_NOSIGNAL
|
||||
flags |= MSG_NOSIGNAL;
|
||||
#endif
|
||||
return flags;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
tb_uart_state::tb_uart_state(const tb_cli_args &args) {
|
||||
uarts[0].init(args.uart0_port);
|
||||
uarts[1].init(args.uart1_port);
|
||||
if (args.uart0_port != 0)
|
||||
std::cout << "UART0 listening on port " << args.uart0_port << "\n";
|
||||
if (args.uart1_port != 0)
|
||||
std::cout << "UART1 listening on port " << args.uart1_port << "\n";
|
||||
}
|
||||
|
||||
tb_uart_state::~tb_uart_state() {
|
||||
for (uint32_t i = 0; i < N_UARTS; ++i)
|
||||
uarts[i].close();
|
||||
}
|
||||
|
||||
void tb_uart_state::uart::init(uint16_t port_) {
|
||||
port = port_;
|
||||
if (port == 0)
|
||||
return;
|
||||
|
||||
server_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (server_fd < 0) {
|
||||
std::cerr << "UART socket creation failed: " << strerror(errno) << "\n";
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
int opt = 1;
|
||||
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
|
||||
std::cerr << "UART setsockopt(SO_REUSEADDR) failed: " << strerror(errno) << "\n";
|
||||
exit(-1);
|
||||
}
|
||||
#ifdef SO_REUSEPORT
|
||||
// Best-effort: can fail on some kernels/configs, but is not required.
|
||||
(void)setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
|
||||
#endif
|
||||
|
||||
bind_addr.sin_family = AF_INET;
|
||||
bind_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
bind_addr.sin_port = htons(port);
|
||||
if (bind(server_fd, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) < 0) {
|
||||
std::cerr << "UART bind failed: " << strerror(errno) << "\n";
|
||||
exit(-1);
|
||||
}
|
||||
if (listen(server_fd, 1) < 0) {
|
||||
std::cerr << "UART listen failed: " << strerror(errno) << "\n";
|
||||
exit(-1);
|
||||
}
|
||||
if (!set_nonblocking(server_fd)) {
|
||||
std::cerr << "UART fcntl(O_NONBLOCK) failed: " << strerror(errno) << "\n";
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void tb_uart_state::uart::close() {
|
||||
close_fd(client_fd);
|
||||
close_fd(server_fd);
|
||||
port = 0;
|
||||
overrun = false;
|
||||
rx_fifo.clear();
|
||||
tx_fifo.clear();
|
||||
}
|
||||
|
||||
void tb_uart_state::uart::poll_accept(uint32_t uart_idx) {
|
||||
if (server_fd < 0 || client_fd >= 0)
|
||||
return;
|
||||
|
||||
sockaddr_in peer {};
|
||||
socklen_t peer_len = sizeof(peer);
|
||||
int fd = accept(server_fd, (struct sockaddr *)&peer, &peer_len);
|
||||
if (fd < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
|
||||
return;
|
||||
std::cerr << "UART" << uart_idx << " accept failed: " << strerror(errno) << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
client_fd = fd;
|
||||
(void)set_nonblocking(client_fd);
|
||||
|
||||
// Low-latency local socket traffic helps interactivity.
|
||||
int flag = 1;
|
||||
setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag));
|
||||
#ifdef SO_NOSIGPIPE
|
||||
// Best-effort: avoid SIGPIPE on some platforms.
|
||||
(void)setsockopt(client_fd, SOL_SOCKET, SO_NOSIGPIPE, &flag, sizeof(flag));
|
||||
#endif
|
||||
|
||||
std::cout << "UART" << uart_idx << " connected\n";
|
||||
}
|
||||
|
||||
void tb_uart_state::uart::poll_rx(uint32_t uart_idx) {
|
||||
poll_accept(uart_idx);
|
||||
if (client_fd < 0)
|
||||
return;
|
||||
|
||||
uint8_t buf[1024];
|
||||
while (true) {
|
||||
ssize_t n = recv(client_fd, buf, sizeof(buf), MSG_DONTWAIT);
|
||||
if (n > 0) {
|
||||
for (ssize_t i = 0; i < n; ++i) {
|
||||
if (rx_fifo.size() >= rx_capacity) {
|
||||
overrun = true;
|
||||
continue;
|
||||
}
|
||||
rx_fifo.push_back(buf[i]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (n == 0) {
|
||||
close_fd(client_fd);
|
||||
std::cout << "UART" << uart_idx << " disconnected\n";
|
||||
return;
|
||||
}
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
|
||||
return;
|
||||
std::cerr << "UART" << uart_idx << " recv failed: " << strerror(errno) << "\n";
|
||||
close_fd(client_fd);
|
||||
std::cout << "UART" << uart_idx << " disconnected\n";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void tb_uart_state::uart::poll_tx(uint32_t uart_idx) {
|
||||
poll_accept(uart_idx);
|
||||
if (client_fd < 0 || tx_fifo.empty())
|
||||
return;
|
||||
|
||||
uint8_t buf[1024];
|
||||
while (!tx_fifo.empty()) {
|
||||
const size_t chunk = std::min(tx_fifo.size(), sizeof(buf));
|
||||
for (size_t i = 0; i < chunk; ++i)
|
||||
buf[i] = tx_fifo[i];
|
||||
|
||||
ssize_t n = send(client_fd, buf, chunk, send_flags());
|
||||
if (n > 0) {
|
||||
for (ssize_t i = 0; i < n; ++i)
|
||||
tx_fifo.pop_front();
|
||||
continue;
|
||||
}
|
||||
if (n == 0)
|
||||
return;
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
|
||||
return;
|
||||
std::cerr << "UART" << uart_idx << " send failed: " << strerror(errno) << "\n";
|
||||
close_fd(client_fd);
|
||||
std::cout << "UART" << uart_idx << " disconnected\n";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void tb_uart_state::step() {
|
||||
for (uint32_t i = 0; i < N_UARTS; ++i) {
|
||||
if (!uarts[i].tx_fifo.empty())
|
||||
uarts[i].poll_tx(i);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t tb_uart_state::read_status(uint32_t uart_idx) {
|
||||
if (uart_idx >= N_UARTS)
|
||||
return 0;
|
||||
uart &u = uarts[uart_idx];
|
||||
u.poll_rx(uart_idx);
|
||||
u.poll_tx(uart_idx);
|
||||
uint32_t status = TB_UART_STATUS_TX_READY;
|
||||
if (!u.rx_fifo.empty())
|
||||
status |= TB_UART_STATUS_RX_AVAIL;
|
||||
if (u.connected())
|
||||
status |= TB_UART_STATUS_CONNECTED;
|
||||
if (u.overrun)
|
||||
status |= TB_UART_STATUS_OVERRUN;
|
||||
return status;
|
||||
}
|
||||
|
||||
uint32_t tb_uart_state::read_data(uint32_t uart_idx) {
|
||||
if (uart_idx >= N_UARTS)
|
||||
return 0xffffffffu;
|
||||
uart &u = uarts[uart_idx];
|
||||
u.poll_rx(uart_idx);
|
||||
if (u.rx_fifo.empty())
|
||||
return 0xffffffffu;
|
||||
const uint8_t byte = u.rx_fifo.front();
|
||||
u.rx_fifo.pop_front();
|
||||
return byte;
|
||||
}
|
||||
|
||||
void tb_uart_state::write_data(uint32_t uart_idx, uint8_t byte) {
|
||||
if (uart_idx >= N_UARTS)
|
||||
return;
|
||||
uart &u = uarts[uart_idx];
|
||||
if (u.server_fd < 0)
|
||||
return;
|
||||
if (u.tx_fifo.size() >= u.tx_capacity && !u.tx_fifo.empty())
|
||||
u.tx_fifo.pop_front();
|
||||
u.tx_fifo.push_back(byte);
|
||||
u.poll_tx(uart_idx);
|
||||
}
|
||||
|
||||
void tb_uart_state::write_ctrl(uint32_t uart_idx, uint32_t value) {
|
||||
if (uart_idx >= N_UARTS)
|
||||
return;
|
||||
uart &u = uarts[uart_idx];
|
||||
if (value & TB_UART_CTRL_CLR_OVERRUN)
|
||||
u.overrun = false;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
build-*
|
||||
tb
|
||||
tb-*
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
include ../project_paths.mk
|
||||
|
||||
TOP := tb
|
||||
DOTF := tb.f
|
||||
CONFIG := default
|
||||
TBEXEC := $(patsubst %.f,%,$(DOTF))
|
||||
|
||||
ifneq ($(CONFIG),default)
|
||||
TBEXEC := $(TBEXEC)-$(CONFIG)
|
||||
endif
|
||||
|
||||
FILE_LIST := $(shell HDL=$(HDL) $(SCRIPTS)/listfiles ../tb_common/hdl/$(DOTF))
|
||||
VINCDIR := $(shell HDL=$(HDL) $(SCRIPTS)/listfiles -f flati ../tb_common/hdl/$(DOTF))
|
||||
BUILD_DIR := build-$(patsubst %.f,%,$(DOTF))
|
||||
VOBJ_DIR := $(BUILD_DIR)/obj_dir
|
||||
VLIB := $(VOBJ_DIR)/V$(TOP)__ALL.a
|
||||
VERILATED_OBJS := $(VOBJ_DIR)/verilated.o $(VOBJ_DIR)/verilated_threads.o
|
||||
|
||||
VERILATOR_ROOT := $(shell verilator --getenv VERILATOR_ROOT)
|
||||
CXX := g++
|
||||
VERILATOR := verilator
|
||||
|
||||
TB_CFILES := tb.cpp $(wildcard ../tb_common/*.cpp)
|
||||
CINCLUDE := $(VOBJ_DIR) $(VERILATOR_ROOT)/include ../tb_common/include
|
||||
|
||||
.PHONY: clean all lint vcc vlib
|
||||
|
||||
all: $(TBEXEC)
|
||||
vcc: $(BUILD_DIR)/vcc.touch
|
||||
vlib: $(BUILD_DIR)/vlib.touch
|
||||
|
||||
$(BUILD_DIR)/vcc.touch: $(FILE_LIST) $(wildcard *.vh)
|
||||
mkdir -p $(VOBJ_DIR)
|
||||
$(VERILATOR) --Mdir $(VOBJ_DIR) --cc --top-module $(TOP) $(addprefix -I,$(VINCDIR)) -DCONFIG_HEADER="\"config_$(CONFIG).vh\"" $(FILE_LIST)
|
||||
touch $@
|
||||
|
||||
$(BUILD_DIR)/vlib.touch: $(BUILD_DIR)/vcc.touch
|
||||
# Verilator's archive (Vtb__ALL.a) does not include the run-time objects
|
||||
# (verilated.o/verilated_threads.o). Build them explicitly so the final
|
||||
# tb link step succeeds across Verilator versions/distros.
|
||||
$(MAKE) VERILATOR_ROOT=$(VERILATOR_ROOT) -C $(VOBJ_DIR) -f V$(TOP).mk \
|
||||
V$(TOP)__ALL.a verilated.o verilated_threads.o
|
||||
touch $@
|
||||
|
||||
clean::
|
||||
rm -rf $(BUILD_DIR) $(TBEXEC)
|
||||
|
||||
$(TBEXEC): $(BUILD_DIR)/vlib.touch $(TB_CFILES)
|
||||
$(CXX) -O3 -std=c++14 $(addprefix -D,$(CDEFINES) $(CDEFINES_$(DOTF))) \
|
||||
$(addprefix -I ,$(CINCLUDE)) $(TB_CFILES) $(VLIB) $(VERILATED_OBJS) -o $(TBEXEC) -pthread -latomic
|
||||
|
||||
lint:
|
||||
$(VERILATOR) --lint-only --top-module $(TOP) $(addprefix -I,$(VINCDIR)) -DCONFIG_HEADER="\"config_$(CONFIG).vh\"" $(FILE_LIST)
|
||||
@@ -0,0 +1,13 @@
|
||||
adapter driver jtag_vpi
|
||||
jtag_vpi set_address 127.0.0.1
|
||||
jtag_vpi set_port 5555
|
||||
transport select jtag
|
||||
|
||||
set _CHIPNAME hazard3
|
||||
jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0xdeadbeef
|
||||
set _TARGETNAME $_CHIPNAME.cpu
|
||||
target create $_TARGETNAME riscv -chain-position $_TARGETNAME
|
||||
|
||||
gdb report_data_abort enable
|
||||
init
|
||||
halt
|
||||
+438
@@ -0,0 +1,438 @@
|
||||
#include "Vtb.h"
|
||||
#include "Vtb___024root.h"
|
||||
#include "verilated.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <cstring>
|
||||
|
||||
#include "tb.h"
|
||||
#include "tb_cli.h"
|
||||
#include "tb_gdb.h"
|
||||
#include "tb_jtag.h"
|
||||
|
||||
class tb_verilator_top: public tb_top {
|
||||
VerilatedContext *contextp;
|
||||
Vtb *top;
|
||||
|
||||
// Loop-carried address-phase requests
|
||||
bus_request req_i;
|
||||
bus_request req_d;
|
||||
bool req_i_vld = false;
|
||||
bool req_d_vld = false;
|
||||
|
||||
public:
|
||||
tb_verilator_top(const tb_cli_args &parsed_args, int argc, char **argv);
|
||||
~tb_verilator_top() {delete top; delete contextp;}
|
||||
|
||||
void step(const tb_cli_args &args, mem_io_state &memio) override;
|
||||
void eval() override {top->eval();}
|
||||
|
||||
void set_trst_n(bool trst_n) override {top->trst_n = trst_n;}
|
||||
void set_tck(bool tck) override {top->tck = tck;}
|
||||
void set_tdi(bool tdi) override {top->tdi = tdi;}
|
||||
void set_tms(bool tms) override {top->tms = tms;}
|
||||
bool get_tdo() override {return top->tdo;}
|
||||
void set_irq(uint32_t mask) override {top->irq = mask;}
|
||||
void set_soft_irq(uint8_t mask) override {top->soft_irq = mask;}
|
||||
void set_timer_irq(uint8_t mask) override {top->timer_irq = mask;}
|
||||
|
||||
Vtb___024root *rootp() { return top->rootp; }
|
||||
|
||||
void reset_dut(const tb_cli_args &args, mem_io_state &memio, uint32_t assert_cycles = 8, uint32_t release_cycles = 0);
|
||||
|
||||
};
|
||||
|
||||
tb_verilator_top::tb_verilator_top(const tb_cli_args &parsed_args, int argc, char **argv): tb_top {parsed_args} {
|
||||
contextp = new VerilatedContext;
|
||||
// Verilated context also gets a chance to parse the arguments. Any
|
||||
// argument prefixed with "+verilator+" is ignored by our tb arg parsing.
|
||||
contextp->commandArgs(argc, argv);
|
||||
top = new Vtb{contextp};
|
||||
|
||||
req_i_vld = false;
|
||||
req_d_vld = false;
|
||||
req_i.reservation_id = 0;
|
||||
req_d.reservation_id = 1;
|
||||
|
||||
// Set bus interfaces to generate good IDLE responses at first
|
||||
top->i_hready = true;
|
||||
top->d_hready = true;
|
||||
|
||||
// Reset + initial clock pulse
|
||||
top->eval();
|
||||
top->clk = true;
|
||||
top->tck = true;
|
||||
top->eval();
|
||||
top->clk = false;
|
||||
top->tck = false;
|
||||
top->trst_n = true;
|
||||
top->rst_n = true;
|
||||
top->eval();
|
||||
}
|
||||
|
||||
void tb_verilator_top::reset_dut(const tb_cli_args &args, mem_io_state &memio, uint32_t assert_cycles, uint32_t release_cycles) {
|
||||
// Clear any loop-carried bus phase state.
|
||||
req_i_vld = false;
|
||||
req_d_vld = false;
|
||||
|
||||
// Ensure a clean default handshake state at reset release.
|
||||
top->i_hready = true;
|
||||
top->d_hready = true;
|
||||
|
||||
// Assert global reset (and JTAG TAP reset) for a few core cycles.
|
||||
top->trst_n = false;
|
||||
top->rst_n = false;
|
||||
for (uint32_t i = 0; i < assert_cycles; ++i)
|
||||
step(args, memio);
|
||||
|
||||
// Release reset. By default, we don't advance any cycles after release so
|
||||
// the CPU is effectively "reset+halted" until the debugger resumes.
|
||||
top->trst_n = true;
|
||||
top->rst_n = true;
|
||||
for (uint32_t i = 0; i < release_cycles; ++i)
|
||||
step(args, memio);
|
||||
}
|
||||
|
||||
void tb_verilator_top::step(const tb_cli_args &args, mem_io_state &memio) {
|
||||
top->clk = false;
|
||||
top->eval();
|
||||
top->clk = true;
|
||||
top->eval();
|
||||
|
||||
// The two bus ports are handled identically. This enables swapping out of
|
||||
// various `tb.v` hardware integration files containing:
|
||||
//
|
||||
// - A single, dual-ported processor (instruction fetch, load/store ports)
|
||||
// - A single, single-ported processor (instruction fetch + load/store muxed internally)
|
||||
// - A pair of single-ported processors, for dual-core debug tests
|
||||
|
||||
if (top->d_hready) {
|
||||
// Clear bus error by default
|
||||
top->d_hresp = false;
|
||||
|
||||
// Handle current data phase
|
||||
req_d.wdata = top->d_hwdata;
|
||||
bus_response resp;
|
||||
if (req_d_vld)
|
||||
resp = mem_callback_d(*this, memio, req_d);
|
||||
else
|
||||
resp.exokay = !memio.monitor_enabled;
|
||||
if (resp.err) {
|
||||
// Phase 1 of error response
|
||||
top->d_hready = false;
|
||||
top->d_hresp = true;
|
||||
}
|
||||
if (req_d_vld && !req_d.write) {
|
||||
top->d_hrdata = resp.rdata;
|
||||
} else {
|
||||
top->d_hrdata = rand();
|
||||
}
|
||||
top->d_hexokay = resp.exokay;
|
||||
} else {
|
||||
// hready=0. Currently this only happens when we're in the first
|
||||
// phase of an error response, so go to phase 2.
|
||||
top->d_hready = true;
|
||||
}
|
||||
|
||||
req_d_vld = false;
|
||||
if (top->d_hready) {
|
||||
// Progress current address phase to data phase
|
||||
req_d_vld = top->d_htrans >> 1;
|
||||
req_d.write = top->d_hwrite;
|
||||
req_d.size = (bus_size_t)top->d_hsize;
|
||||
req_d.addr = top->d_haddr;
|
||||
req_d.excl = top->d_hexcl;
|
||||
}
|
||||
|
||||
if (top->i_hready) {
|
||||
top->i_hresp = false;
|
||||
|
||||
req_i.wdata = top->i_hwdata;
|
||||
bus_response resp;
|
||||
if (req_i_vld)
|
||||
resp = mem_callback_i(*this, memio, req_i);
|
||||
else
|
||||
resp.exokay = !memio.monitor_enabled;
|
||||
if (resp.err) {
|
||||
// Phase 1 of error response
|
||||
top->i_hready = false;
|
||||
top->i_hresp = true;
|
||||
}
|
||||
if (req_i_vld && !req_i.write) {
|
||||
top->i_hrdata = resp.rdata;
|
||||
} else {
|
||||
top->i_hrdata = rand();
|
||||
}
|
||||
top->i_hexokay = resp.exokay;
|
||||
} else {
|
||||
// hready=0. Currently this only happens when we're in the first
|
||||
// phase of an error response, so go to phase 2.
|
||||
top->i_hready = true;
|
||||
}
|
||||
|
||||
req_i_vld = false;
|
||||
if (top->i_hready) {
|
||||
// Progress current address phase to data phase
|
||||
req_i_vld = top->i_htrans >> 1;
|
||||
req_i.write = top->i_hwrite;
|
||||
req_i.size = (bus_size_t)top->i_hsize;
|
||||
req_i.addr = top->i_haddr;
|
||||
req_i.excl = top->i_hexcl;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
tb_cli_args args;
|
||||
tb_parse_args(argc, argv, args);
|
||||
|
||||
VerilatedContext *contextp = new VerilatedContext;
|
||||
contextp->commandArgs(argc, argv);
|
||||
|
||||
tb_jtag_state jtag(args);
|
||||
mem_io_state memio(args);
|
||||
tb_verilator_top tb(args, argc, argv);
|
||||
|
||||
bool timed_out = false;
|
||||
int64_t cycle = 0;
|
||||
|
||||
if (args.gdb_port != 0) {
|
||||
struct verilator_gdb_target final : tb_gdb_target {
|
||||
tb_verilator_top &tb;
|
||||
mem_io_state &memio;
|
||||
const tb_cli_args &args;
|
||||
Vtb___024root *root;
|
||||
int64_t &cycle;
|
||||
bool &timed_out;
|
||||
|
||||
verilator_gdb_target(tb_verilator_top &tb_, mem_io_state &memio_, const tb_cli_args &args_, int64_t &cycle_, bool &timed_out_)
|
||||
: tb(tb_), memio(memio_), args(args_), root(tb_.rootp()), cycle(cycle_), timed_out(timed_out_) {}
|
||||
|
||||
uint32_t read_reg(uint32_t regno) override {
|
||||
if (regno == 0)
|
||||
return 0;
|
||||
if (regno < 32) {
|
||||
// GDB single-step stops with decode PC already advanced to the
|
||||
// next instruction, while the just-computed register result can
|
||||
// still be sitting in the M stage for one cycle before it is
|
||||
// committed into the architectural register file. Overlay the
|
||||
// pending writeback so the debugger sees a coherent post-step
|
||||
// snapshot.
|
||||
if (root->tb__DOT__cpu__DOT__core__DOT__m_reg_wen_if_nonzero &&
|
||||
regno == root->tb__DOT__cpu__DOT__core__DOT__xm_rd) {
|
||||
return (uint32_t)root->tb__DOT__cpu__DOT__core__DOT__m_result;
|
||||
}
|
||||
return (uint32_t)root->tb__DOT__cpu__DOT__core__DOT__regs__DOT__real_dualport_reset__DOT__mem[regno];
|
||||
}
|
||||
if (regno == 32) {
|
||||
return (uint32_t)root->tb__DOT__cpu__DOT__core__DOT__decode_u__DOT__pc;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void write_reg(uint32_t regno, uint32_t value) override {
|
||||
if (regno == 0)
|
||||
return;
|
||||
if (regno < 32) {
|
||||
root->tb__DOT__cpu__DOT__core__DOT__regs__DOT__real_dualport_reset__DOT__mem[regno] = value;
|
||||
tb.eval();
|
||||
return;
|
||||
}
|
||||
if (regno == 32) {
|
||||
root->tb__DOT__cpu__DOT__core__DOT__decode_u__DOT__pc = value;
|
||||
tb.eval();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool read_mem(uint32_t addr, uint8_t *dst, size_t len) override {
|
||||
if (len == 0)
|
||||
return true;
|
||||
if (addr < MEM_BASE || addr + len > MEM_BASE + MEM_SIZE)
|
||||
return false;
|
||||
memcpy(dst, memio.mem + (addr - MEM_BASE), len);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool write_mem(uint32_t addr, const uint8_t *src, size_t len) override {
|
||||
if (len == 0)
|
||||
return true;
|
||||
if (addr < MEM_BASE || addr + len > MEM_BASE + MEM_SIZE)
|
||||
return false;
|
||||
memcpy(memio.mem + (addr - MEM_BASE), src, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
tb_gdb_run_result step_instruction() override {
|
||||
while (true) {
|
||||
if (args.max_cycles != 0 && cycle >= args.max_cycles) {
|
||||
timed_out = true;
|
||||
return tb_gdb_run_result::timed_out;
|
||||
}
|
||||
|
||||
const bool will_retire = root->tb__DOT__cpu__DOT__core__DOT__x_instr_ret;
|
||||
memio.step(tb);
|
||||
tb.step(args, memio);
|
||||
++cycle;
|
||||
|
||||
if (memio.exit_req)
|
||||
return tb_gdb_run_result::exited;
|
||||
if (args.max_cycles != 0 && cycle >= args.max_cycles) {
|
||||
timed_out = true;
|
||||
return tb_gdb_run_result::timed_out;
|
||||
}
|
||||
if (will_retire)
|
||||
return tb_gdb_run_result::ok;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t exit_code() const override { return memio.exit_code; }
|
||||
|
||||
bool monitor_cmd(const std::string &cmd, std::string &out_console) override {
|
||||
auto is_space = [](unsigned char c) { return std::isspace(c) != 0; };
|
||||
|
||||
// Normalise whitespace and case to make matching user-friendly.
|
||||
size_t start = 0;
|
||||
while (start < cmd.size() && is_space((unsigned char)cmd[start]))
|
||||
++start;
|
||||
size_t end = cmd.size();
|
||||
while (end > start && is_space((unsigned char)cmd[end - 1]))
|
||||
--end;
|
||||
|
||||
std::string norm;
|
||||
norm.reserve(end - start);
|
||||
bool in_space = false;
|
||||
for (size_t i = start; i < end; ++i) {
|
||||
const unsigned char c = (unsigned char)cmd[i];
|
||||
if (is_space(c)) {
|
||||
in_space = true;
|
||||
continue;
|
||||
}
|
||||
if (in_space && !norm.empty())
|
||||
norm.push_back(' ');
|
||||
in_space = false;
|
||||
norm.push_back((char)std::tolower(c));
|
||||
}
|
||||
|
||||
if (norm == "reset halt" || norm == "reset") {
|
||||
// Clear external testbench state (but keep RAM contents).
|
||||
memio.mtime = 0;
|
||||
memio.mtimecmp[0] = 0;
|
||||
memio.mtimecmp[1] = 0;
|
||||
memio.exit_req = false;
|
||||
memio.exit_code = 0;
|
||||
memio.monitor_enabled = false;
|
||||
memio.poison_addr = -4u;
|
||||
memio.soft_irq_state = 0;
|
||||
memio.irq_state = 0;
|
||||
for (int i = 0; i < N_RESERVATIONS; ++i) {
|
||||
memio.reservation_valid[i] = false;
|
||||
memio.reservation_addr[i] = 0;
|
||||
}
|
||||
for (uint32_t i = 0; i < tb_uart_state::N_UARTS; ++i) {
|
||||
memio.uart.uarts[i].overrun = false;
|
||||
memio.uart.uarts[i].rx_fifo.clear();
|
||||
memio.uart.uarts[i].tx_fifo.clear();
|
||||
}
|
||||
tb.set_soft_irq(0);
|
||||
tb.set_timer_irq(0);
|
||||
tb.set_irq(0);
|
||||
|
||||
cycle = 0;
|
||||
timed_out = false;
|
||||
|
||||
tb.reset_dut(args, memio);
|
||||
out_console = "reset halt\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
verilator_gdb_target target(tb, memio, args, cycle, timed_out);
|
||||
while (!memio.exit_req && !timed_out) {
|
||||
tb_gdb_server gdb(args.gdb_port, target);
|
||||
const tb_gdb_run_result r = gdb.serve();
|
||||
if (r == tb_gdb_run_result::error)
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
while (args.max_cycles == 0 || cycle < args.max_cycles) {
|
||||
uint32_t core_cycles_advanced = 0;
|
||||
bool jtag_exit_cmd = jtag.step(tb, &memio, &cycle, &timed_out, &core_cycles_advanced);
|
||||
|
||||
if (memio.exit_req) {
|
||||
fprintf(tb.logfile, "CPU requested halt. Exit code %d\n", memio.exit_code);
|
||||
fprintf(tb.logfile, "Ran for " I64_FMT " cycles\n", cycle);
|
||||
break;
|
||||
}
|
||||
if (timed_out) {
|
||||
fprintf(tb.logfile, "Max cycles reached\n");
|
||||
break;
|
||||
}
|
||||
if (jtag_exit_cmd)
|
||||
break;
|
||||
|
||||
// If the JTAG handler didn't advance the core clock, free-run for one cycle.
|
||||
if (core_cycles_advanced == 0) {
|
||||
memio.step(tb);
|
||||
tb.step(args, memio);
|
||||
++cycle;
|
||||
if (memio.exit_req) {
|
||||
fprintf(tb.logfile, "CPU requested halt. Exit code %d\n", memio.exit_code);
|
||||
fprintf(tb.logfile, "Ran for " I64_FMT " cycles\n", cycle);
|
||||
break;
|
||||
}
|
||||
if (args.max_cycles != 0 && cycle >= args.max_cycles) {
|
||||
fprintf(tb.logfile, "Max cycles reached\n");
|
||||
timed_out = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jtag.close();
|
||||
|
||||
for (auto r : args.dump_ranges) {
|
||||
fprintf(tb.logfile, "Dumping memory from %08x to %08x:\n", r.first, r.second);
|
||||
for (int i = 0; i < r.second - r.first; ++i)
|
||||
fprintf(tb.logfile, "%02x%c", memio.mem[r.first + i - MEM_BASE], i % 16 == 15 ? '\n' : ' ');
|
||||
fprintf(tb.logfile, "\n");
|
||||
}
|
||||
|
||||
if (args.sig_path != "") {
|
||||
FILE *sigfile = fopen(args.sig_path.c_str(), "wb");
|
||||
for (auto r : args.dump_ranges) {
|
||||
for (uint32_t i = 0; i < r.second - r.first; i += 4) {
|
||||
fprintf(
|
||||
sigfile,
|
||||
"%02x%02x%02x%02x\n",
|
||||
memio.mem[r.first + i + 3 - MEM_BASE],
|
||||
memio.mem[r.first + i + 2 - MEM_BASE],
|
||||
memio.mem[r.first + i + 1 - MEM_BASE],
|
||||
memio.mem[r.first + i + 0 - MEM_BASE]
|
||||
);
|
||||
}
|
||||
}
|
||||
fclose(sigfile);
|
||||
}
|
||||
|
||||
if (args.propagate_return_code && timed_out) {
|
||||
return -1;
|
||||
} else if (args.propagate_return_code && memio.exit_req) {
|
||||
return memio.exit_code;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Needed on MacOS, haven't looked into why
|
||||
double sc_time_stamp() {
|
||||
return 0.0;
|
||||
}
|
||||
Reference in New Issue
Block a user