Compliance update: GNU Coding Standards for C, relative paths in Python, and updated README.
This commit is contained in:
@@ -1,9 +1,14 @@
|
|||||||
# ADSP-219x radare2 Plugin
|
# ADSP-219x Radare2 Plugin
|
||||||
|
|
||||||
Dieses Plugin fügt ADSP-2191 Support zu radare2 und iaito hinzu.
|
This plugin adds support for the ADSP-219x architecture to radare2 and iaito.
|
||||||
|
|
||||||
## Kompilieren & Installation
|
## Prerequisites
|
||||||
Da der Zielrechner air-gapped ist, stelle sicher, dass `gcc` und `radare2-dev` (oder Header) installiert sind.
|
|
||||||
|
- GCC
|
||||||
|
- Radare2 5.8.0+ (uses the `RArchPlugin` API)
|
||||||
|
- Python 3 (for test ROM generation)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd r2plugin
|
cd r2plugin
|
||||||
@@ -11,20 +16,29 @@ make
|
|||||||
make install
|
make install
|
||||||
```
|
```
|
||||||
|
|
||||||
## Nutzung
|
To uninstall:
|
||||||
Lade dein ROM mit dem `-a` (Architecture) und `-b` (Bits) Schalter:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
r2 -a adsp219x -b 24 firmware_dump.bin
|
make uninstall
|
||||||
```
|
```
|
||||||
|
|
||||||
In `iaito`:
|
## Usage
|
||||||
1. Datei öffnen
|
|
||||||
2. `Architecture`: `adsp219x` (im Dropdown suchen oder tippen)
|
|
||||||
3. `Bits`: `24`
|
|
||||||
|
|
||||||
## Features
|
Load a binary file specifying the architecture and bits:
|
||||||
- Volle 24-bit Instruktionsbreite
|
|
||||||
- Unterstützung für Multifunktions-Compute (ALU/MAC)
|
```bash
|
||||||
- Anzeige von Delayed-Branch (DB) Flags
|
r2 -a adsp219x -b 24 firmware.bin
|
||||||
- Jump-Adressen Decodierung
|
```
|
||||||
|
|
||||||
|
## Maintenance & Testing
|
||||||
|
|
||||||
|
A test ROM generator is provided in the `testrom/` directory to verify instruction decoding accuracy.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd testrom
|
||||||
|
python3 gen_isa_test.py
|
||||||
|
r2 -a adsp219x -b 24 -q -c "pd 42" test_roms/isa_test.bin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Coding Standards
|
||||||
|
|
||||||
|
This project follows the **GNU Coding Standards** for C code and avoids hardcoded absolute paths in scripts.
|
||||||
|
|||||||
@@ -1,101 +1,214 @@
|
|||||||
/* ADSP-219x radare2 arch plugin - Precise Opcode Table Implementation */
|
/* asm_adsp219x.c -- Radare2 architecture plugin for Analog Devices ADSP-219x
|
||||||
|
Copyright (C) 2026 OpenClaw
|
||||||
|
|
||||||
|
This file is part of our reverse engineering project for ADSP-219x.
|
||||||
|
It implements the disassembler via the RArchPlugin interface. */
|
||||||
|
|
||||||
#include <r_arch.h>
|
#include <r_arch.h>
|
||||||
|
|
||||||
static const char *cond_str[] = { "EQ", "NE", "GT", "LE", "LT", "GE", "AV", "NOT AV", "AC", "NOT AC", "SWCOND", "NOT SWCOND", "MV", "NOT MV", "NOT CE", "TRUE" };
|
/* Register name definitions for ADSP-219x. */
|
||||||
static const char *reg0[] = { "AX0", "AX1", "MX0", "MX1", "AY0", "AY1", "MY0", "MY1", "MR2", "SR2", "AR", "SI", "MR1", "SR1", "MR0", "SR0" };
|
static const char *cond_str[] =
|
||||||
static const char *reg1[] = { "I0", "I1", "I2", "I3", "M0", "M1", "M2", "M3", "L0", "L1", "L2", "L3", "IMASK", "IRPTL", "ICNTL", "CNTR" };
|
{ "EQ", "NE", "GT", "LE", "LT", "GE", "AV", "NOT AV", "AC", "NOT AC", "SWCOND", "NOT SWCOND", "MV", "NOT MV", "NOT CE", "TRUE" };
|
||||||
static const char *reg2[] = { "I4", "I5", "I6", "I7", "M4", "M5", "M6", "M7", "L4", "L5", "L6", "L7", "STACKA", "LPCSTACKA", "RES", "RES" };
|
|
||||||
static const char *reg3[] = { "ASTAT", "MSTAT", "SSTAT", "LPSTACKP", "CCODE", "SE", "SB", "PX", "DMPG1", "DMPG2", "IOPG", "IJPG", "RES", "RES", "RES", "STACKP" };
|
|
||||||
|
|
||||||
static const char *get_reg(int gp, int idx) {
|
static const char *reg0[] =
|
||||||
switch (gp & 3) {
|
{ "AX0", "AX1", "MX0", "MX1", "AY0", "AY1", "MY0", "MY1", "MR2", "SR2", "AR", "SI", "MR1", "SR1", "MR0", "SR0" };
|
||||||
case 0: return reg0[idx & 0xF];
|
|
||||||
case 1: return reg1[idx & 0xF];
|
static const char *reg1[] =
|
||||||
case 2: return reg2[idx & 0xF];
|
{ "I0", "I1", "I2", "I3", "M0", "M1", "M2", "M3", "L0", "L1", "L2", "L3", "IMASK", "IRPTL", "ICNTL", "CNTR" };
|
||||||
case 3: return reg3[idx & 0xF];
|
|
||||||
}
|
static const char *reg2[] =
|
||||||
|
{ "I4", "I5", "I6", "I7", "M4", "M5", "M6", "M7", "L4", "L5", "L6", "L7", "STACKA", "LPCSTACKA", "RESERVED", "RESERVED" };
|
||||||
|
|
||||||
|
static const char *reg3[] =
|
||||||
|
{ "ASTAT", "MSTAT", "SSTAT", "LPSTACKP", "CCODE", "SE", "SB", "PX", "DMPG1", "DMPG2", "IOPG", "IJPG", "RES", "RES", "RES", "STACKP" };
|
||||||
|
|
||||||
|
/* Returns the name of a register given its group GP and index IDX. */
|
||||||
|
static const char *
|
||||||
|
get_reg (int gp, int idx)
|
||||||
|
{
|
||||||
|
switch (gp & 0x3)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return reg0[idx & 0xF];
|
||||||
|
case 1:
|
||||||
|
return reg1[idx & 0xF];
|
||||||
|
case 2:
|
||||||
|
return reg2[idx & 0xF];
|
||||||
|
case 3:
|
||||||
|
return reg3[idx & 0xF];
|
||||||
|
default:
|
||||||
return "??";
|
return "??";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool decode(RArchSession *as, RAnalOp *op, RAnalOpMask mask) {
|
/* Decodes the instruction at current OP position using MASK. */
|
||||||
if (op->size < 3) return false;
|
static bool
|
||||||
|
decode (RArchSession *as, RAnalOp *op, RAnalOpMask mask)
|
||||||
|
{
|
||||||
|
ut32 ins;
|
||||||
const ut8 *b = op->bytes;
|
const ut8 *b = op->bytes;
|
||||||
ut32 ins = ((ut32)b[0] << 16) | ((ut32)b[1] << 8) | (ut32)b[2];
|
|
||||||
op->size = 3; op->type = R_ANAL_OP_TYPE_UNK;
|
|
||||||
if (!(mask & R_ARCH_OP_MASK_DISASM)) return true;
|
|
||||||
|
|
||||||
/* Top bits classification */
|
if (op->size < 3)
|
||||||
ut32 b23_22 = (ins >> 22) & 3;
|
return false;
|
||||||
ut32 top8 = (ins >> 16) & 0xFF;
|
|
||||||
|
|
||||||
/* Type 30/31: NOP / IDLE */
|
ins = ((ut32) b[0] << 16) | ((ut32) b[1] << 8) | (ut32) b[2];
|
||||||
if (top8 == 0x00) {
|
op->size = 3;
|
||||||
if (ins == 0) { op->mnemonic = strdup("NOP"); op->type = R_ANAL_OP_TYPE_NOP; }
|
op->type = R_ANAL_OP_TYPE_UNK;
|
||||||
else { op->mnemonic = r_str_newf("IDLE 0x%X", ins & 0xFF); op->type = R_ANAL_OP_TYPE_TRAP; }
|
|
||||||
|
if (!(mask & R_ARCH_OP_MASK_DISASM))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
/* Basic opcode classification based on high bits. */
|
||||||
|
ut32 top_bits_2 = (ins >> 22) & 0x3;
|
||||||
|
ut32 top_bits_8 = (ins >> 16) & 0xFF;
|
||||||
|
|
||||||
|
/* Type 30/31: NOP / IDLE commands. */
|
||||||
|
if (top_bits_8 == 0x00)
|
||||||
|
{
|
||||||
|
if (ins == 0)
|
||||||
|
{
|
||||||
|
op->mnemonic = strdup ("NOP");
|
||||||
|
op->type = R_ANAL_OP_TYPE_NOP;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
op->mnemonic = r_str_newf ("IDLE 0x%X", ins & 0xFF);
|
||||||
|
op->type = R_ANAL_OP_TYPE_TRAP;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Type 1: Multifunction (11xxxx) */
|
/* Type 1: Multifunction (11xxxx). */
|
||||||
if (b23_22 == 3) {
|
if (top_bits_2 == 0x3)
|
||||||
op->mnemonic = r_str_newf("multifunc 0x%06X", ins); return true;
|
{
|
||||||
|
op->mnemonic = r_str_newf ("compute_dm_pm 0x%06X", ins);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Type 3: Direct Memory (10xxxx) */
|
/* Type 3: Direct Memory access (10xxxx). */
|
||||||
if (b23_22 == 2) {
|
if (top_bits_2 == 0x2)
|
||||||
ut32 d = (ins >> 20) & 1, addr = (ins >> 4) & 0xFFFF, reg = ins & 0xF;
|
{
|
||||||
op->mnemonic = r_str_newf("%s %s DM(0x%04X)", reg0[reg], d?"=":"=", addr); return true;
|
ut32 write_flag = (ins >> 20) & 0x1;
|
||||||
|
ut32 address = (ins >> 4) & 0xFFFF;
|
||||||
|
ut32 register_idx = ins & 0xF;
|
||||||
|
|
||||||
|
op->mnemonic = r_str_newf ("%s %s DM(0x%04X)",
|
||||||
|
reg0[register_idx],
|
||||||
|
write_flag ? "=" : "=", address);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Type 6/7/IO/Reg (01xxxx) */
|
/* Instructions starting with 01xxxx. */
|
||||||
if (b23_22 == 1) {
|
if (top_bits_2 == 0x1)
|
||||||
ut32 top4 = (ins >> 20) & 0xF;
|
{
|
||||||
if (top4 == 4) { ut32 val = (ins >> 4) & 0xFFFF, dr = ins & 0xF; op->mnemonic = r_str_newf("%s = 0x%04X", reg0[dr], val); return true; }
|
ut32 top_bits_4 = (ins >> 20) & 0xF;
|
||||||
if (top4 == 5) { ut32 val = (ins >> 4) & 0xFFFF, dr = ins & 0xF; op->mnemonic = r_str_newf("%s = 0x%04X", reg1[dr], val); return true; }
|
|
||||||
if (top8 == 0x6D) { ut32 addr = (ins >> 4) & 0xFF, dr = ins & 0xF; op->mnemonic = r_str_newf("IO(0x%02X) = %s", addr, reg0[dr]); return true; }
|
|
||||||
if (top8 == 0x6C) { ut32 addr = (ins >> 4) & 0xFF, dr = ins & 0xF; op->mnemonic = r_str_newf("REG(0x%02X) = %s", addr, reg0[dr]); return true; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Groups under (00xxxx) */
|
if (top_bits_4 == 0x4)
|
||||||
if (b23_22 == 0) {
|
{
|
||||||
ut32 top4 = (ins >> 20) & 0xF;
|
/* Type 6: Dreg = Imm16. */
|
||||||
if (top4 == 3) { ut32 val = (ins >> 4) & 0xFFFF, dr = ins & 0xF; op->mnemonic = r_str_newf("%s = 0x%04X", reg2[dr], val); return true; }
|
op->mnemonic = r_str_newf ("%s = 0x%04X", reg0[ins & 0xF], (ins >> 4) & 0xFFFF);
|
||||||
if ((ins >> 19) == 3) {
|
return true;
|
||||||
ut32 db = (ins >> 18) & 1, s = (ins >> 17) & 1, addr = (ins >> 4) & 0x1FFF, cond = ins & 0xF;
|
|
||||||
op->mnemonic = r_str_newf("%s%s %s 0x%04X%s", (cond==15?"":"IF "), (cond==15?"":cond_str[cond]), (s?"CALL":"JUMP"), addr, (db?" (DB)":""));
|
|
||||||
if (db) op->delay = 1; return true;
|
|
||||||
}
|
}
|
||||||
if ((ins >> 18) == 7) {
|
if (top_bits_4 == 0x5)
|
||||||
ut32 addr = (ins >> 4 & 0x3FFF) | (ins & 3) << 14, db = (ins >> 3) & 1, s = (ins >> 2) & 1;
|
{
|
||||||
op->mnemonic = r_str_newf("%s 0x%05X%s", (s?"CALL":"JUMP"), addr, (db?" (DB)":""));
|
/* Type 7: Reg1 = Imm16. */
|
||||||
if (db) op->delay = 1; return true;
|
op->mnemonic = r_str_newf ("%s = 0x%04X", reg1[ins & 0xF], (ins >> 4) & 0xFFFF);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
if (top8 == 0x0A) {
|
if (top_bits_8 == 0x6D)
|
||||||
ut32 t = (ins >> 14) & 1, cond = ins & 0xF;
|
{
|
||||||
op->mnemonic = r_str_newf("%s%s %s", (cond==15?"":"IF "), (cond==15?"":cond_str[cond]), (t?"RTI":"RTS")); return true;
|
/* Type 34: IO Register load. */
|
||||||
|
op->mnemonic = r_str_newf ("IO(0x%02X) = %s", (ins >> 4) & 0xFF, reg0[ins & 0xF]);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
if (top8 == 0x0D) {
|
if (top_bits_8 == 0x6C)
|
||||||
ut32 drgp = (ins >> 10) & 3, srgp = (ins >> 8) & 3, dr = (ins >> 4) & 0xF, sr = ins & 0xF;
|
{
|
||||||
op->mnemonic = r_str_newf("%s = %s", get_reg(drgp, dr), get_reg(srgp, sr)); return true;
|
/* Type 35: System Register load. */
|
||||||
|
op->mnemonic = r_str_newf ("REG(0x%02X) = %s", (ins >> 4) & 0xFF, reg0[ins & 0xF]);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
op->mnemonic = r_str_newf("unk 0x%06X", ins);
|
/* Instructions starting with 00xxxx. */
|
||||||
|
if (top_bits_2 == 0x0)
|
||||||
|
{
|
||||||
|
ut32 top_bits_4 = (ins >> 20) & 0xF;
|
||||||
|
|
||||||
|
if (top_bits_4 == 0x3)
|
||||||
|
{
|
||||||
|
/* Type 7: Reg2 = Imm16. */
|
||||||
|
op->mnemonic = r_str_newf ("%s = 0x%04X", reg2[ins & 0xF], (ins >> 4) & 0xFFFF);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ((ins >> 19) == 0x3)
|
||||||
|
{
|
||||||
|
/* Type 10: Jump/Call. */
|
||||||
|
ut32 delay_slot = (ins >> 18) & 0x1;
|
||||||
|
ut32 sub_flag = (ins >> 17) & 0x1;
|
||||||
|
ut32 jump_addr = (ins >> 4) & 0x1FFF;
|
||||||
|
ut32 condition = ins & 0xF;
|
||||||
|
|
||||||
|
op->mnemonic = r_str_newf ("%s%s %s 0x%04X%s",
|
||||||
|
(condition == 15 ? "" : "IF "),
|
||||||
|
(condition == 15 ? "" : cond_str[condition]),
|
||||||
|
(sub_flag ? "CALL" : "JUMP"), jump_addr,
|
||||||
|
delay_slot ? " (DB)" : "");
|
||||||
|
if (delay_slot)
|
||||||
|
op->delay = 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (top_bits_8 == 0x0D)
|
||||||
|
{
|
||||||
|
/* Type 17: Register-to-Register move. */
|
||||||
|
ut32 dest_gp = (ins >> 10) & 0x3;
|
||||||
|
ut32 src_gp = (ins >> 8) & 0x3;
|
||||||
|
ut32 dest_idx = (ins >> 4) & 0xF;
|
||||||
|
ut32 src_idx = ins & 0xF;
|
||||||
|
|
||||||
|
op->mnemonic = r_str_newf ("%s = %s", get_reg (dest_gp, dest_idx), get_reg (src_gp, src_idx));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
op->mnemonic = r_str_newf ("unk 0x%06X", ins);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int archinfo(RArchSession *s, ut32 q) {
|
/* Returns the info corresponding to code alignment and op sizes. */
|
||||||
switch (q) {
|
static int
|
||||||
case R_ARCH_INFO_CODE_ALIGN: return 3;
|
archinfo (RArchSession *s, ut32 q)
|
||||||
case R_ARCH_INFO_MINOP_SIZE: case R_ARCH_INFO_MAXOP_SIZE: return 3;
|
{
|
||||||
default: return -1;
|
switch (q)
|
||||||
|
{
|
||||||
|
case R_ARCH_INFO_CODE_ALIGN:
|
||||||
|
return 3;
|
||||||
|
case R_ARCH_INFO_MINOP_SIZE:
|
||||||
|
case R_ARCH_INFO_MAXOP_SIZE:
|
||||||
|
return 3;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Definition of the RArchPlugin structure for ADSP-219x. */
|
||||||
const RArchPlugin r_arch_plugin_adsp219x = {
|
const RArchPlugin r_arch_plugin_adsp219x = {
|
||||||
.meta = { .name = "adsp219x", .author = "OpenClaw", .desc = "ADSP-219x Full Disasm", .license = "LGPL-3.0-only" },
|
.meta = {
|
||||||
.arch = "adsp219x", .bits = R_SYS_BITS_PACK(24), .endian = R_SYS_ENDIAN_BIG, .info = archinfo, .decode = (RArchPluginDecodeCallback)decode,
|
.name = "adsp219x",
|
||||||
|
.author = "OpenClaw",
|
||||||
|
.desc = "ADSP-219x (GNU Style Layout)",
|
||||||
|
.license = "LGPL-3.0-only"
|
||||||
|
},
|
||||||
|
.arch = "adsp219x",
|
||||||
|
.bits = R_SYS_BITS_PACK (24),
|
||||||
|
.endian = R_SYS_ENDIAN_BIG,
|
||||||
|
.info = archinfo,
|
||||||
|
.decode = (RArchPluginDecodeCallback) decode,
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifndef R2_PLUGIN_INCORE
|
#ifndef R2_PLUGIN_INCORE
|
||||||
R_API RLibStruct radare_plugin = { .type = R_LIB_TYPE_ARCH, .data = &r_arch_plugin_adsp219x, .version = R2_VERSION };
|
R_API RLibStruct radare_plugin = {
|
||||||
|
.type = R_LIB_TYPE_ARCH,
|
||||||
|
.data = (void *) &r_arch_plugin_adsp219x,
|
||||||
|
.version = R2_VERSION
|
||||||
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,24 +1,35 @@
|
|||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
def write_ins(f, ins_list):
|
def write_ins(f, ins_list):
|
||||||
|
"""Write 24-bit instructions to the file in Big Endian format."""
|
||||||
for ins in ins_list:
|
for ins in ins_list:
|
||||||
f.write(bytes([(ins >> 16) & 0xFF, (ins >> 8) & 0xFF, ins & 0xFF]))
|
f.write(bytes([(ins >> 16) & 0xFF, (ins >> 8) & 0xFF, ins & 0xFF]))
|
||||||
|
|
||||||
def gen_isa_test_rom():
|
def gen_isa_test_rom():
|
||||||
rom_p = "/home/openclaw/adsp219x-re/testrom/test_roms/isa_test.bin"
|
"""Generates a test ROM with various ADSP-219x instruction types."""
|
||||||
|
# Use path relative to the script's location
|
||||||
|
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
rom_p = os.path.join(base_dir, "test_roms", "isa_test.bin")
|
||||||
|
|
||||||
os.makedirs(os.path.dirname(rom_p), exist_ok=True)
|
os.makedirs(os.path.dirname(rom_p), exist_ok=True)
|
||||||
|
|
||||||
with open(rom_p, "wb") as f:
|
|
||||||
instructions = [
|
instructions = [
|
||||||
0x000000, 0x000215, 0xC00000, 0xCC9A55, 0x801234, 0x815678, 0x606800,
|
0x000000, 0x000215, 0xC00000, 0xCC9A55, 0x801234, 0x815678, 0x606800,
|
||||||
0x412340, 0x4ABCDD, 0x511110, 0x344440, 0x226810, 0x206800, 0x18100F,
|
0x412340, 0x4ABCDD, 0x511110, 0x344440, 0x226810, 0x206800, 0x18100F,
|
||||||
0x1A2000, 0x1C1234 << 4, 0x16610F, 0x124000, 0x128010, 0x0F0050,
|
0x1A2000, (0x1C1234 << 4), 0x16610F, 0x124000, 0x128010, 0x0F0050,
|
||||||
0x0EE000, 0x0D4400, 0x0C0001, 0x0C1000, 0x0B000F, 0x0A000F, 0x0A800F,
|
0x0EE000, 0x0D4400, 0x0C0001, 0x0C1000, 0x0B000F, 0x0A000F, 0x0A800F,
|
||||||
0x018000, 0x010100, 0x07E340, 0x120000, 0x038000, 0x03F000, 0x030000,
|
0x018000, 0x010100, 0x07E340, 0x120000, 0x038000, 0x03F000, 0x030000,
|
||||||
0x040010, 0x081050, 0x150040, 0x06D100, 0x06C200, 0x06000F, 0x123456, 0x077001
|
0x040010, 0x081050, 0x150040, 0x06D100, 0x06C200, 0x06000F, 0x123456, 0x077001
|
||||||
]
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(rom_p, "wb") as f:
|
||||||
write_ins(f, instructions)
|
write_ins(f, instructions)
|
||||||
print(f"Generated ISA test ROM with {len(instructions)} instructions at {rom_p}")
|
print(f"Generated ISA test ROM with {len(instructions)} instructions at: {rom_p}")
|
||||||
|
except OSError as e:
|
||||||
|
print(f"Error writing ROM file: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
gen_isa_test_rom()
|
gen_isa_test_rom()
|
||||||
|
|||||||
Reference in New Issue
Block a user