riscv-openocd

Introduce riscv openocd

Install openocd

Download openocd from https://github.com/riscv/riscv-openocd

Download jimtcl from https://github.com/msteveb/jimtcl

Copy jimtcl to riscv-openocd-riscv/jimtcl

1
2
3
4
5
> ./bootstrap
> ./configure --prefix=/home/francis.zheng/openocd/ --enable-remote-bitbang
> make
> make install
> openocd -v

--enable-remote-bitbang means drive JTAG from a remote process. This sets up a UNIX or TCP socket connection with a remote process and sends ASCII encoded bitbang requests to that process instead of directly driving JTAG.

Integrate to testbench

  1. Integrate SimJTAG.v to testbench

    https://github.com/freechipsproject/rocket-chip/blob/new_remote_bitbang/vsrc/SimJTAG.v

    Or the simpler one.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    //VCS coverage exclude_file
    import "DPI-C" function int jtag_tick
    (
    output bit jtag_TCK,
    output bit jtag_TMS,
    output bit jtag_TDI,
    output bit jtag_TRSTn,
    input bit jtag_TDO
    );

    module SimJTAG(
    input clock,
    input reset,
    output jtag_TCK,
    output jtag_TMS,
    output jtag_TDI,
    output jtag_TRSTn,
    input jtag_TDO_data,
    input jtag_TDO_driven
    );

    int TICK_DELAY = 5;
    int tick_counter;
    reg jtag_vpi_enable;
    initial begin
    if(!$value$plusargs("jtag_vpi_enable=%d",jtag_vpi_enable))
    jtag_vpi_enable = 0;
    end

    always @(posedge clock) begin
    if (reset) begin
    tick_count = TICK_DELAY;
    end else begin
    if(jtag_vpi_enable & (tick_counter == 0)) begin
    void'(jtag_tick(
    jtag_TCK,
    jtag_TMS,
    jtag_TDI,
    jtag_TRSTn,
    jtag_TDO_data
    ));
    tick_counter = TICK_DELAY;
    end
    else begin
    tick_counter = tick_counter - 1;
    end
    end
    end
    endmodule
  1. Include SimJTAG.cc , remote_bitbang.cc and remote_bitbang.hto vcs comilation

    https://github.com/freechipsproject/rocket-chip/blob/src/main/resources/csrc/SimJTAG.cc

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include <cstdlib>
    #include "remote_bitbang.h"

    remote_bitbang_t* jtag;
    extern "C" int jtag_tick
    (
    unsigned char * jtag_TCK,
    unsigned char * jtag_TMS,
    unsigned char * jtag_TDI,
    unsigned char * jtag_TRSTn,
    unsigned char jtag_TDO
    ){
    if (!jtag) {
    // TODO: Pass in real port number
    jtag = new remote_bitbang_t(0);
    }
    jtag->tick(jtag_TCK, jtag_TMS, jtag_TDI, jtag_TRSTn, jtag_TDO);
    return jtag->done() ? (jtag->exit_code() << 1 | 1) : 0;
    }

    https://github.com/freechipsproject/rocket-chip/blob/src/main/resources/csrc/remote_bitbang.cc

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    #include <arpa/inet.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <algorithm>
    #include <cassert>
    #include <cstdio>
    #include <cstdlib>
    #include "remote_bitbang.h"
    /////////// remote_bitbang_t
    remote_bitbang_t::remote_bitbang_t(uint16_t port) :
    socket_fd(0),
    client_fd(0),
    recv_start(0),
    recv_end(0),
    err(0)
    {
    socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_fd == -1) {
    fprintf(stderr, "remote_bitbang failed to make socket: %s (%d)\n",
    strerror(errno), errno);
    abort();
    }
    fcntl(socket_fd, F_SETFL, O_NONBLOCK);
    int reuseaddr = 1;
    if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr,
    sizeof(int)) == -1) {
    fprintf(stderr, "remote_bitbang failed setsockopt: %s (%d)\n",
    strerror(errno), errno);
    abort();
    }
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(port);
    if (::bind(socket_fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
    fprintf(stderr, "remote_bitbang failed to bind socket: %s (%d)\n",
    strerror(errno), errno);
    abort();
    }
    if (listen(socket_fd, 1) == -1) {
    fprintf(stderr, "remote_bitbang failed to listen on socket: %s (%d)\n",
    strerror(errno), errno);
    abort();
    }
    socklen_t addrlen = sizeof(addr);
    if (getsockname(socket_fd, (struct sockaddr *) &addr, &addrlen) == -1) {
    fprintf(stderr, "remote_bitbang getsockname failed: %s (%d)\n",
    strerror(errno), errno);
    abort();
    }
    tck = 1;
    tms = 1;
    tdi = 1;
    trstn = 1;
    quit = 0;
    fprintf(stderr, "This emulator compiled with JTAG Remote Bitbang client. To enable, use +jtag_rbb_enable=1.\n");
    fprintf(stderr, "Listening on port %d\n",
    ntohs(addr.sin_port));
    }
    void remote_bitbang_t::accept()
    {
    fprintf(stderr,"Attempting to accept client socket\n");
    int again = 1;
    while (again != 0) {
    client_fd = ::accept(socket_fd, NULL, NULL);
    if (client_fd == -1) {
    if (errno == EAGAIN) {
    // No client waiting to connect right now.
    } else {
    fprintf(stderr, "failed to accept on socket: %s (%d)\n", strerror(errno),
    errno);
    again = 0;
    abort();
    }
    } else {
    fcntl(client_fd, F_SETFL, O_NONBLOCK);
    fprintf(stderr, "Accepted successfully.");
    again = 0;
    }
    }
    }
    void remote_bitbang_t::tick(
    unsigned char * jtag_tck,
    unsigned char * jtag_tms,
    unsigned char * jtag_tdi,
    unsigned char * jtag_trstn,
    unsigned char jtag_tdo
    )
    {
    if (client_fd > 0) {
    tdo = jtag_tdo;
    execute_command();
    } else {
    this->accept();
    }
    * jtag_tck = tck;
    * jtag_tms = tms;
    * jtag_tdi = tdi;
    * jtag_trstn = trstn;
    }
    void remote_bitbang_t::reset(){
    //trstn = 0;
    }
    void remote_bitbang_t::set_pins(char _tck, char _tms, char _tdi){
    tck = _tck;
    tms = _tms;
    tdi = _tdi;
    }
    void remote_bitbang_t::execute_command()
    {
    char command;
    int again = 1;
    while (again) {
    ssize_t num_read = read(client_fd, &command, sizeof(command));
    if (num_read == -1) {
    if (errno == EAGAIN) {
    // We'll try again the next call.
    //fprintf(stderr, "Received no command. Will try again on the next call\n");
    } else {
    fprintf(stderr, "remote_bitbang failed to read on socket: %s (%d)\n",
    strerror(errno), errno);
    again = 0;
    abort();
    }
    } else if (num_read == 0) {
    fprintf(stderr, "No Command Received.\n");
    again = 1;
    } else {
    again = 0;
    }
    }
    //fprintf(stderr, "Received a command %c\n", command);
    int dosend = 0;
    char tosend = '?';
    switch (command) {
    case 'B': /* fprintf(stderr, "*BLINK*\n"); */ break;
    case 'b': /* fprintf(stderr, "_______\n"); */ break;
    case 'r': reset(); break; // This is wrong. 'r' has other bits that indicated TRST and SRST.
    case '0': set_pins(0, 0, 0); break;
    case '1': set_pins(0, 0, 1); break;
    case '2': set_pins(0, 1, 0); break;
    case '3': set_pins(0, 1, 1); break;
    case '4': set_pins(1, 0, 0); break;
    case '5': set_pins(1, 0, 1); break;
    case '6': set_pins(1, 1, 0); break;
    case '7': set_pins(1, 1, 1); break;
    case 'R': dosend = 1; tosend = tdo ? '1' : '0'; break;
    case 'Q': quit = 1; break;
    default:
    fprintf(stderr, "remote_bitbang got unsupported command '%c'\n",
    command);
    }
    if (dosend){
    while (1) {
    ssize_t bytes = write(client_fd, &tosend, sizeof(tosend));
    if (bytes == -1) {
    fprintf(stderr, "failed to write to socket: %s (%d)\n", strerror(errno), errno);
    abort();
    }
    if (bytes > 0) {
    break;
    }
    }
    }
    if (quit) {
    // The remote disconnected.
    fprintf(stderr, "Remote end disconnected\n");
    close(client_fd);
    client_fd = 0;
    }
    }

