feat: publish FreeRTOS C FC10 card

This commit is contained in:
2026-07-19 16:36:03 +02:00
commit 2e0fdfdc7e
199 changed files with 59630 additions and 0 deletions
+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);
};