pld: add support for gowin devices 68/7368/19
authorDaniel Anselmi <danselmi@gmx.ch>
Mon, 12 Dec 2022 08:49:51 +0000 (09:49 +0100)
committerAntonio Borneo <borneo.antonio@gmail.com>
Sun, 30 Apr 2023 14:54:59 +0000 (14:54 +0000)
Change-Id: Idd1a09514bbbbe0a7b54d69010f6c2f91215fd1d
Signed-off-by: Daniel Anselmi <danselmi@gmx.ch>
Reviewed-on: https://review.openocd.org/c/openocd/+/7368
Tested-by: jenkins
Reviewed-by: Antonio Borneo <borneo.antonio@gmail.com>
doc/openocd.texi
src/pld/Makefile.am
src/pld/gowin.c [new file with mode: 0644]
src/pld/pld.c
tcl/board/gowin_runber.cfg [new file with mode: 0644]
tcl/fpga/gowin_gw1n.cfg [new file with mode: 0644]

index 8099455ac2211da7868e7fa264b4d0d854cc9780..cea02bd5429dcc17dea5b61d03fb861be8c6a545 100644 (file)
@@ -8559,6 +8559,13 @@ With a value of -1 for @var{pos} the check will be omitted.
 @end deffn
 
 
+@deffn {FPGA Driver} {gowin}
+This driver can be used to load the bitstream into FPGAs from Gowin.
+It is possible to program the SRAM. Programming the flash is not supported.
+The files @verb{|.fs|} and @verb{|.bin|} generated by Gowin FPGA Designer are supported.
+@end deffn
+
+
 @node General Commands
 @chapter General Commands
 @cindex commands
index a13f738ea6b8e3bb1e4d52561d59201d75b33c62..22ae9fd208e6b5329ca2c354184b1b3a232d9415 100644 (file)
@@ -6,6 +6,7 @@ noinst_LTLIBRARIES += %D%/libpld.la
        %D%/ecp2_3.c \
        %D%/ecp5.c \
        %D%/efinix.c \
+       %D%/gowin.c \
        %D%/intel.c \
        %D%/lattice.c \
        %D%/lattice_bit.c \