https://github.com/freechipsproject/rocket-chip/blob/src/main/resources/csrc/remote_bitbang.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#ifndef REMOTE_BITBANG_H
#define REMOTE_BITBANG_H
#include <stdint.h>
#include <sys/types.h>

class remote_bitbang_t
{
public:
// Create a new server, listening for connections from localhost on the given
// port.
remote_bitbang_t(uint16_t port);
// Do a bit of work.
void tick(unsigned char * jtag_tck,
unsigned char * jtag_tms,
unsigned char * jtag_tdi,
unsigned char * jtag_trstn,
unsigned char jtag_tdo);
unsigned char done() {return quit;}
int exit_code() {return err;}
private:
int err;
unsigned char tck;
unsigned char tms;
unsigned char tdi;
unsigned char trstn;
unsigned char tdo;
unsigned char quit;
int socket_fd;
int client_fd;
static const ssize_t buf_size = 64 * 1024;
char recv_buf[buf_size];
ssize_t recv_start, recv_end;
// Check for a client connecting, and accept if there is one.
void accept();
// Execute any commands the client has for us.
// But we only execute 1 because we need time for the
// simulation to run.
void execute_command();
// Reset. Currently does nothing.
void reset();
void set_pins(char _tck, char _tms, char _tdi);
};

#endif

