#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; }