From 887875959aa84af92291db334898aaa20956e632 Mon Sep 17 00:00:00 2001 From: allexanderbergmans Date: Fri, 3 Jul 2026 12:17:10 +0200 Subject: init --- gen/docs/convert.sh | 55 ++++ gen/docs/generator.c | 481 ++++++++++++++++++++++++++++++++++ gen/docs/generator.h | 9 + gen/docs/isa-docgen | Bin 0 -> 51736 bytes gen/docs/isa_defs/arch.isa | 68 +++++ gen/docs/isa_defs/csrs.isa | 191 ++++++++++++++ gen/docs/isa_defs/instructions.isa | 512 +++++++++++++++++++++++++++++++++++++ gen/docs/isa_defs/registers.isa | 289 +++++++++++++++++++++ gen/docs/main.c | 83 ++++++ gen/docs/parser.c | 256 +++++++++++++++++++ gen/docs/parser.h | 9 + gen/docs/style.css | 139 ++++++++++ gen/docs/types.h | 78 ++++++ 13 files changed, 2170 insertions(+) create mode 100644 gen/docs/convert.sh create mode 100644 gen/docs/generator.c create mode 100644 gen/docs/generator.h create mode 100755 gen/docs/isa-docgen create mode 100644 gen/docs/isa_defs/arch.isa create mode 100644 gen/docs/isa_defs/csrs.isa create mode 100644 gen/docs/isa_defs/instructions.isa create mode 100644 gen/docs/isa_defs/registers.isa create mode 100644 gen/docs/main.c create mode 100644 gen/docs/parser.c create mode 100644 gen/docs/parser.h create mode 100644 gen/docs/style.css create mode 100644 gen/docs/types.h (limited to 'gen/docs') diff --git a/gen/docs/convert.sh b/gen/docs/convert.sh new file mode 100644 index 0000000..047eb63 --- /dev/null +++ b/gen/docs/convert.sh @@ -0,0 +1,55 @@ +#!/bin/sh +# convert.sh — Convert generated Markdown ISA doc to PDF +# Usage: ./convert.sh [input.md] [output.pdf] +# +# Requires: pandoc + xelatex (or wkhtmltopdf) +# Install: brew install pandoc mactex (macOS) +# apt install pandoc texlive (Linux) + +set -e + +INPUT="${1:-../../doc/spec/isa_reference.md}" +OUTPUT="${2:-../../doc/spec/isa_reference.pdf}" +DIR="$(cd "$(dirname "$OUTPUT")" && pwd)" +BASE="$(basename "$OUTPUT")" + +echo "Converting: $INPUT → $OUTPUT" + +if command -v pandoc >/dev/null 2>&1; then + if command -v xelatex >/dev/null 2>&1; then + pandoc "$INPUT" \ + -o "$OUTPUT" \ + --pdf-engine=xelatex \ + -V geometry:margin=1in \ + -V colorlinks=true \ + -V linkcolor=blue \ + -V toccolor=blue \ + -V mainfont="Times New Roman" \ + -V monofont="Courier New" \ + --toc --toc-depth=3 \ + --highlight-style=tango + echo "PDF generated: $OUTPUT" + elif command -v wkhtmltopdf >/dev/null 2>&1; then + DIR="$(dirname "$INPUT")" + pandoc "$INPUT" \ + -o "${DIR}/isa_reference.html" \ + --self-contained \ + --toc --toc-depth=3 + wkhtmltopdf \ + --margin-top 20mm \ + --margin-bottom 20mm \ + --margin-left 15mm \ + --margin-right 15mm \ + --toc \ + "${DIR}/isa_reference.html" "$OUTPUT" + echo "PDF generated: $OUTPUT" + else + echo "Error: No PDF engine found." + echo "Install: brew install pandoc mactex (macOS)" + echo " or: apt install pandoc texlive-latex-base (Linux)" + exit 1 + fi +else + echo "Error: pandoc not found. Install it first." + exit 1 +fi diff --git a/gen/docs/generator.c b/gen/docs/generator.c new file mode 100644 index 0000000..f67e42f --- /dev/null +++ b/gen/docs/generator.c @@ -0,0 +1,481 @@ +#include "generator.h" +#include +#include +#include + +static void write_title_page(FILE *out, const IsaDb *db) { + fprintf(out, "---\n"); + fprintf(out, "title: \"%s Instruction Set Architecture\"\n", db->arch_name[0] ? db->arch_name : "Fyntv"); + fprintf(out, "subtitle: \"Reference Manual\"\n"); + fprintf(out, "version: \"%s\"\n", db->arch_version[0] ? db->arch_version : "1.0.0"); + fprintf(out, "date: \"%s\"\n", db->arch_date[0] ? db->arch_date : "June 2026"); + fprintf(out, "status: \"%s\"\n", db->arch_status[0] ? db->arch_status : "Draft"); + fprintf(out, "geometry: margin=1in\n"); + fprintf(out, "toc: true\n"); + fprintf(out, "numbersections: true\n"); + fprintf(out, "---\n\n"); +} + +static void write_revision_history(FILE *out, const IsaDb *db) { + fprintf(out, "\\newpage\n\n"); + fprintf(out, "# Revision History\n\n"); + fprintf(out, "| Version | Date | Description |\n"); + fprintf(out, "|---------|------|-------------|\n"); + fprintf(out, "| %s | %s | Initial %s release |\n", + db->arch_version[0] ? db->arch_version : "1.0.0", + db->arch_date[0] ? db->arch_date : "2026-06", + db->arch_name[0] ? db->arch_name : "ISA"); + fprintf(out, "\n\\newpage\n\n"); +} + +static void write_toc_placeholder(FILE *out) { + fprintf(out, "\\tableofcontents\n\n"); + fprintf(out, "\\newpage\n\n"); +} + +static void write_intro(FILE *out, const IsaDb *db) { + fprintf(out, "# Introduction\n\n"); + fprintf(out, "This document defines the **%s Instruction Set Architecture (ISA)** version %s.\n\n", + db->arch_name[0] ? db->arch_name : "Fyntv", + db->arch_version[0] ? db->arch_version : "1.0.0"); + + fprintf(out, "## Scope\n\n"); + fprintf(out, "This specification covers:\n\n"); + fprintf(out, "- Instruction formats and encoding\n"); + fprintf(out, "- Register file and calling convention\n"); + fprintf(out, "- All base and extension instructions\n"); + fprintf(out, "- Control and status registers (CSRs)\n"); + fprintf(out, "- Exception and trap handling\n\n"); + + fprintf(out, "## ISA Overview\n\n"); + fprintf(out, "The %s ISA is a load-store architecture with:\n\n", + db->arch_name[0] ? db->arch_name : "Fyntv"); + fprintf(out, "- 32 general-purpose registers (x0–x31)\n"); + fprintf(out, "- Fixed-width 32-bit instructions\n"); + fprintf(out, "- Three operand formats: register (R-type), immediate (I-type), and store (S-type)\n"); + fprintf(out, "- Branch (B-type) and upper-immediate (U-type) variants\n"); + fprintf(out, "- A separate jump-and-link (J-type) format\n\n"); + fprintf(out, "\\newpage\n\n"); +} + +static void write_registers(FILE *out, const IsaDb *db) { + fprintf(out, "# Registers\n\n"); + fprintf(out, "## General-Purpose Registers\n\n"); + fprintf(out, "The architecture provides 32 general-purpose registers (x0–x31), " + "each 32 bits wide. Register x0 is hardwired to the constant zero.\n\n"); + + fprintf(out, "| Register | ABI Name | Description | Callee-Saved |\n"); + fprintf(out, "|----------|----------|-------------|--------------|\n"); + for (int i = 0; i < db->num_registers; i++) { + const IsaRegister *r = &db->registers[i]; + fprintf(out, "| %s | %s | %s | %s |\n", + r->name, r->abbr[0] ? r->abbr : "—", + r->desc[0] ? r->desc : "—", + r->preserve ? "Yes" : (r->preserve == 0 && r->index != 0 ? "No" : "—")); + } + + fprintf(out, "\n### ABI Calling Convention\n\n"); + fprintf(out, "| Register | ABI Name | Caller-Saved | Argument |\n"); + fprintf(out, "|----------|----------|-------------|----------|\n"); + for (int i = 0; i < db->num_registers; i++) { + const IsaRegister *r = &db->registers[i]; + fprintf(out, "| %s | %s | %s | %s |\n", + r->name, r->abbr[0] ? r->abbr : "—", + r->caller_saved ? "Yes" : "No", + r->arg_reg ? "Yes" : "No"); + } + fprintf(out, "\n\\newpage\n\n"); +} + +static void write_format_diagram(FILE *out, const IsaFormat *fmt) { + fprintf(out, "### %s-Type Format\n\n", fmt->name); + fprintf(out, "Bit width: **%d bits**\n\n", fmt->width); + fprintf(out, "```\n"); + + for (int row = 0; row < 3; row++) { + for (int j = fmt->num_fields - 1; j >= 0; j--) { + int fw = fmt->fields[j].high - fmt->fields[j].low + 1; + if (j < fmt->num_fields - 1) fputc('|', out); + + if (row == 0) { + int rpad = (fw - 5) / 2; + int lpad = fw - 5 - rpad; + if (rpad < 0) rpad = 0; + if (lpad < 0) lpad = 0; + for (int k = 0; k < rpad; k++) fputc(' ', out); + fprintf(out, "%d:%d", fmt->fields[j].high, fmt->fields[j].low); + for (int k = 0; k < lpad; k++) fputc(' ', out); + } else if (row == 1) { + int slen = (int)strlen(fmt->fields[j].name); + int rpad = (fw - slen) / 2; + int lpad = fw - slen - rpad; + if (rpad < 0) rpad = 0; + if (lpad < 0) lpad = 0; + for (int k = 0; k < rpad; k++) fputc(' ', out); + fprintf(out, "%s", fmt->fields[j].name); + for (int k = 0; k < lpad; k++) fputc(' ', out); + } else { + char buf[16]; + snprintf(buf, sizeof(buf), "%d", fw); + int slen = (int)strlen(buf); + int rpad = (fw - slen) / 2; + int lpad = fw - slen - rpad; + if (rpad < 0) rpad = 0; + if (lpad < 0) lpad = 0; + for (int k = 0; k < rpad; k++) fputc(' ', out); + fprintf(out, "%s", buf); + for (int k = 0; k < lpad; k++) fputc(' ', out); + } + } + fprintf(out, "\n"); + } + + fprintf(out, "```\n\n"); +} + +static void write_formats(FILE *out, const IsaDb *db) { + fprintf(out, "# Instruction Formats\n\n"); + fprintf(out, "Instructions are encoded in one of several fixed formats. " + "All formats share the same opcode placement (bits 6:0) to " + "allow efficient decoding.\n\n"); + + for (int i = 0; i < db->num_formats; i++) { + write_format_diagram(out, &db->formats[i]); + } + + fprintf(out, "\\newpage\n\n"); +} + +static int cat_cmp(const void *a, const void *b) { + return strcasecmp(((const IsaInstruction *)a)->category, + ((const IsaInstruction *)b)->category); +} + +static void write_instructions(FILE *out, const IsaDb *db) { + fprintf(out, "# Instruction Set\n\n"); + + if (db->num_instructions == 0) { + fprintf(out, "No instructions defined.\n\n"); + return; + } + + IsaInstruction *sorted = malloc(db->num_instructions * sizeof(IsaInstruction)); + if (!sorted) return; + memcpy(sorted, db->instructions, db->num_instructions * sizeof(IsaInstruction)); + qsort(sorted, db->num_instructions, sizeof(IsaInstruction), cat_cmp); + + char current_cat[MAX_LABEL] = ""; + for (int i = 0; i < db->num_instructions; i++) { + const IsaInstruction *inst = &sorted[i]; + const char *cat = inst->category[0] ? inst->category : "Uncategorized"; + + if (strcasecmp(current_cat, cat) != 0) { + strncpy(current_cat, cat, MAX_LABEL - 1); + fprintf(out, "## %s Operations\n\n", cat); + + fprintf(out, "| Mnemonic | Format | Opcode | Funct3 | Funct7 | Operands | Description |\n"); + fprintf(out, "|----------|--------|--------|--------|--------|----------|-------------|\n"); + } + + char f3buf[16], f7buf[16]; + if (inst->funct3_valid) snprintf(f3buf, sizeof(f3buf), "0x%X", inst->funct3); + else strcpy(f3buf, "—"); + if (inst->funct7_valid) snprintf(f7buf, sizeof(f7buf), "0x%02X", inst->funct7); + else strcpy(f7buf, "—"); + + fprintf(out, "| `%s` | %s | `0x%02X` | %s | %s | %s | %s |\n", + inst->name, + inst->fmt_name, + inst->opcode, + f3buf, f7buf, + inst->operands[0] ? inst->operands : "—", + inst->desc); + } + + fprintf(out, "\n\\newpage\n\n"); + + fprintf(out, "## Instruction Details\n\n"); + strcpy(current_cat, ""); + for (int i = 0; i < db->num_instructions; i++) { + const IsaInstruction *inst = &sorted[i]; + const char *cat = inst->category[0] ? inst->category : "Uncategorized"; + + if (strcasecmp(current_cat, cat) != 0) { + strncpy(current_cat, cat, MAX_LABEL - 1); + } + + fprintf(out, "### %s\n\n", inst->name); + fprintf(out, "- **Format:** %s\n", inst->fmt_name); + fprintf(out, "- **Opcode:** `0x%02X`\n", inst->opcode); + if (inst->funct3_valid) + fprintf(out, "- **Funct3:** `0x%X`\n", inst->funct3); + if (inst->funct7_valid) + fprintf(out, "- **Funct7:** `0x%02X`\n", inst->funct7); + fprintf(out, "- **Operands:** `%s`\n", inst->operands[0] ? inst->operands : "(none)"); + fprintf(out, "- **Description:** %s\n", inst->desc); + if (inst->note[0]) + fprintf(out, "- **Operation:** `%s`\n", inst->note); + fprintf(out, "\n"); + } + + free(sorted); + fprintf(out, "\\newpage\n\n"); +} + +static void write_opcode_map(FILE *out, const IsaDb *db) { + fprintf(out, "# Opcode Map\n\n"); + fprintf(out, "The following table shows the top-level opcode mapping. " + "The opcode occupies bits 6:0 of every instruction.\n\n"); + + fprintf(out, "| opcode[6:0] | Type | Instructions |\n"); + fprintf(out, "|-------------|------|-------------|\n"); + + int seen_ops[128] = {0}; + for (int i = 0; i < db->num_instructions; i++) { + const IsaInstruction *inst = &db->instructions[i]; + int op = inst->opcode & 0x7F; + if (seen_ops[op]) continue; + seen_ops[op] = 1; + + fprintf(out, "| `0x%02X` (%d) | %s | %s", op, op, inst->fmt_name, inst->name); + int count = 1; + for (int j = i + 1; j < db->num_instructions; j++) { + if ((db->instructions[j].opcode & 0x7F) == (unsigned)op) { + fprintf(out, ", %s", db->instructions[j].name); + count++; + } + } + fprintf(out, " (%d inst)%s |\n", count, count > 1 ? "s" : ""); + } + + fprintf(out, "\n### Encoding Matrix\n\n"); + fprintf(out, "| opcode \\ funct3 |"); + for (int f3 = 0; f3 < 8; f3++) fprintf(out, " %X |", f3); + fprintf(out, "\n"); + fprintf(out, "|"); + for (int f3 = 0; f3 < 9; f3++) fprintf(out, "---|"); + fprintf(out, "\n"); + + int seen_ops2[128] = {0}; + for (int i = 0; i < db->num_instructions; i++) { + const IsaInstruction *inst = &db->instructions[i]; + int op = inst->opcode & 0x7F; + if (seen_ops2[op]) continue; + seen_ops2[op] = 1; + + fprintf(out, "| `0x%02X` |", op); + for (int f3 = 0; f3 < 8; f3++) { + int found = 0; + for (int j = 0; j < db->num_instructions; j++) { + if ((db->instructions[j].opcode & 0x7F) == (unsigned)op && + db->instructions[j].funct3 == (unsigned)f3 && + db->instructions[j].funct3_valid) { + fprintf(out, " %s |", db->instructions[j].name); + found = 1; + break; + } + } + if (!found) fprintf(out, " — |"); + } + fprintf(out, "\n"); + } + + fprintf(out, "\n\\newpage\n\n"); +} + +static void write_csrs(FILE *out, const IsaDb *db) { + if (db->num_csrs == 0) return; + + fprintf(out, "# Control and Status Registers\n\n"); + fprintf(out, "The following CSRs are accessible via the `CSRRW`, `CSRRS`, " + "`CSRRC`, and their immediate variants.\n\n"); + + fprintf(out, "| Address | Name | Description |\n"); + fprintf(out, "|---------|------|-------------|\n"); + for (int i = 0; i < db->num_csrs; i++) { + fprintf(out, "| `0x%03X` | %s | %s |\n", + db->csrs[i].number, db->csrs[i].name, db->csrs[i].desc); + } + fprintf(out, "\n\\newpage\n\n"); +} + +static void write_exceptions(FILE *out) { + fprintf(out, "# Exception and Trap Handling\n\n"); + fprintf(out, "## Exception Types\n\n"); + fprintf(out, "| Exception Code | Name | Description |\n"); + fprintf(out, "|----------------|------|-------------|\n"); + fprintf(out, "| 0 | Instruction address misaligned | PC not aligned to 4 bytes |\n"); + fprintf(out, "| 1 | Instruction access fault | Failed to fetch instruction |\n"); + fprintf(out, "| 2 | Illegal instruction | Unrecognized opcode |\n"); + fprintf(out, "| 3 | Breakpoint | EBREAK instruction executed |\n"); + fprintf(out, "| 4 | Load address misaligned | Load address not properly aligned |\n"); + fprintf(out, "| 5 | Load access fault | Failed to load from memory |\n"); + fprintf(out, "| 6 | Store address misaligned | Store address not properly aligned |\n"); + fprintf(out, "| 7 | Store access fault | Failed to store to memory |\n"); + fprintf(out, "| 8 | Environment call from U-mode | ECALL in user mode |\n"); + fprintf(out, "| 9 | Environment call from S-mode | ECALL in supervisor mode |\n"); + fprintf(out, "| 11 | Environment call from M-mode | ECALL in machine mode |\n\n"); + + fprintf(out, "## Trap Vector\n\n"); + fprintf(out, "On taking a trap, the implementation:\n\n"); + fprintf(out, "1. Sets `mepc` to the PC of the trapping instruction (or next PC for ECALL/EBREAK)\n"); + fprintf(out, "2. Sets `mcause` to the exception code\n"); + fprintf(out, "3. Sets `mtval` to exception-specific information\n"); + fprintf(out, "4. Sets `mstatus.MPP` to the current privilege mode\n"); + fprintf(out, "5. Sets `mstatus.MPIE` to `mstatus.MIE`\n"); + fprintf(out, "6. Clears `mstatus.MIE`\n"); + fprintf(out, "7. Sets PC to `mtvec`\n\n"); + + fprintf(out, "\\newpage\n\n"); +} + +static void write_pseudo_instructions(FILE *out) { + fprintf(out, "# Pseudo-Instructions\n\n"); + fprintf(out, "| Pseudo-instruction | Expansion | Description |\n"); + fprintf(out, "|-------------------|-----------|-------------|\n"); + fprintf(out, "| `NOP` | `ADDI x0, x0, 0` | No operation |\n"); + fprintf(out, "| `MV rd, rs` | `ADDI rd, rs, 0` | Copy register |\n"); + fprintf(out, "| `NOT rd, rs` | `XORI rd, rs, -1` | Bitwise NOT |\n"); + fprintf(out, "| `NEG rd, rs` | `SUB rd, x0, rs` | Negate register |\n"); + fprintf(out, "| `LI rd, imm` | (multiple) | Load immediate |\n"); + fprintf(out, "| `LA rd, label` | (multiple) | Load address |\n"); + fprintf(out, "| `RET` | `JALR x0, x1, 0` | Return from subroutine |\n"); + fprintf(out, "| `CALL label` | (multiple) | Call subroutine |\n"); + fprintf(out, "| `J label` | `JAL x0, label` | Unconditional jump |\n"); + fprintf(out, "| `JR rs` | `JALR x0, rs, 0` | Jump register |\n"); + fprintf(out, "\n\\newpage\n\n"); +} + +static void write_quick_reference(FILE *out) { + fprintf(out, "# Quick Reference\n\n"); + fprintf(out, "## Instruction Encoding Quick Reference\n\n"); + + fprintf(out, "### R-Type\n\n"); + fprintf(out, "```\n"); + fprintf(out, " 31:27 | 26:25 | 24:20 | 19:15 | 14:12 | 11:7 | 6:0\n"); + fprintf(out, " funct7 | — | rs2 | rs1 | funct3 | rd | opcode\n"); + fprintf(out, " 7 | 2 | 5 | 5 | 3 | 5 | 7\n"); + fprintf(out, "```\n\n"); + + fprintf(out, "### I-Type\n\n"); + fprintf(out, "```\n"); + fprintf(out, " 31:20 | 19:15 | 14:12 | 11:7 | 6:0\n"); + fprintf(out, " imm[11:0]| rs1 | funct3 | rd | opcode\n"); + fprintf(out, " 12 | 5 | 3 | 5 | 7\n"); + fprintf(out, "```\n\n"); + + fprintf(out, "### S-Type\n\n"); + fprintf(out, "```\n"); + fprintf(out, " 31:27 | 26:25 | 24:20 | 19:15 | 14:12 | 11:7 | 6:0\n"); + fprintf(out, " imm[11:5]| — | rs2 | rs1 | funct3 | imm[4:0]| opcode\n"); + fprintf(out, " 7 | 2 | 5 | 5 | 3 | 5 | 7\n"); + fprintf(out, "```\n\n"); + + fprintf(out, "### B-Type\n\n"); + fprintf(out, "```\n"); + fprintf(out, " 31 | 30:27 | 26:25 | 24:20 | 19:15 | 14:12 | 11:8 | 7 | 6:0\n"); + fprintf(out, " imm[12]| imm[10:5]| — | rs2 | rs1 | funct3 | imm[4:1]| imm[11]| opcode\n"); + fprintf(out, " 1 | 6 | 2 | 5 | 5 | 3 | 4 | 1 | 7\n"); + fprintf(out, "```\n\n"); + + fprintf(out, "### U-Type\n\n"); + fprintf(out, "```\n"); + fprintf(out, " 31:12 | 11:7 | 6:0\n"); + fprintf(out, " imm[31:12] | rd | opcode\n"); + fprintf(out, " 20 | 5 | 7\n"); + fprintf(out, "```\n\n"); + + fprintf(out, "### J-Type\n\n"); + fprintf(out, "```\n"); + fprintf(out, " 31 | 30:21 | 20 | 19:12 | 11:7 | 6:0\n"); + fprintf(out, " imm[20]| imm[10:1]| imm[11]| imm[19:12]| rd | opcode\n"); + fprintf(out, " 1 | 10 | 1 | 8 | 5 | 7\n"); + fprintf(out, "```\n\n"); +} + +int gen_markdown(const IsaDb *db, const char *output_path) { + FILE *out = fopen(output_path, "w"); + if (!out) { + fprintf(stderr, "Error: cannot write to %s\n", output_path); + return -1; + } + + printf("Generating: %s\n", output_path); + printf(" Formats: %d\n", db->num_formats); + printf(" Registers: %d\n", db->num_registers); + printf(" Instructions: %d\n", db->num_instructions); + printf(" CSRs: %d\n", db->num_csrs); + + write_title_page(out, db); + write_revision_history(out, db); + write_toc_placeholder(out); + write_intro(out, db); + write_registers(out, db); + write_formats(out, db); + write_instructions(out, db); + write_opcode_map(out, db); + write_exceptions(out); + write_pseudo_instructions(out); + + if (db->num_csrs > 0) + write_csrs(out, db); + + write_quick_reference(out); + + fclose(out); + printf(" Done: %s\n", output_path); + return 0; +} + +int gen_html(const IsaDb *db, const char *output_path) { + FILE *out = fopen(output_path, "w"); + if (!out) { + fprintf(stderr, "Error: cannot write to %s\n", output_path); + return -1; + } + + fprintf(out, "\n\n\n"); + fprintf(out, "\n"); + fprintf(out, "%s ISA Reference Manual\n", + db->arch_name[0] ? db->arch_name : "Fyntv"); + fprintf(out, "\n"); + fprintf(out, "\n\n
\n"); + + fprintf(out, "
\n"); + fprintf(out, "

