feat: publish FreeRTOS C FC05 card

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