Usage

  1. Start the simulation, the port will be displayed.

    1
    2
    > ./simv 
    Listening on port 36054
  1. Run openocd

    1
    2
    3
    4
    5
    > setenv JTAG_VPI_PORT 36054; setenv JTAG_DTM_ENABLE_SBA on; openocd -f RocketSim.cfg -d -l log

    Listening on port 3333 for gdb connections
    Listening on port 6666 for tcl connections
    Listening on port 4444 for telnet connections
  1. Run telnet to send command to openocd

    1
    2
    3
    > telnet 127.0.0.1 4444
    Open On-Chip Debugger
    >

Openocd commands

config

An example:

https://github.com/freechipsproject/rocket-chip/blob/scripts/RocketSim.cfg

OpenOCD doesn’t have a concept of harts, but thinks of each hart as a completely independent core. gdb doesn’t have a concept of cores, but can only think about threads. OpenOCD does have a concept of threads when there is an RTOS on the system. Combining these 3 pieces, we can create an acceptable debug experience by having OpenOCD pretend there is an RTOS, and expose each hart as a thread to gdb.

Originally we did this using -rtos riscv which is RISC-V-specific. Since we implemented that, OpenOCD has gained a new ability which solves the same problem in a target-independent way: -rtos hwthread. While the behavior for the user is almost identical, the configuration required is a bit different.

-rtos riscv

The OpenOCD -rtos riscv configuration option is deprecated and will be removed from OpenOCD in a future version. Using -rtos hwthread is the multi-core configuration option that should be used going forward. Existing config scripts should be converted from riscv to hwthread.

The configuration only mentions a single target, and our code will figure out how many harts there really are and expose each one as a thread. Example configuration:

1
2
set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME riscv -chain-position $_TARGETNAME -rtos riscv

-rtos hwthread

The configuration mentions every hart explicitly. The harts are combined into SMP groups, and the first target in the SMP group mentions the RTOS. If you like you can create multiple SMP groups, and OpenOCD will listen for one gdb per group. Example:

1
2
3
4
5
set _TARGETNAME_0 $_CHIPNAME.cpu0
set _TARGETNAME_1 $_CHIPNAME.cpu1
target create $_TARGETNAME_0 riscv -chain-position $_CHIPNAME.cpu -rtos hwthread
target create $_TARGETNAME_1 riscv -chain-position $_CHIPNAME.cpu -coreid 1
target smp $_TARGETNAME_0 $_TARGETNAME_1

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
adapter_khz 10000

interface ftdi
ftdi_device_desc "Dual RS232-HS"
ftdi_vid_pid 0x0403 0x6010

ftdi_layout_init 0x0008 0x001b
ftdi_layout_signal nSRST -oe 0x0020

set _CHIPNAME riscv
jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x20000913

set _TARGETNAME_0 $_CHIPNAME.cpu0
set _TARGETNAME_1 $_CHIPNAME.cpu1
set _TARGETNAME_2 $_CHIPNAME.cpu2
set _TARGETNAME_3 $_CHIPNAME.cpu3
set _TARGETNAME_4 $_CHIPNAME.cpu4
target create $_TARGETNAME_0 riscv -chain-position $_CHIPNAME.cpu -rtos hwthread
target create $_TARGETNAME_1 riscv -chain-position $_CHIPNAME.cpu -coreid 1
target create $_TARGETNAME_2 riscv -chain-position $_CHIPNAME.cpu -coreid 2
target create $_TARGETNAME_3 riscv -chain-position $_CHIPNAME.cpu -coreid 3
target create $_TARGETNAME_4 riscv -chain-position $_CHIPNAME.cpu -coreid 4
target smp $_TARGETNAME_0 $_TARGETNAME_1 $_TARGETNAME_2 $_TARGETNAME_3 $_TARGETNAME_4

$_TARGETNAME_0 configure -work-area-phys 0x80000000 -work-area-size 10000 -work-area-backup 1

flash bank onboard_spi_flash fespi 0x20000000 0 0 0 $_TARGETNAME_0 0x10040000

init
halt

# Uncomment this if you want to be able to clobber your SPI Flash, which
# probably you don't since you can do it through Linux
#flash protect 0 0 last off

commands

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
sleep msec [busy]
halt [ms]
wait_halt [ms]
resume [address]
step [address]
reset
reset run
reset halt
reset init
soft_reset_halt

# Display contents of address addr, as 64-bit doublewords (mdd), 32-bit words (mdw),
16-bit halfwords (mdh), or 8-bit bytes (mdb).
mdd [phys] addr [count]
mdw [phys] addr [count]
mdh [phys] addr [count]
mdb [phys] addr [count]

# Writes the specified doubleword (64 bits), word (32 bits), halfword (16 bits), or byte
(8-bit) value, at the specified address addr.
mwd [phys] addr doubleword [count]
mww [phys] addr word [count]
mwh [phys] addr halfword [count]
mwb [phys] addr byte [count]

dump_image filename address size
load_image filename address [[bin|ihex|elf|s19] min_addr max_length]

# With no parameters, lists all active breakpoints. Else sets a breakpoint on code
execution starting at address for length bytes.
bp [address len [hw]]

# Remove the breakpoint at address.
rbp address

# Remove data watchpoint on address
rwp address

# With no parameters, lists all active watchpoints. Else sets a data watchpoint on data
from address for length bytes.
wp [address len [(r|w|a) [value [mask]]]]