%s Instruction Set Architecture

\n", + db->arch_name[0] ? db->arch_name : "Fyntv"); + fprintf(out, "

Reference Manual

\n"); + fprintf(out, "

Version: %s  |  Date: %s

\n", + db->arch_version[0] ? db->arch_version : "1.0.0", + db->arch_date[0] ? db->arch_date : "June 2026"); + fprintf(out, "
\n"); + + fprintf(out, "
\n"); + fprintf(out, "

Registers

\n"); + fprintf(out, "\n"); + for (int i = 0; i < db->num_registers; i++) { + fprintf(out, "\n", + db->registers[i].name, + db->registers[i].abbr[0] ? db->registers[i].abbr : "—", + db->registers[i].desc[0] ? db->registers[i].desc : "—", + db->registers[i].preserve ? "Yes" : "No"); + } + fprintf(out, "
RegisterABI NameDescriptionSaved
%s%s%s%s
\n
\n"); + + fprintf(out, "
\n"); + fprintf(out, "

Instruction Set

\n"); + fprintf(out, "\n"); + for (int i = 0; i < db->num_instructions; i++) { + fprintf(out, "\n", + db->instructions[i].name, + db->instructions[i].fmt_name, + db->instructions[i].opcode, + db->instructions[i].operands[0] ? db->instructions[i].operands : "—", + db->instructions[i].desc); + } + fprintf(out, "
MnemonicFormatOpcodeOperandsDescription
%s%s0x%02X%s%s
\n
\n"); + + fprintf(out, "
\n\n\n"); + fclose(out); + return 0; +} diff --git a/gen/docs/generator.h b/gen/docs/generator.h new file mode 100644 index 0000000..407b47a --- /dev/null +++ b/gen/docs/generator.h @@ -0,0 +1,9 @@ +#ifndef GENERATOR_H +#define GENERATOR_H + +#include "types.h" + +int gen_markdown(const IsaDb *db, const char *output_path); +int gen_html(const IsaDb *db, const char *output_path); + +#endif diff --git a/gen/docs/isa-docgen b/gen/docs/isa-docgen new file mode 100755 index 0000000..c330db7 Binary files /dev/null and b/gen/docs/isa-docgen differ diff --git a/gen/docs/isa_defs/arch.isa b/gen/docs/isa_defs/arch.isa new file mode 100644 index 0000000..f1added --- /dev/null +++ b/gen/docs/isa_defs/arch.isa @@ -0,0 +1,68 @@ +# Fyntv Architecture Definition +ARCH Fyntv + NAME Fyntv + VERSION 0.0.0.1 + DATE June 2026 + STATUS Draft +END + +# ============================================================================= +# Instruction Formats +# ============================================================================= + +FORMAT R + WIDTH 32 + FIELD opcode 6:0 + FIELD rd 11:7 + FIELD funct3 14:12 + FIELD rs1 19:15 + FIELD rs2 24:20 + FIELD funct7 31:25 +END + +FORMAT I + WIDTH 32 + FIELD opcode 6:0 + FIELD rd 11:7 + FIELD funct3 14:12 + FIELD rs1 19:15 + FIELD imm 31:20 +END + +FORMAT S + WIDTH 32 + FIELD opcode 6:0 + FIELD imm_lo 11:7 + FIELD funct3 14:12 + FIELD rs1 19:15 + FIELD rs2 24:20 + FIELD imm_hi 31:25 +END + +FORMAT B + WIDTH 32 + FIELD opcode 6:0 + FIELD imm_4_1 11:7 + FIELD funct3 14:12 + FIELD rs1 19:15 + FIELD rs2 24:20 + FIELD imm_10_5 30:25 + FIELD imm_12 31:31 +END + +FORMAT U + WIDTH 32 + FIELD opcode 6:0 + FIELD rd 11:7 + FIELD imm 31:12 +END + +FORMAT J + WIDTH 32 + FIELD opcode 6:0 + FIELD rd 11:7 + FIELD imm_19_12 19:12 + FIELD imm_11 20:20 + FIELD imm_10_1 30:21 + FIELD imm_20 31:31 +END diff --git a/gen/docs/isa_defs/csrs.isa b/gen/docs/isa_defs/csrs.isa new file mode 100644 index 0000000..e2098ef --- /dev/null +++ b/gen/docs/isa_defs/csrs.isa @@ -0,0 +1,191 @@ +# Fyntv Control and Status Register Definitions + +CSR mstatus + NUMBER 0x300 + DESC Machine status register — holds global interrupt enable and privilege state +END + +CSR misa + NUMBER 0x301 + DESC Machine ISA register — encodes supported ISA extensions +END + +CSR medeleg + NUMBER 0x302 + DESC Machine exception delegation register +END + +CSR mideleg + NUMBER 0x303 + DESC Machine interrupt delegation register +END + +CSR mie + NUMBER 0x304 + DESC Machine interrupt-enable register +END + +CSR mtvec + NUMBER 0x305 + DESC Machine trap-handler base address +END + +CSR mcounteren + NUMBER 0x306 + DESC Machine counter enable register +END + +CSR mscratch + NUMBER 0x340 + DESC Scratch register for machine-mode trap handlers +END + +CSR mepc + NUMBER 0x341 + DESC Machine exception program counter — holds PC of trapping instruction +END + +CSR mcause + NUMBER 0x342 + DESC Machine trap cause — encodes exception or interrupt cause +END + +CSR mtval + NUMBER 0x343 + DESC Machine trap value — exception-specific information +END + +CSR mip + NUMBER 0x344 + DESC Machine interrupt pending register +END + +CSR pmpcfg0 + NUMBER 0x3A0 + DESC Physical memory protection configuration 0 +END + +CSR pmpaddr0 + NUMBER 0x3B0 + DESC Physical memory protection address 0 +END + +CSR pmpaddr1 + NUMBER 0x3B1 + DESC Physical memory protection address 1 +END + +CSR pmpaddr2 + NUMBER 0x3B2 + DESC Physical memory protection address 2 +END + +CSR pmpaddr3 + NUMBER 0x3B3 + DESC Physical memory protection address 3 +END + +CSR mcycle + NUMBER 0xB00 + DESC Machine cycle counter — counts number of clock cycles +END + +CSR minstret + NUMBER 0xB02 + DESC Machine instructions-retired counter +END + +CSR mhpmcounter3 + NUMBER 0xB03 + DESC Machine hardware performance counter 3 +END + +CSR mhpmcounter4 + NUMBER 0xB04 + DESC Machine hardware performance counter 4 +END + +CSR mhpmcounter5 + NUMBER 0xB05 + DESC Machine hardware performance counter 5 +END + +CSR mhpmevent3 + NUMBER 0x323 + DESC Machine hardware performance event selector 3 +END + +CSR mhpmevent4 + NUMBER 0x324 + DESC Machine hardware performance event selector 4 +END + +CSR mhpmevent5 + NUMBER 0x325 + DESC Machine hardware performance event selector 5 +END + +CSR ucycle + NUMBER 0xC00 + DESC User-mode cycle counter read +END + +CSR uinstret + NUMBER 0xC02 + DESC User-mode instructions-retired read +END + +CSR ucycleh + NUMBER 0xC80 + DESC Upper 32 bits of user-mode cycle counter +END + +CSR uinstreth + NUMBER 0xC82 + DESC Upper 32 bits of user-mode instructions-retired counter +END + +CSR sstatus + NUMBER 0x100 + DESC Supervisor status register +END + +CSR sie + NUMBER 0x104 + DESC Supervisor interrupt-enable register +END + +CSR stvec + NUMBER 0x105 + DESC Supervisor trap-handler base address +END + +CSR sscratch + NUMBER 0x140 + DESC Scratch register for supervisor-mode trap handlers +END + +CSR sepc + NUMBER 0x141 + DESC Supervisor exception program counter +END + +CSR scause + NUMBER 0x142 + DESC Supervisor trap cause register +END + +CSR stval + NUMBER 0x143 + DESC Supervisor trap value register +END + +CSR sip + NUMBER 0x144 + DESC Supervisor interrupt pending register +END + +CSR satp + NUMBER 0x180 + DESC Supervisor address translation and protection — controls page tables +END diff --git a/gen/docs/isa_defs/instructions.isa b/gen/docs/isa_defs/instructions.isa new file mode 100644 index 0000000..c7255d3 --- /dev/null +++ b/gen/docs/isa_defs/instructions.isa @@ -0,0 +1,512 @@ +# Fyntv Core Integer Instructions + +INSTRUCTION ADD + FORMAT R + OPCODE 0x33 + FUNCT3 0x0 + FUNCT7 0x00 + OPERANDS rd, rs1, rs2 + DESC Add registers + NOTE rd = rs1 + rs2 + CATEGORY Integer +END + +INSTRUCTION SUB + FORMAT R + OPCODE 0x33 + FUNCT3 0x0 + FUNCT7 0x20 + OPERANDS rd, rs1, rs2 + DESC Subtract registers + NOTE rd = rs1 - rs2 + CATEGORY Integer +END + +INSTRUCTION ADDI + FORMAT I + OPCODE 0x13 + FUNCT3 0x0 + OPERANDS rd, rs1, imm12 + DESC Add sign-extended 12-bit immediate to register rs1 + NOTE rd = rs1 + sext(imm12) + CATEGORY Integer + IMM true +END + +INSTRUCTION SLT + FORMAT R + OPCODE 0x33 + FUNCT3 0x2 + FUNCT7 0x00 + OPERANDS rd, rs1, rs2 + DESC Set if rs1 is less than rs2 (signed) + NOTE rd = (rs1 < rs2) ? 1 : 0 + CATEGORY Integer +END + +INSTRUCTION SLTU + FORMAT R + OPCODE 0x33 + FUNCT3 0x3 + FUNCT7 0x00 + OPERANDS rd, rs1, rs2 + DESC Set if rs1 is less than rs2 (unsigned) + NOTE rd = (rs1 < rs2) ? 1 : 0 + CATEGORY Integer +END + +INSTRUCTION SLTI + FORMAT I + OPCODE 0x13 + FUNCT3 0x2 + OPERANDS rd, rs1, imm12 + DESC Set if rs1 is less than immediate (signed) + NOTE rd = (rs1 < sext(imm12)) ? 1 : 0 + CATEGORY Integer + IMM true +END + +INSTRUCTION SLTIU + FORMAT I + OPCODE 0x13 + FUNCT3 0x3 + OPERANDS rd, rs1, imm12 + DESC Set if rs1 is less than immediate (unsigned) + NOTE rd = (rs1 < sext(imm12)) ? 1 : 0 + CATEGORY Integer + IMM true +END + +INSTRUCTION LUI + FORMAT U + OPCODE 0x37 + OPERANDS rd, imm20 + DESC Load upper immediate — places 20-bit immediate in upper 20 bits of rd + NOTE rd = imm20 << 12 + CATEGORY Integer +END + +INSTRUCTION AUIPC + FORMAT U + OPCODE 0x17 + OPERANDS rd, imm20 + DESC Add upper immediate to PC — forms PC-relative address + NOTE rd = PC + (imm20 << 12) + CATEGORY Integer +END + +# ============================================================================= +# Logical Instructions +# ============================================================================= + +INSTRUCTION AND + FORMAT R + OPCODE 0x33 + FUNCT3 0x7 + FUNCT7 0x00 + OPERANDS rd, rs1, rs2 + DESC Bitwise AND + NOTE rd = rs1 & rs2 + CATEGORY Logical +END + +INSTRUCTION OR + FORMAT R + OPCODE 0x33 + FUNCT3 0x6 + FUNCT7 0x00 + OPERANDS rd, rs1, rs2 + DESC Bitwise OR + NOTE rd = rs1 | rs2 + CATEGORY Logical +END + +INSTRUCTION XOR + FORMAT R + OPCODE 0x33 + FUNCT3 0x4 + FUNCT7 0x00 + OPERANDS rd, rs1, rs2 + DESC Bitwise XOR + NOTE rd = rs1 ^ rs2 + CATEGORY Logical +END + +INSTRUCTION ANDI + FORMAT I + OPCODE 0x13 + FUNCT3 0x7 + OPERANDS rd, rs1, imm12 + DESC Bitwise AND with immediate + NOTE rd = rs1 & sext(imm12) + CATEGORY Logical + IMM true +END + +INSTRUCTION ORI + FORMAT I + OPCODE 0x13 + FUNCT3 0x6 + OPERANDS rd, rs1, imm12 + DESC Bitwise OR with immediate + NOTE rd = rs1 | sext(imm12) + CATEGORY Logical + IMM true +END + +INSTRUCTION XORI + FORMAT I + OPCODE 0x13 + FUNCT3 0x4 + OPERANDS rd, rs1, imm12 + DESC Bitwise XOR with immediate + NOTE rd = rs1 ^ sext(imm12) + CATEGORY Logical + IMM true +END + +# ============================================================================= +# Shift Instructions +# ============================================================================= + +INSTRUCTION SLL + FORMAT R + OPCODE 0x33 + FUNCT3 0x1 + FUNCT7 0x00 + OPERANDS rd, rs1, rs2 + DESC Logical left shift by lower 5 bits of rs2 + NOTE rd = rs1 << rs2[4:0] + CATEGORY Shift +END + +INSTRUCTION SRL + FORMAT R + OPCODE 0x33 + FUNCT3 0x5 + FUNCT7 0x00 + OPERANDS rd, rs1, rs2 + DESC Logical right shift by lower 5 bits of rs2 + NOTE rd = rs1 >> rs2[4:0] + CATEGORY Shift +END + +INSTRUCTION SRA + FORMAT R + OPCODE 0x33 + FUNCT3 0x5 + FUNCT7 0x20 + OPERANDS rd, rs1, rs2 + DESC Arithmetic right shift by lower 5 bits of rs2 + NOTE rd = rs1 >>> rs2[4:0] + CATEGORY Shift +END + +INSTRUCTION SLLI + FORMAT I + OPCODE 0x13 + FUNCT3 0x1 + OPERANDS rd, rs1, shamt5 + DESC Logical left shift by immediate shift amount + NOTE rd = rs1 << shamt + CATEGORY Shift +END + +INSTRUCTION SRLI + FORMAT I + OPCODE 0x13 + FUNCT3 0x5 + OPERANDS rd, rs1, shamt5 + DESC Logical right shift by immediate shift amount + NOTE rd = rs1 >> shamt + CATEGORY Shift +END + +INSTRUCTION SRAI + FORMAT I + OPCODE 0x13 + FUNCT3 0x5 + OPERANDS rd, rs1, shamt5 + DESC Arithmetic right shift by immediate shift amount + NOTE rd = rs1 >>> shamt + CATEGORY Shift +END + +# ============================================================================= +# Memory Instructions +# ============================================================================= + +INSTRUCTION LB + FORMAT I + OPCODE 0x03 + FUNCT3 0x0 + OPERANDS rd, offset(rs1) + DESC Load byte (sign-extended) + NOTE rd = sext(MEM[rs1 + offset][7:0]) + CATEGORY Memory +END + +INSTRUCTION LH + FORMAT I + OPCODE 0x03 + FUNCT3 0x1 + OPERANDS rd, offset(rs1) + DESC Load halfword (sign-extended) + NOTE rd = sext(MEM[rs1 + offset][15:0]) + CATEGORY Memory +END + +INSTRUCTION LW + FORMAT I + OPCODE 0x03 + FUNCT3 0x2 + OPERANDS rd, offset(rs1) + DESC Load word + NOTE rd = MEM[rs1 + offset][31:0] + CATEGORY Memory +END + +INSTRUCTION LBU + FORMAT I + OPCODE 0x03 + FUNCT3 0x4 + OPERANDS rd, offset(rs1) + DESC Load byte (zero-extended) + NOTE rd = MEM[rs1 + offset][7:0] + CATEGORY Memory +END + +INSTRUCTION LHU + FORMAT I + OPCODE 0x03 + FUNCT3 0x5 + OPERANDS rd, offset(rs1) + DESC Load halfword (zero-extended) + NOTE rd = MEM[rs1 + offset][15:0] + CATEGORY Memory +END + +INSTRUCTION SB + FORMAT S + OPCODE 0x23 + FUNCT3 0x0 + OPERANDS rs2, offset(rs1) + DESC Store byte + NOTE MEM[rs1 + offset][7:0] = rs2[7:0] + CATEGORY Memory +END + +INSTRUCTION SH + FORMAT S + OPCODE 0x23 + FUNCT3 0x1 + OPERANDS rs2, offset(rs1) + DESC Store halfword + NOTE MEM[rs1 + offset][15:0] = rs2[15:0] + CATEGORY Memory +END + +INSTRUCTION SW + FORMAT S + OPCODE 0x23 + FUNCT3 0x2 + OPERANDS rs2, offset(rs1) + DESC Store word + NOTE MEM[rs1 + offset][31:0] = rs2[31:0] + CATEGORY Memory +END + +# ============================================================================= +# Branch Instructions +# ============================================================================= + +INSTRUCTION BEQ + FORMAT B + OPCODE 0x63 + FUNCT3 0x0 + OPERANDS rs1, rs2, label + DESC Branch equal + NOTE if (rs1 == rs2) PC += sext(offset) + CATEGORY Branch +END + +INSTRUCTION BNE + FORMAT B + OPCODE 0x63 + FUNCT3 0x1 + OPERANDS rs1, rs2, label + DESC Branch not equal + NOTE if (rs1 != rs2) PC += sext(offset) + CATEGORY Branch +END + +INSTRUCTION BLT + FORMAT B + OPCODE 0x63 + FUNCT3 0x4 + OPERANDS rs1, rs2, label + DESC Branch less than (signed) + NOTE if (rs1 < rs2) PC += sext(offset) + CATEGORY Branch +END + +INSTRUCTION BGE + FORMAT B + OPCODE 0x63 + FUNCT3 0x5 + OPERANDS rs1, rs2, label + DESC Branch greater than or equal (signed) + NOTE if (rs1 >= rs2) PC += sext(offset) + CATEGORY Branch +END + +INSTRUCTION BLTU + FORMAT B + OPCODE 0x63 + FUNCT3 0x6 + OPERANDS rs1, rs2, label + DESC Branch less than (unsigned) + NOTE if (rs1 < rs2) PC += sext(offset) + CATEGORY Branch +END + +INSTRUCTION BGEU + FORMAT B + OPCODE 0x63 + FUNCT3 0x7 + OPERANDS rs1, rs2, label + DESC Branch greater than or equal (unsigned) + NOTE if (rs1 >= rs2) PC += sext(offset) + CATEGORY Branch +END + +# ============================================================================= +# Jump Instructions +# ============================================================================= + +INSTRUCTION JAL + FORMAT J + OPCODE 0x6F + OPERANDS rd, label + DESC Jump and link — jump to PC+offset, save return address to rd + NOTE rd = PC + 4; PC += sext(offset) + CATEGORY Jump +END + +INSTRUCTION JALR + FORMAT I + OPCODE 0x67 + FUNCT3 0x0 + OPERANDS rd, rs1, offset + DESC Jump and link register — jump to rs1+offset, save return address + NOTE rd = PC + 4; PC = (rs1 + sext(offset)) & ~1 + CATEGORY Jump +END + +# ============================================================================= +# Synchronization Instructions +# ============================================================================= + +INSTRUCTION FENCE + FORMAT I + OPCODE 0x0F + FUNCT3 0x0 + OPERANDS pred, succ + DESC Memory ordering fence + NOTE Orders memory accesses as specified by pred and succ fields + CATEGORY Synchronization +END + +INSTRUCTION FENCEI + FORMAT I + OPCODE 0x0F + FUNCT3 0x1 + OPERANDS — + DESC Instruction fence — synchronizes instruction and data streams + NOTE Flushes instruction cache after data writes + CATEGORY Synchronization +END + +# ============================================================================= +# System Instructions +# ============================================================================= + +INSTRUCTION ECALL + FORMAT I + OPCODE 0x73 + FUNCT3 0x0 + OPERANDS — + DESC Environment call — raises a system call exception + NOTE Traps to the configured exception handler + CATEGORY System +END + +INSTRUCTION EBREAK + FORMAT I + OPCODE 0x73 + FUNCT3 0x0 + OPERANDS — + DESC Breakpoint — raises a breakpoint exception + NOTE Used by debuggers to halt program execution + CATEGORY System +END + +INSTRUCTION CSRRW + FORMAT I + OPCODE 0x73 + FUNCT3 0x1 + OPERANDS rd, csr, rs1 + DESC Atomic read/write CSR — writes rs1 to CSR, returns old value in rd + NOTE rd = CSR[csr]; CSR[csr] = rs1 + CATEGORY System +END + +INSTRUCTION CSRRS + FORMAT I + OPCODE 0x73 + FUNCT3 0x2 + OPERANDS rd, csr, rs1 + DESC Atomic read and set bits in CSR + NOTE rd = CSR[csr]; CSR[csr] |= rs1 + CATEGORY System +END + +INSTRUCTION CSRRC + FORMAT I + OPCODE 0x73 + FUNCT3 0x3 + OPERANDS rd, csr, rs1 + DESC Atomic read and clear bits in CSR + NOTE rd = CSR[csr]; CSR[csr] &= ~rs1 + CATEGORY System +END + +INSTRUCTION CSRRWI + FORMAT I + OPCODE 0x73 + FUNCT3 0x5 + OPERANDS rd, csr, uimm5 + DESC Atomic read/write CSR with immediate + NOTE rd = CSR[csr]; CSR[csr] = zimm + CATEGORY System +END + +INSTRUCTION CSRRSI + FORMAT I + OPCODE 0x73 + FUNCT3 0x6 + OPERANDS rd, csr, uimm5 + DESC Atomic read and set bits in CSR with immediate + NOTE rd = CSR[csr]; CSR[csr] |= zimm + CATEGORY System +END + +INSTRUCTION CSRRCI + FORMAT I + OPCODE 0x73 + FUNCT3 0x7 + OPERANDS rd, csr, uimm5 + DESC Atomic read and clear bits in CSR with immediate + NOTE rd = CSR[csr]; CSR[csr] &= ~zimm + CATEGORY System +END diff --git a/gen/docs/isa_defs/registers.isa b/gen/docs/isa_defs/registers.isa new file mode 100644 index 0000000..baa8124 --- /dev/null +++ b/gen/docs/isa_defs/registers.isa @@ -0,0 +1,289 @@ +# Fyntv Register Definitions + +REGISTER x0 + ABBR zero + DESC Always-zero register — reads return 0, writes are discarded + PRESERVE false + CALLER false + ARG false + INDEX 0 +END + +REGISTER x1 + ABBR ra + DESC Return address link register + PRESERVE false + CALLER false + ARG false + INDEX 1 +END + +REGISTER x2 + ABBR sp + DESC Stack pointer + PRESERVE true + CALLER false + ARG false + INDEX 2 +END + +REGISTER x3 + ABBR gp + DESC Global pointer + PRESERVE true + CALLER false + ARG false + INDEX 3 +END + +REGISTER x4 + ABBR tp + DESC Thread pointer + PRESERVE true + CALLER false + ARG false + INDEX 4 +END + +REGISTER x5 + ABBR t0 + DESC Temporary register 0 + PRESERVE false + CALLER true + ARG false + INDEX 5 +END + +REGISTER x6 + ABBR t1 + DESC Temporary register 1 + PRESERVE false + CALLER true + ARG false + INDEX 6 +END + +REGISTER x7 + ABBR t2 + DESC Temporary register 2 + PRESERVE false + CALLER true + ARG false + INDEX 7 +END + +REGISTER x8 + ABBR s0 + DESC Saved register 0 / frame pointer + PRESERVE true + CALLER false + ARG false + INDEX 8 +END + +REGISTER x9 + ABBR s1 + DESC Saved register 1 + PRESERVE true + CALLER false + ARG false + INDEX 9 +END + +REGISTER x10 + ABBR a0 + DESC Function argument 0 / return value 0 + PRESERVE false + CALLER true + ARG true + INDEX 10 +END + +REGISTER x11 + ABBR a1 + DESC Function argument 1 / return value 1 + PRESERVE false + CALLER true + ARG true + INDEX 11 +END + +REGISTER x12 + ABBR a2 + DESC Function argument 2 + PRESERVE false + CALLER true + ARG true + INDEX 12 +END + +REGISTER x13 + ABBR a3 + DESC Function argument 3 + PRESERVE false + CALLER true + ARG true + INDEX 13 +END + +REGISTER x14 + ABBR a4 + DESC Function argument 4 + PRESERVE false + CALLER true + ARG true + INDEX 14 +END + +REGISTER x15 + ABBR a5 + DESC Function argument 5 + PRESERVE false + CALLER true + ARG true + INDEX 15 +END + +REGISTER x16 + ABBR a6 + DESC Function argument 6 + PRESERVE false + CALLER true + ARG true + INDEX 16 +END + +REGISTER x17 + ABBR a7 + DESC Function argument 7 + PRESERVE false + CALLER true + ARG true + INDEX 17 +END + +REGISTER x18 + ABBR s2 + DESC Saved register 2 + PRESERVE true + CALLER false + ARG false + INDEX 18 +END + +REGISTER x19 + ABBR s3 + DESC Saved register 3 + PRESERVE true + CALLER false + ARG false + INDEX 19 +END + +REGISTER x20 + ABBR s4 + DESC Saved register 4 + PRESERVE true + CALLER false + ARG false + INDEX 20 +END + +REGISTER x21 + ABBR s5 + DESC Saved register 5 + PRESERVE true + CALLER false + ARG false + INDEX 21 +END + +REGISTER x22 + ABBR s6 + DESC Saved register 6 + PRESERVE true + CALLER false + ARG false + INDEX 22 +END + +REGISTER x23 + ABBR s7 + DESC Saved register 7 + PRESERVE true + CALLER false + ARG false + INDEX 23 +END + +REGISTER x24 + ABBR s8 + DESC Saved register 8 + PRESERVE true + CALLER false + ARG false + INDEX 24 +END + +REGISTER x25 + ABBR s9 + DESC Saved register 9 + PRESERVE true + CALLER false + ARG false + INDEX 25 +END + +REGISTER x26 + ABBR s10 + DESC Saved register 10 + PRESERVE true + CALLER false + ARG false + INDEX 26 +END + +REGISTER x27 + ABBR s11 + DESC Saved register 11 + PRESERVE true + CALLER false + ARG false + INDEX 27 +END + +REGISTER x28 + ABBR t3 + DESC Temporary register 3 + PRESERVE false + CALLER true + ARG false + INDEX 28 +END + +REGISTER x29 + ABBR t4 + DESC Temporary register 4 + PRESERVE false + CALLER true + ARG false + INDEX 29 +END + +REGISTER x30 + ABBR t5 + DESC Temporary register 5 + PRESERVE false + CALLER true + ARG false + INDEX 30 +END + +REGISTER x31 + ABBR t6 + DESC Temporary register 6 + PRESERVE false + CALLER true + ARG false + INDEX 31 +END diff --git a/gen/docs/main.c b/gen/docs/main.c new file mode 100644 index 0000000..760253e --- /dev/null +++ b/gen/docs/main.c @@ -0,0 +1,83 @@ +#include "parser.h" +#include "generator.h" +#include +#include +#include + +static void print_usage(const char *prog) { + fprintf(stderr, "Usage: %s [options] \n\n", prog); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -o Output Markdown file (default: doc/spec/isa_reference.md)\n"); + fprintf(stderr, " --html Also generate HTML output\n"); + fprintf(stderr, " --help Show this help\n\n"); + fprintf(stderr, "Generates professional ISA reference documentation from .isa definition files.\n"); +} + +int main(int argc, char **argv) { + const char *defs_dir = NULL; + const char *output = "doc/spec/isa_reference.md"; + int gen_html_flag = 0; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--help") == 0) { + print_usage(argv[0]); + return 0; + } else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) { + output = argv[++i]; + } else if (strcmp(argv[i], "--html") == 0) { + gen_html_flag = 1; + } else if (argv[i][0] != '-') { + defs_dir = argv[i]; + } else { + fprintf(stderr, "Unknown option: %s\n", argv[i]); + print_usage(argv[0]); + return 1; + } + } + + if (!defs_dir) { + fprintf(stderr, "Error: no ISA definitions directory specified.\n\n"); + print_usage(argv[0]); + return 1; + } + + IsaDb db; + memset(&db, 0, sizeof(db)); + + printf("ISA Documentation Generator\n"); + printf("Reading definitions from: %s\n", defs_dir); + + if (isa_parse_dir(defs_dir, &db) != 0) { + fprintf(stderr, "Error: failed to parse ISA definitions.\n"); + return 1; + } + + printf("\nParsed:\n"); + printf(" %d format(s)\n", db.num_formats); + printf(" %d register(s)\n", db.num_registers); + printf(" %d instruction(s)\n", db.num_instructions); + printf(" %d CSR(s)\n", db.num_csrs); + + if (db.num_instructions == 0) { + fprintf(stderr, "Warning: no instructions loaded. Output will be sparse.\n"); + } + + if (gen_markdown(&db, output) != 0) { + fprintf(stderr, "Error: failed to generate Markdown.\n"); + return 1; + } + + if (gen_html_flag) { + char html_path[1024]; + snprintf(html_path, sizeof(html_path), "%s.html", output); + const char *ext = strrchr(output, '.'); + if (ext) { + size_t len = ext - output; + snprintf(html_path, sizeof(html_path), "%.*s.html", (int)len, output); + } + gen_html(&db, html_path); + } + + printf("\nDone. Output: %s\n", output); + return 0; +} diff --git a/gen/docs/parser.c b/gen/docs/parser.c new file mode 100644 index 0000000..2852070 --- /dev/null +++ b/gen/docs/parser.c @@ -0,0 +1,256 @@ +#include "parser.h" +#include +#include +#include +#include +#include + +static void str_trim(char *s) { + char *e; + while (isspace((unsigned char)*s)) s++; + if (*s == 0) return; + e = s + strlen(s) - 1; + while (e > s && isspace((unsigned char)*e)) e--; + *(e + 1) = '\0'; +} + +static int str_split(const char *line, char **key, char **val) { + static char buf[MAX_LINE]; + strncpy(buf, line, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + char *eq = strchr(buf, ' '); + if (!eq) { *key = buf; *val = ""; return 0; } + *eq = '\0'; + *key = buf; + *val = eq + 1; + str_trim(*key); + str_trim(*val); + return 1; +} + +static int str_to_int(const char *s) { + if (strncmp(s, "0x", 2) == 0 || strncmp(s, "0X", 2) == 0) + return (int)strtol(s, NULL, 16); + return atoi(s); +} + +static int str_to_bool(const char *s) { + return strcmp(s, "true") == 0 || strcmp(s, "yes") == 0 || strcmp(s, "1") == 0; +} + +static int parse_block(FILE *f, const char *block_type, const char *block_name, IsaDb *db) { + char line[MAX_LINE]; + + if (strcasecmp(block_type, "FORMAT") == 0) { + if (db->num_formats >= MAX_FMTS) { + fprintf(stderr, "Too many formats (max %d)\n", MAX_FMTS); + return -1; + } + IsaFormat *fmt = &db->formats[db->num_formats]; + strncpy(fmt->name, block_name, MAX_NAME - 1); + fmt->width = 32; + fmt->num_fields = 0; + + while (fgets(line, sizeof(line), f)) { + char *trimmed = line; + while (isspace((unsigned char)*trimmed)) trimmed++; + if (*trimmed == '#' || *trimmed == '\n') continue; + if (strncmp(trimmed, "END", 3) == 0) break; + + char *key, *val; + str_split(trimmed, &key, &val); + + if (strcasecmp(key, "WIDTH") == 0) { + fmt->width = str_to_int(val); + } else if (strcasecmp(key, "FIELD") == 0) { + if (fmt->num_fields >= MAX_FIELDS) { + fprintf(stderr, "Too many fields in format %s\n", fmt->name); + continue; + } + char fname[64]; + int high, low; + if (sscanf(val, "%63s %d:%d", fname, &high, &low) >= 3) { + strncpy(fmt->fields[fmt->num_fields].name, fname, MAX_LABEL - 1); + fmt->fields[fmt->num_fields].high = high; + fmt->fields[fmt->num_fields].low = low; + fmt->num_fields++; + } + } + } + db->num_formats++; + } else if (strcasecmp(block_type, "REGISTER") == 0) { + if (db->num_registers >= MAX_REGS) { + fprintf(stderr, "Too many registers (max %d)\n", MAX_REGS); + return -1; + } + IsaRegister *reg = &db->registers[db->num_registers]; + memset(reg, 0, sizeof(*reg)); + strncpy(reg->name, block_name, MAX_NAME - 1); + reg->preserve = -1; + + while (fgets(line, sizeof(line), f)) { + char *trimmed = line; + while (isspace((unsigned char)*trimmed)) trimmed++; + if (*trimmed == '#' || *trimmed == '\n') continue; + if (strncmp(trimmed, "END", 3) == 0) break; + + char *key, *val; + str_split(trimmed, &key, &val); + + if (strcasecmp(key, "ABBR") == 0) + strncpy(reg->abbr, val, MAX_LABEL - 1); + else if (strcasecmp(key, "DESC") == 0) + strncpy(reg->desc, val, MAX_DESC - 1); + else if (strcasecmp(key, "PRESERVE") == 0) + reg->preserve = str_to_bool(val); + else if (strcasecmp(key, "CALLER") == 0) + reg->caller_saved = str_to_bool(val); + else if (strcasecmp(key, "ARG") == 0) + reg->arg_reg = str_to_bool(val); + else if (strcasecmp(key, "INDEX") == 0) + reg->index = str_to_int(val); + } + if (reg->preserve == -1) reg->preserve = 0; + db->num_registers++; + } else if (strcasecmp(block_type, "INSTRUCTION") == 0) { + if (db->num_instructions >= MAX_INSTS) { + fprintf(stderr, "Too many instructions (max %d)\n", MAX_INSTS); + return -1; + } + IsaInstruction *inst = &db->instructions[db->num_instructions]; + memset(inst, 0, sizeof(*inst)); + strncpy(inst->name, block_name, MAX_NAME - 1); + inst->funct3_valid = 0; + inst->funct7_valid = 0; + + while (fgets(line, sizeof(line), f)) { + char *trimmed = line; + while (isspace((unsigned char)*trimmed)) trimmed++; + if (*trimmed == '#' || *trimmed == '\n') continue; + if (strncmp(trimmed, "END", 3) == 0) break; + + char *key, *val; + str_split(trimmed, &key, &val); + + if (strcasecmp(key, "FORMAT") == 0) + strncpy(inst->fmt_name, val, MAX_NAME - 1); + else if (strcasecmp(key, "OPCODE") == 0) + inst->opcode = (uint32_t)str_to_int(val); + else if (strcasecmp(key, "FUNCT3") == 0) { + inst->funct3 = (uint32_t)str_to_int(val); + inst->funct3_valid = 1; + } else if (strcasecmp(key, "FUNCT7") == 0) { + inst->funct7 = (uint32_t)str_to_int(val); + inst->funct7_valid = 1; + } else if (strcasecmp(key, "OPERANDS") == 0) + strncpy(inst->operands, val, MAX_OPERANDS - 1); + else if (strcasecmp(key, "DESC") == 0) + strncpy(inst->desc, val, MAX_DESC - 1); + else if (strcasecmp(key, "NOTE") == 0) + strncpy(inst->note, val, MAX_NOTE - 1); + else if (strcasecmp(key, "CATEGORY") == 0) + strncpy(inst->category, val, MAX_LABEL - 1); + else if (strcasecmp(key, "IMM") == 0) + inst->has_imm = str_to_bool(val); + } + db->num_instructions++; + } else if (strcasecmp(block_type, "CSR") == 0) { + if (db->num_csrs >= MAX_CSRS) { + fprintf(stderr, "Too many CSRs (max %d)\n", MAX_CSRS); + return -1; + } + IsaCsr *csr = &db->csrs[db->num_csrs]; + memset(csr, 0, sizeof(*csr)); + strncpy(csr->name, block_name, MAX_NAME - 1); + + while (fgets(line, sizeof(line), f)) { + char *trimmed = line; + while (isspace((unsigned char)*trimmed)) trimmed++; + if (*trimmed == '#' || *trimmed == '\n') continue; + if (strncmp(trimmed, "END", 3) == 0) break; + + char *key, *val; + str_split(trimmed, &key, &val); + + if (strcasecmp(key, "NUMBER") == 0) + csr->number = str_to_int(val); + else if (strcasecmp(key, "DESC") == 0) + strncpy(csr->desc, val, MAX_DESC - 1); + } + db->num_csrs++; + } else if (strcasecmp(block_type, "ARCH") == 0) { + while (fgets(line, sizeof(line), f)) { + char *trimmed = line; + while (isspace((unsigned char)*trimmed)) trimmed++; + if (*trimmed == '#' || *trimmed == '\n') continue; + if (strncmp(trimmed, "END", 3) == 0) break; + + char *key, *val; + str_split(trimmed, &key, &val); + + if (strcasecmp(key, "NAME") == 0) + strncpy(db->arch_name, val, MAX_NAME - 1); + else if (strcasecmp(key, "VERSION") == 0) + strncpy(db->arch_version, val, MAX_LABEL - 1); + else if (strcasecmp(key, "DATE") == 0) + strncpy(db->arch_date, val, MAX_LABEL - 1); + else if (strcasecmp(key, "STATUS") == 0) + strncpy(db->arch_status, val, MAX_LABEL - 1); + } + } + + return 0; +} + +int isa_parse_file(const char *path, IsaDb *db) { + FILE *f = fopen(path, "r"); + if (!f) { + fprintf(stderr, "Error: cannot open %s\n", path); + return -1; + } + + char line[MAX_LINE]; + int line_num = 0; + + while (fgets(line, sizeof(line), f)) { + line_num++; + char *trimmed = line; + while (isspace((unsigned char)*trimmed)) trimmed++; + if (*trimmed == '#' || *trimmed == '\n' || *trimmed == '\r') continue; + + char type[MAX_LABEL], name[MAX_NAME]; + if (sscanf(trimmed, "%31s %63s", type, name) >= 2) { + if (parse_block(f, type, name, db) != 0) { + fprintf(stderr, "Error parsing %s in %s line %d\n", type, path, line_num); + fclose(f); + return -1; + } + } + } + + fclose(f); + return 0; +} + +int isa_parse_dir(const char *dir, IsaDb *db) { + DIR *d = opendir(dir); + if (!d) { + fprintf(stderr, "Error: cannot open directory %s\n", dir); + return -1; + } + + struct dirent *entry; + while ((entry = readdir(d)) != NULL) { + if (entry->d_type != DT_REG) continue; + const char *ext = strrchr(entry->d_name, '.'); + if (!ext || strcasecmp(ext, ".isa") != 0) continue; + + char path[MAX_PATH]; + snprintf(path, sizeof(path), "%s/%s", dir, entry->d_name); + printf(" Parsing: %s\n", path); + isa_parse_file(path, db); + } + + closedir(d); + return 0; +} diff --git a/gen/docs/parser.h b/gen/docs/parser.h new file mode 100644 index 0000000..53a27fc --- /dev/null +++ b/gen/docs/parser.h @@ -0,0 +1,9 @@ +#ifndef PARSER_H +#define PARSER_H + +#include "types.h" + +int isa_parse_file(const char *path, IsaDb *db); +int isa_parse_dir(const char *dir, IsaDb *db); + +#endif diff --git a/gen/docs/style.css b/gen/docs/style.css new file mode 100644 index 0000000..305b00b --- /dev/null +++ b/gen/docs/style.css @@ -0,0 +1,139 @@ +/* style.css — Professional styling for ISA reference HTML (used by wkhtmltopdf) */ + +body { + font-family: "Times New Roman", Times, serif; + font-size: 11pt; + line-height: 1.5; + color: #1a1a1a; + max-width: 7in; + margin: 0 auto; + padding: 1in 0.5in; +} + +h1 { + font-size: 24pt; + text-align: center; + margin-top: 2in; + margin-bottom: 0.3in; + page-break-before: always; +} + +h1:first-of-type { + margin-top: 3in; + page-break-before: avoid; +} + +.subtitle { + text-align: center; + font-size: 16pt; + color: #555; + margin-bottom: 0.5in; +} + +h2 { + font-size: 16pt; + border-bottom: 2px solid #333; + padding-bottom: 4pt; + margin-top: 0.5in; +} + +h3 { + font-size: 13pt; + margin-top: 0.3in; +} + +h4 { + font-size: 11pt; + font-style: italic; + margin-top: 0.2in; +} + +table { + width: 100%; + border-collapse: collapse; + margin: 0.2in 0; + font-size: 9.5pt; +} + +th, td { + border: 1px solid #999; + padding: 4pt 6pt; + text-align: left; + vertical-align: top; +} + +th { + background-color: #e8e8e8; + font-weight: bold; +} + +tr:nth-child(even) { + background-color: #f6f6f6; +} + +code { + font-family: "Courier New", Courier, monospace; + font-size: 9.5pt; + background-color: #f0f0f0; + padding: 1pt 3pt; + border-radius: 2pt; +} + +pre { + font-family: "Courier New", Courier, monospace; + font-size: 9pt; + background-color: #f8f8f8; + border: 1px solid #ddd; + padding: 8pt; + overflow-x: auto; + line-height: 1.3; +} + +pre code { + background: none; + padding: 0; +} + +/* Page breaks */ +section, .chapter { + page-break-before: always; +} + +/* Tables of contents */ +#TOC { + page-break-after: always; +} + +#TOC ul { + list-style: none; + padding-left: 0; +} + +#TOC li { + margin: 4pt 0; +} + +/* Header and footer */ +@page { + @top-center { + content: element(header); + font-size: 9pt; + color: #888; + } + @bottom-center { + content: counter(page); + font-size: 9pt; + } +} + +@media print { + body { + padding: 0; + } + table { + page-break-inside: avoid; + } + h2, h3 { + page-break-after: avoid; + } +} diff --git a/gen/docs/types.h b/gen/docs/types.h new file mode 100644 index 0000000..14fb4e0 --- /dev/null +++ b/gen/docs/types.h @@ -0,0 +1,78 @@ +#ifndef TYPES_H +#define TYPES_H + +#include +#include + +#define MAX_NAME 64 +#define MAX_LABEL 32 +#define MAX_DESC 512 +#define MAX_NOTE 512 +#define MAX_OPERANDS 128 +#define MAX_FIELDS 8 +#define MAX_INSTS 256 +#define MAX_REGS 64 +#define MAX_FMTS 16 +#define MAX_CSRS 128 +#define MAX_LINE 1024 +#define MAX_TOKENS 16 +#define MAX_PATH 256 + +typedef struct { + char name[MAX_NAME]; + int width; + int num_fields; + struct { + char name[MAX_LABEL]; + int high; + int low; + } fields[MAX_FIELDS]; +} IsaFormat; + +typedef struct { + char name[MAX_NAME]; + char abbr[MAX_LABEL]; + char desc[MAX_DESC]; + int preserve; + int caller_saved; + int arg_reg; + int index; +} IsaRegister; + +typedef struct { + char name[MAX_NAME]; + char fmt_name[MAX_NAME]; + uint32_t opcode; + uint32_t funct3; + uint32_t funct7; + uint32_t funct3_valid; + uint32_t funct7_valid; + char operands[MAX_OPERANDS]; + char desc[MAX_DESC]; + char note[MAX_NOTE]; + char category[MAX_LABEL]; + int has_imm; +} IsaInstruction; + +typedef struct { + char name[MAX_NAME]; + int number; + char desc[MAX_DESC]; +} IsaCsr; + +typedef struct { + IsaFormat formats[MAX_FMTS]; + int num_formats; + IsaRegister registers[MAX_REGS]; + int num_registers; + IsaInstruction instructions[MAX_INSTS]; + int num_instructions; + IsaCsr csrs[MAX_CSRS]; + int num_csrs; + char arch_name[MAX_NAME]; + char arch_version[MAX_LABEL]; + char arch_date[MAX_LABEL]; + char arch_status[MAX_LABEL]; +} IsaDb; + +#endif -- cgit v1.3