feat: publish FreeRTOS C FC02 card

This commit is contained in:
2026-07-19 16:36:02 +02:00
commit e3420748ee
208 changed files with 57240 additions and 0 deletions
+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;
}