diff --git a/src/pld/gowin.c b/src/pld/gowin.c
new file mode 100644 (file)
index 0000000..467b799
--- /dev/null
@@ -0,0 +1,581 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/***************************************************************************
+ *   Copyright (C) 2022 by Daniel Anselmi                                  *
+ *   danselmi@gmx.ch                                                       *
+ ***************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <jtag/jtag.h>
+#include <jtag/adapter.h>
+#include <helper/bits.h>
+#include "pld.h"
+#include "raw_bit.h"
+
+#define NO_OP                       0x02
+#define ERASE_SRAM                  0x05
+#define SRAM_ERASE_DONE             0x09
+#define IDCODE                      0x11
+#define ADDRESS_INITIALIZATION      0x12
+#define READ_USERCODE               0x13
+#define CONFIG_ENABLE               0x15
+#define TRANSFER_CONFIGURATION_DATA 0x17
+#define CONFIG_DISABLE              0x3A
+#define RELOAD                      0x3C
+#define STATUS_REGISTER             0x41
+#define ERASE_FLASH                 0x75
+#define ENABLE_2ND_FLASH            0x78
+
+#define STAUS_MASK_MEMORY_ERASE     BIT(5)
+#define STAUS_MASK_SYSTEM_EDIT_MODE BIT(7)
+
+struct gowin_pld_device {
+       struct jtag_tap *tap;
+};
+
+struct gowin_bit_file {
+       struct raw_bit_file raw_file;
+       size_t capacity;
+       uint32_t id;
+       uint16_t stored_checksum;
+       int compressed;
+       int crc_en;
+       uint16_t checksum;
+       uint8_t replace8x;
+       uint8_t replace4x;
+       uint8_t replace2x;
+};
+
+static uint64_t gowin_read_fs_file_bitsequence(const char *bits, int length)
+{
+       uint64_t res = 0;
+       for (int i = 0; i < length; i++)
+               res = (res << 1) | (*bits++ == '1' ? 1 : 0);
+       return res;
+}
+
+static int gowin_add_byte_to_bit_file(struct gowin_bit_file *bit_file, uint8_t byte)
+{
+       if (bit_file->raw_file.length + 1 > bit_file->capacity) {
+               uint8_t *buffer;
+               if (bit_file->raw_file.data)
+                       buffer = realloc(bit_file->raw_file.data, bit_file->capacity + 8192);
+               else
+                       buffer = malloc(8192);
+               if (!buffer) {
+                       LOG_ERROR("Out of memory");
+                       return ERROR_FAIL;
+               }
+               bit_file->raw_file.data = buffer;
+               bit_file->capacity += 8192;
+       }
+
+       bit_file->raw_file.data[bit_file->raw_file.length++] = byte;
+
+       return ERROR_OK;
+}
+
+static int gowin_read_fs_file_header(struct gowin_bit_file *bit_file, FILE *stream)
+{
+       if (!bit_file)
+               return ERROR_FAIL;
+
+       int end_of_header = 0;
+       while (!end_of_header) {
+               char buffer[256];
+               char *line = fgets(buffer, 256, stream);
+               if (!line || feof(stream) || ferror(stream))
+                       return ERROR_FAIL;
+
+               if (line[0] == '/')
+                       continue;
+
+               size_t line_length = strlen(line);
+               if (line[line_length - 1] != '\n')
+                       return ERROR_FAIL;
+               line_length--;
+
+               for (unsigned int i = 0; i < line_length; i += 8) {
+                       uint8_t byte = gowin_read_fs_file_bitsequence(line + i, 8);
+                       int retval = gowin_add_byte_to_bit_file(bit_file, byte);
+                       if (retval != ERROR_OK)
+                               return retval;
+               }
+
+               uint8_t key = gowin_read_fs_file_bitsequence(line, 8);
+               line += 8;
+               uint64_t value = gowin_read_fs_file_bitsequence(line, line_length - 8);
+
+               if (key == 0x06) {
+                       bit_file->id = value & 0xffffffff;
+               } else if (key == 0x3B) {
+                       end_of_header = 1;
+                       bit_file->crc_en = (value & BIT(23)) ? 1 : 0;
+               }
+       }
+
+       return ERROR_OK;
+}
+
+static int gowin_read_fs_file(struct gowin_bit_file *bit_file, const char *filename)
+{
+       FILE *input_file = fopen(filename, "r");
+
+       if (!input_file) {
+               LOG_ERROR("Couldn't open %s: %s", filename, strerror(errno));
+               return ERROR_PLD_FILE_LOAD_FAILED;
+       }
+
+       int retval = gowin_read_fs_file_header(bit_file, input_file);
+       if (retval != ERROR_OK) {
+               free(bit_file->raw_file.data);
+               fclose(input_file);
+               return retval;
+       }
+
+       char digits_buffer[9]; /* 8 + 1 trailing zero */
+       do {
+               char *digits = fgets(digits_buffer, 9, input_file);
+               if (feof(input_file))
+                       break;
+               if (!digits || ferror(input_file)) {
+                       free(bit_file->raw_file.data);
+                       fclose(input_file);
+                       return ERROR_FAIL;
+               }
+               if (digits[0] == '\n')
+                       continue;
+
+               if (strlen(digits) != 8) {
+                       free(bit_file->raw_file.data);
+                       fclose(input_file);
+                       return ERROR_FAIL;
+               }
+               uint8_t byte = gowin_read_fs_file_bitsequence(digits, 8);
+               retval = gowin_add_byte_to_bit_file(bit_file, byte);
+               if (retval != ERROR_OK) {
+                       free(bit_file->raw_file.data);
+                       fclose(input_file);
+                       return ERROR_FAIL;
+               }
+       } while (1);
+
+       fclose(input_file);
+       return ERROR_OK;
+}
+
+static int gowin_read_file(struct gowin_bit_file *bit_file, const char *filename, bool *is_fs)
+{
+       memset(bit_file, 0, sizeof(struct gowin_bit_file));
+
+       if (!filename || !bit_file)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       const char *file_suffix_pos = strrchr(filename, '.');
+       if (!file_suffix_pos) {
+               LOG_ERROR("Unable to detect filename suffix");
+               return ERROR_PLD_FILE_LOAD_FAILED;
+       }
+
+       /* check if binary .bin or ascii .fs */
+       if (strcasecmp(file_suffix_pos, ".bin") == 0) {
+               *is_fs = false;
+               return cpld_read_raw_bit_file(&bit_file->raw_file, filename);
+       } else if (strcasecmp(file_suffix_pos, ".fs") == 0) {
+               *is_fs = true;
+               return gowin_read_fs_file(bit_file, filename);
+       }
+
+       LOG_ERROR("Filetype not supported, expecting .fs or .bin file");
+       return ERROR_PLD_FILE_LOAD_FAILED;
+}
+
+static int gowin_set_instr(struct jtag_tap *tap, uint8_t new_instr)
+{
+       struct scan_field field;
+       field.num_bits = tap->ir_length;
+       void *t = calloc(DIV_ROUND_UP(field.num_bits, 8), 1);
+       if (!t) {
+               LOG_ERROR("Out of memory");
+               return ERROR_FAIL;
+       }
+       field.out_value = t;
+       buf_set_u32(t, 0, field.num_bits, new_instr);
+       field.in_value = NULL;
+       jtag_add_ir_scan(tap, &field, TAP_IDLE);
+       jtag_add_runtest(3, TAP_IDLE);
+       free(t);
+       return ERROR_OK;
+}
+
+static int gowin_read_register(struct jtag_tap *tap, uint32_t reg, uint32_t *result)
+{
+       struct scan_field field;
+
+       int retval = gowin_set_instr(tap, reg);
+       if (retval != ERROR_OK)
+               return retval;
+       retval = jtag_execute_queue();
+       if (retval != ERROR_OK)
+               return retval;
+
+       uint8_t buf[4] = {0};
+       field.check_mask = NULL;
+       field.check_value = NULL;
+       field.num_bits = 32;
+       field.out_value = buf;
+       field.in_value = buf;
+
+       jtag_add_dr_scan(tap, 1, &field, TAP_IDLE);
+       retval = jtag_execute_queue();
+       *result = le_to_h_u32(buf);
+       return retval;
+}
+
+static int gowin_check_status_flag(struct jtag_tap *tap, uint32_t mask, uint32_t flag)
+{
+       uint32_t status = 0;
+
+       int retries = 0;
+       do {
+               int retval = gowin_read_register(tap, STATUS_REGISTER, &status);
+               if (retval != ERROR_OK)
+                       return retval;
+               if (retries++ == 100000)
+                       return ERROR_FAIL;
+       } while ((status & mask) != flag);
+
+       return ERROR_OK;
+}
+
+static int gowin_enable_config(struct jtag_tap *tap)
+{
+       int retval = gowin_set_instr(tap, CONFIG_ENABLE);
+       if (retval != ERROR_OK)
+               return retval;
+       retval = jtag_execute_queue();
+       if (retval != ERROR_OK)
+               return retval;
+
+       return gowin_check_status_flag(tap, STAUS_MASK_SYSTEM_EDIT_MODE, STAUS_MASK_SYSTEM_EDIT_MODE);
+}
+
+static int gowin_disable_config(struct jtag_tap *tap)
+{
+       int retval = gowin_set_instr(tap, CONFIG_DISABLE);
+       if (retval != ERROR_OK)
+               return retval;
+       retval = jtag_execute_queue();
+       if (retval != ERROR_OK)
+               return retval;
+
+       return gowin_check_status_flag(tap, STAUS_MASK_SYSTEM_EDIT_MODE, 0);
+}
+
+static int gowin_reload(struct jtag_tap *tap)
+{
+       int retval = gowin_set_instr(tap, RELOAD);
+       if (retval != ERROR_OK)
+               return retval;
+       retval = gowin_set_instr(tap, NO_OP);
+       if (retval != ERROR_OK)
+               return retval;
+       return jtag_execute_queue();
+}
+
+static int gowin_runtest_idle(struct jtag_tap *tap, unsigned int frac_sec)
+{
+       int speed = adapter_get_speed_khz() * 1000;
+       int cycles = DIV_ROUND_UP(speed, frac_sec);
+       jtag_add_runtest(cycles, TAP_IDLE);
+       return jtag_execute_queue();
+}
+
+static int gowin_erase_sram(struct jtag_tap *tap, bool tx_erase_done)
+{
+       /* config is already enabled */
+       int retval = gowin_set_instr(tap, ERASE_SRAM);
+       if (retval != ERROR_OK)
+               return retval;
+       retval = gowin_set_instr(tap, NO_OP);
+       if (retval != ERROR_OK)
+               return retval;
+
+       /* Delay or Run Test 2~10ms */
+       /* 10 ms is worst case for GW2A-55 */
+       jtag_add_sleep(10000);
+       retval = jtag_execute_queue();
+       if (retval != ERROR_OK)
+               return retval;
+
+       retval = gowin_check_status_flag(tap, STAUS_MASK_MEMORY_ERASE,
+                                                                       STAUS_MASK_MEMORY_ERASE);
+       if (retval != ERROR_OK)
+               return retval;
+
+       if (tx_erase_done) {
+               retval = gowin_set_instr(tap, SRAM_ERASE_DONE);
+               if (retval != ERROR_OK)
+                       return retval;
+               retval = gowin_set_instr(tap, NO_OP);
+               if (retval != ERROR_OK)
+                       return retval;
+               retval = jtag_execute_queue();
+               if (retval != ERROR_OK)
+                       return retval;
+               /* gen clock cycles in RUN/IDLE for 500us -> 1/500us = 2000/s */
+               retval = gowin_runtest_idle(tap, 2000);
+               if (retval != ERROR_OK)
+                       return retval;
+       }
+
+       retval = gowin_set_instr(tap, NO_OP);
+       if (retval != ERROR_OK)
+               return retval;
+       return jtag_execute_queue();
+}
+
+static int gowin_load_to_sram(struct pld_device *pld_device, const char *filename)
+{
+       if (!pld_device)
+               return ERROR_FAIL;
+
+       struct gowin_pld_device *gowin_info = pld_device->driver_priv;
+
+       if (!gowin_info || !gowin_info->tap)
+               return ERROR_FAIL;
+       struct jtag_tap *tap = gowin_info->tap;
+
+       bool is_fs = false;
+       struct gowin_bit_file bit_file;
+       int retval = gowin_read_file(&bit_file, filename, &is_fs);
+       if (retval != ERROR_OK)
+               return retval;
+
+       for (unsigned int i = 0; i < bit_file.raw_file.length; i++)
+               bit_file.raw_file.data[i] = flip_u32(bit_file.raw_file.data[i], 8);
+
+       uint32_t id;
+       retval = gowin_read_register(tap, IDCODE, &id);
+       if (retval != ERROR_OK) {
+               free(bit_file.raw_file.data);
+               return retval;
+       }
+
+       if (is_fs && id != bit_file.id) {
+               free(bit_file.raw_file.data);
+               LOG_ERROR("Id on device (0x%8.8" PRIx32 ") and id in bit-stream (0x%8.8" PRIx32 ") don't match.",
+                       id, bit_file.id);
+               return ERROR_FAIL;
+       }
+
+       retval = gowin_enable_config(tap);
+       if (retval != ERROR_OK) {
+               free(bit_file.raw_file.data);
+               return retval;
+       }
+
+       retval = gowin_erase_sram(tap, false);
+       if (retval != ERROR_OK) {
+               free(bit_file.raw_file.data);
+               return retval;
+       }
+
+       retval = gowin_set_instr(tap, ADDRESS_INITIALIZATION);
+       if (retval != ERROR_OK) {
+               free(bit_file.raw_file.data);
+               return retval;
+       }
+       retval = gowin_set_instr(tap, TRANSFER_CONFIGURATION_DATA);
+       if (retval != ERROR_OK) {
+               free(bit_file.raw_file.data);
+               return retval;
+       }
+
+       /* scan out the bitstream */
+       struct scan_field field;
+       field.num_bits = bit_file.raw_file.length * 8;
+       field.out_value = bit_file.raw_file.data;
+       field.in_value = bit_file.raw_file.data;
+       jtag_add_dr_scan(gowin_info->tap, 1, &field, TAP_IDLE);
+       jtag_add_runtest(3, TAP_IDLE);
+
+       retval = jtag_execute_queue();
+       if (retval != ERROR_OK) {
+               free(bit_file.raw_file.data);
+               return retval;
+       }
+
+       retval = gowin_disable_config(tap);
+       free(bit_file.raw_file.data);
+       if (retval != ERROR_OK)
+               return retval;
+
+       retval = gowin_set_instr(gowin_info->tap, NO_OP);
+       if (retval != ERROR_OK)
+               return retval;
+
+       retval = jtag_execute_queue();
+
+       return retval;
+}
+
+static int gowin_read_register_command(struct pld_device *pld_device, uint32_t cmd, uint32_t *value)
+{
+       if (!pld_device)
+               return ERROR_FAIL;
+
+       struct gowin_pld_device *gowin_info = pld_device->driver_priv;
+
+       if (!gowin_info || !gowin_info->tap)
+               return ERROR_FAIL;
+
+       return gowin_read_register(gowin_info->tap, cmd, value);
+}
+
+static int gowin_reload_command(struct pld_device *pld_device)
+{
+       if (!pld_device)
+               return ERROR_FAIL;
+
+       struct gowin_pld_device *gowin_info = pld_device->driver_priv;
+
+       if (!gowin_info || !gowin_info->tap)
+               return ERROR_FAIL;
+
+       return gowin_reload(gowin_info->tap);
+}
+
+COMMAND_HANDLER(gowin_read_status_command_handler)
+{
+       int dev_id;
+
+       if (CMD_ARGC != 1)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], dev_id);
+       struct pld_device *device = get_pld_device_by_num(dev_id);
+       if (!device) {
+               command_print(CMD, "pld device '#%s' is out of bounds", CMD_ARGV[0]);
+               return ERROR_FAIL;
+       }
+
+       uint32_t status = 0;
+       int retval = gowin_read_register_command(device, STATUS_REGISTER, &status);
+
+       if (retval == ERROR_OK)
+               command_print(CMD, "0x%8.8" PRIx32, status);
+
+       return retval;
+}
+
+COMMAND_HANDLER(gowin_read_user_register_command_handler)
+{
+       int dev_id;
+
+       if (CMD_ARGC != 1)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], dev_id);
+       struct pld_device *device = get_pld_device_by_num(dev_id);
+       if (!device) {
+               command_print(CMD, "pld device '#%s' is out of bounds", CMD_ARGV[0]);
+               return ERROR_FAIL;
+       }
+
+       uint32_t user_reg = 0;
+       int retval = gowin_read_register_command(device, READ_USERCODE, &user_reg);
+
+       if (retval == ERROR_OK)
+               command_print(CMD, "0x%8.8" PRIx32, user_reg);
+
+       return retval;
+}
+
+COMMAND_HANDLER(gowin_reload_command_handler)
+{
+       int dev_id;
+
+       if (CMD_ARGC != 1)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], dev_id);
+       struct pld_device *device = get_pld_device_by_num(dev_id);
+       if (!device) {
+               command_print(CMD, "pld device '#%s' is out of bounds", CMD_ARGV[0]);
+               return ERROR_FAIL;
+       }
+
+       return gowin_reload_command(device);
+}
+
+static const struct command_registration gowin_exec_command_handlers[] = {
+       {
+               .name = "read_status",
+               .mode = COMMAND_EXEC,
+               .handler = gowin_read_status_command_handler,
+               .help = "reading status register from FPGA",
+               .usage = "num_pld",
+       }, {
+               .name = "read_user",
+               .mode = COMMAND_EXEC,
+               .handler = gowin_read_user_register_command_handler,
+               .help = "reading user register from FPGA",
+               .usage = "num_pld",
+       }, {
+               .name = "reload",
+               .mode = COMMAND_EXEC,
+               .handler = gowin_reload_command_handler,
+               .help = "reloading bitstream from flash to SRAM",
+               .usage = "num_pld",
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+static const struct command_registration gowin_command_handler[] = {
+       {
+               .name = "gowin",
+               .mode = COMMAND_ANY,
+               .help = "gowin specific commands",
+               .usage = "",
+               .chain = gowin_exec_command_handlers
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+PLD_DEVICE_COMMAND_HANDLER(gowin_pld_device_command)
+{
+       struct jtag_tap *tap;
+
+       struct gowin_pld_device *gowin_info;
+
+       if (CMD_ARGC != 2)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       tap = jtag_tap_by_string(CMD_ARGV[1]);
+       if (!tap) {
+               command_print(CMD, "Tap: %s does not exist", CMD_ARGV[1]);
+               return ERROR_FAIL;
+       }
+
+       gowin_info = malloc(sizeof(struct gowin_pld_device));
+       if (!gowin_info) {
+               LOG_ERROR("Out of memory");
+               return ERROR_FAIL;
+       }
+       gowin_info->tap = tap;
+
+       pld->driver_priv = gowin_info;
+
+       return ERROR_OK;
+}
+
+struct pld_driver gowin_pld = {
+       .name = "gowin",
+       .commands = gowin_command_handler,
+       .pld_device_command = &gowin_pld_device_command,
+       .load = &gowin_load_to_sram,
+};
index f4f79bacb0792ac816a9a2b0dceb9e4cf145822e..d9e01f16cd15a0867118fabd897a20961a22e284 100644 (file)
 /* pld drivers
  */
 extern struct pld_driver efinix_pld;
+extern struct pld_driver gowin_pld;
 extern struct pld_driver intel_pld;
 extern struct pld_driver lattice_pld;
 extern struct pld_driver virtex2_pld;
 
 static struct pld_driver *pld_drivers[] = {
        &efinix_pld,
+       &gowin_pld,
        &intel_pld,
        &lattice_pld,
        &virtex2_pld,
diff --git a/tcl/board/gowin_runber.cfg b/tcl/board/gowin_runber.cfg
new file mode 100644 (file)
index 0000000..9496c6f
--- /dev/null
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# Gowin RUNBER FPGA Development Board
+# https://www.seeedstudio.com/Gowin-RUNBER-Development-Board-p-4779.html
+
+adapter driver ftdi
+ftdi vid_pid 0x0403 0x6010
+
+ftdi channel 0
+ftdi layout_init 0x0008 0x008b
+reset_config none
+transport select jtag
+adapter speed 6000
+
+source [find fpga/gowin_gw1n.cfg]
+
+
+#openocd -f board/gowin_runber.cfg  -c "init" -c "pld load 0 impl/pnr/gw1n_blinker.fs"
+#ipdbg -start -tap gw1n.tap -hub 0x42 -port 5555 -tool 0
diff --git a/tcl/fpga/gowin_gw1n.cfg b/tcl/fpga/gowin_gw1n.cfg
new file mode 100644 (file)
index 0000000..43d66b7
--- /dev/null
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+#  Gowin FPGA IDCODEs
+# from JTAG Programming and Configuration Guide
+# http://cdn.gowinsemi.com.cn/TN653E.pdf
+
+if { [info exists CHIPNAME] } {
+       set _CHIPNAME $CHIPNAME
+} else {
+       set _CHIPNAME gw1n
+}
+
+jtag newtap $_CHIPNAME tap -irlen 8 -ignore-version \
+       -expected-id 0x0900281B \
+       -expected-id 0x0900381B \
+       -expected-id 0x0100681B \
+       -expected-id 0x0300081B \
+       -expected-id 0x0300181B \
+       -expected-id 0x0120681B \
+       -expected-id 0x0100381B \
+       -expected-id 0x1100381B \
+       -expected-id 0x0100981B \
+       -expected-id 0x1100581B \
+       -expected-id 0x1100481B \
+       -expected-id 0x0100181B \
+       -expected-id 0x1100181B \
+       -expected-id 0x0100481B
+
+pld device gowin $_CHIPNAME.tap

Linking to existing account procedure

If you already have an account and want to add another login method you MUST first sign in with your existing account and then change URL to read https://review.openocd.org/login/?link to get to this page again but this time it'll work for linking. Thank you.

SSH host keys fingerprints

1024 SHA256:YKx8b7u5ZWdcbp7/4AeXNaqElP49m6QrwfXaqQGJAOk gerrit-code-review@openocd.zylin.com (DSA)
384 SHA256:jHIbSQa4REvwCFG4cq5LBlBLxmxSqelQPem/EXIrxjk gerrit-code-review@openocd.org (ECDSA)
521 SHA256:UAOPYkU9Fjtcao0Ul/Rrlnj/OsQvt+pgdYSZ4jOYdgs gerrit-code-review@openocd.org (ECDSA)
256 SHA256:A13M5QlnozFOvTllybRZH6vm7iSt0XLxbA48yfc2yfY gerrit-code-review@openocd.org (ECDSA)
256 SHA256:spYMBqEYoAOtK7yZBrcwE8ZpYt6b68Cfh9yEVetvbXg gerrit-code-review@openocd.org (ED25519)
+--[ED25519 256]--+
|=..              |
|+o..   .         |
|*.o   . .        |
|+B . . .         |
|Bo. = o S        |
|Oo.+ + =         |
|oB=.* = . o      |
| =+=.+   + E     |
|. .=o   . o      |
+----[SHA256]-----+
2048 SHA256:0Onrb7/PHjpo6iVZ7xQX2riKN83FJ3KGU0TvI0TaFG4 gerrit-code-review@openocd.zylin.com (RSA)