From 887875959aa84af92291db334898aaa20956e632 Mon Sep 17 00:00:00 2001 From: allexanderbergmans Date: Fri, 3 Jul 2026 12:17:10 +0200 Subject: init --- gen/docs/generator.c | 481 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 481 insertions(+) create mode 100644 gen/docs/generator.c (limited to 'gen/docs/generator.c') 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; +} -- cgit v1.3