From dd9447a55f9bcaff946c9fb9328ec234a98a2ebb Mon Sep 17 00:00:00 2001 From: luojunlin Date: Fri, 12 Jun 2026 18:25:03 +0800 Subject: [PATCH 1/5] numbering issues --- .../15.High-Speed_Interface_System.md | 22 +++---- .../16.Low-Speed_Interface_System.md | 58 +++++++++---------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/en/key_stone/k1/k1_docs/k1_usermanual/15.High-Speed_Interface_System.md b/en/key_stone/k1/k1_docs/k1_usermanual/15.High-Speed_Interface_System.md index 98c7816..8d7afb8 100644 --- a/en/key_stone/k1/k1_docs/k1_usermanual/15.High-Speed_Interface_System.md +++ b/en/key_stone/k1/k1_docs/k1_usermanual/15.High-Speed_Interface_System.md @@ -273,7 +273,7 @@ The architecture of the USB port set is depicted below, where | --- | --- | --- | --- | --- | | 31:0 | pumon_monitor_ro[63:32] | RO | 0x0 | Debug signal from PUPHY | -#### 17.1.4.15 P_ADDR_RO4 REGISTER +#### 15.1.4.15 P_ADDR_RO4 REGISTER **Offset: 0x48** @@ -444,15 +444,15 @@ The architecture of the USB port set is depicted below, where | 1 | sof_toggle_out | RO | 0x0 | sof_toggle_out signal | | 0 | interrupt | RO | 0x0 | interrupt signal | -## 16.2 PCIe +## 15.2 PCIe -### 16.2.1 Introduction +### 15.2.1 Introduction K1 implements three PCIe Dual-Mode ports which can be configured as either Root Complex (RC) or Endpoint (EP) device. All ports support Gen2 with a data transfer speed of 5GT/s per lane. However, one port supports one lane only and two ports support two lanes each. -### 16.2.2 Features +### 15.2.2 Features - Support for Dual-Mode, programmable as either Complex (RC) or Endpoint (EP) device - Support for all non-optional features of the PCI Express Base Specification - Revision 5.0 - Version 1.0 (limited to Gen2 speed scope) @@ -473,7 +473,7 @@ All ports support Gen2 with a data transfer speed of 5GT/s per lane. However, on - Support for MSI Capability in EP Mode - Support for Integrated MSI Reception Module in RC Mode -### 16.2.3 Functional Description +### 15.2.3 Functional Description The architecture of the PCIe Dual-Mode port set is depicted below. @@ -608,9 +608,9 @@ The embedded DMA controller (EDMA) offloads data transfer tasks from the CPU and - Fetches the channel context (transfer control information) - Executes transfers based on the DMA elements programmed in local memory -## 16.3 EMAC +## 15.3 EMAC -### 16.3.1 Introduction +### 15.3.1 Introduction K1 features a EMAC core which includes the essential protocol requirements for the operation of 10/100/1000 Mbps Ethernet/IEEE 802.3-2012 compliant node. @@ -618,7 +618,7 @@ On the system side, the EMAC core features a 64-bit AXI Master Interface and a 3 The EMAC core can operate at 10 Mbps, 100 Mbps (Fast Ethernet) or 1000 Mbps (Gigabit Ethernet). Additionally, it includes a powerful 64-bit Scatter-Gather DMA to transfer packets between HOST Memory and Internal FIFOs to achieve high performance. -### 16.3.2 Features +### 15.3.2 Features - Capability of handling transmit/receive data encapsulation functions, including Framing (frame boundary delimitation, frame synchronization) and Error Detection (physical medium transmission errors) - Media access management with medium allocation (collision avoidance) and contention resolution (collision handling) in Half-Duplex Mode of operation at speeds of 10/100 Mbps @@ -629,7 +629,7 @@ The EMAC core can operate at 10 Mbps, 100 Mbps (Fast Ethernet) or 1000 Mbps (Gig - Bus mastering on the AXI interface to transfer packets between the HOST memory and the internal FIFOs using 64-bit transfer mode - Automatic transfer of packets between the HOST memory and internal FIFOs (based on descriptors) to minimize CPU overhead -### 16.3.3 Block Diagram +### 15.3.3 Block Diagram The micro-architecture of EMAC unit is depicted below. @@ -649,7 +649,7 @@ A brief description of each module is provided below. - **AXI Master Interface**: This module provides the AXI Master functionality to generate transactions on the AXI Bus. The transactions are generated based on the requests from the Transmit/Receive DMA’s. - **AXI Target Interface**: This module provides the AXI Target functionality to the AXI Host (CPU). This interface is used to access all the DMA/MAC registers in the Registers Module. -### 16.3.4 Programming Model +### 15.3.4 Programming Model The EMAC Core transfers received data frames/packets from the RMII/RGMII interface to the receive buffers in the host memory. Similarly, it transmits data frames/packets from the transmit buffers in the host memory to the RMII/RGMII interface. Descriptors, which reside in the host memory, act as pointers to these buffers. The receive and transmit FIFOs inside the EMAC Core serve as temporary storage for frame transmission and reception. @@ -665,7 +665,7 @@ Below is depicted the descriptor chain structure. In this configuration, each de ![](static/Programming_Model_1.png) -### 16.3.5 Register Description +### 15.3.5 Register Description The EMAC registers are used to control the operation of the EMAC core and to read status/interrupt information from it. The EMAC core decodes all 16 bits of the AHB/AXI (Slave) target address to access the registers. These registers are 32-bit word-aligned and must be accessed using 32-bit aligned addresses only. Reserved fields should be written as zero, and the EMAC core returns a value of zero in those fields. All registers are set to their default values upon reset. diff --git a/en/key_stone/k1/k1_docs/k1_usermanual/16.Low-Speed_Interface_System.md b/en/key_stone/k1/k1_docs/k1_usermanual/16.Low-Speed_Interface_System.md index 6e86f83..59317b0 100644 --- a/en/key_stone/k1/k1_docs/k1_usermanual/16.Low-Speed_Interface_System.md +++ b/en/key_stone/k1/k1_docs/k1_usermanual/16.Low-Speed_Interface_System.md @@ -1079,7 +1079,7 @@ To reset the I2C unit using the **ICR** register, follow these steps: - Clear the ISR register - Clear reset in the ICR -### 17.1.4 Register Description +### 16.1.4 Register Description > **Note.** The base address of I2C registers are tabled below. @@ -1430,9 +1430,9 @@ Software can write '0' to this register to flush the FIFO after an interrupt. | 31:4 | RSVD | R | 0 | Reserved for future use | | 3:0 | DATA | R/W | 0x0 | This is the location in the TX FIFO where the next entry will be read from by the hardware. | -## 17.2 SPI/I2S +## 16.2 SPI/I2S -### 17.2.1 Introduction +### 16.2.1 Introduction The SPI/I2S is a synchronous serial controller that can be connected to a variety of external Analog-to-Digital converters (ADC), audio and telecommunication codecs and many other devices that use serial protocols for data transfer. The SPI/I2S Controllers directly support the following protocols: @@ -1445,7 +1445,7 @@ The SPI/I2S can be configurate to operate in Master mode (the attached periphera The FIFOs can be loaded or emptied by CPU using programmed I/O (PIO) or DMA burst transfers. -### 17.2.2 Features +### 16.2.2 Features - Directly support for Motorola\* Serial Peripheral Interface (SPI) - The I2S is supported by programming, and data sample sizes can be set to 8, 16, 18 or 32 bits @@ -1456,7 +1456,7 @@ The FIFOs can be loaded or emptied by CPU using programmed I/O (PIO) or DMA burs - Receive-without-Transmit operation - Audio clock control to provide a 4x or 8x output clock to support most standard audio frequencies -### 17.2.3 Functional Description +### 16.2.3 Functional Description Data transfers between an SPI/I2S and memory are initiated by the CPU using programmed I/O (PIO) or DMA bursts. Separate Transmit and Receive FIFOs and serial data paths permit simultaneous transfers in both directions to and from the external peripheral, depending on the protocols chosen. @@ -1726,7 +1726,7 @@ Wait two SS_SCLK cycles before writing new data to the TXFIFO. The SPI/I2S Baud ![](static/SPI-I2S.png) -### 17.2.4 Register Description +### 16.2.4 Register Description > **Note.** The base address of SPI/I2S registers are tabled below. @@ -1932,9 +1932,9 @@ SPI/I2S root counter value write for read request register. | --- | --- | --- | --- | --- | | 31:0 | SSRWOTCVWR | R/W | 0x0 | This register prevents the risk of instability on rwot_counter value reading, it's only valid after SPI/I2S has been enabled Write
0 = No effect Write
1 = Capture value of rwot_counter Read: Returns the captured value of rwot_counter | -## 17.3 UART +## 16.3 UART -### 17.3.1 Introduction +### 16.3.1 Introduction The K1 has 10 UARTs (UART 0-9). The UARTs use the same programming model. @@ -1961,7 +1961,7 @@ The supported baud rates of each UART are tabled below. | 3 | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | 4 | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -### 17.3.2 Features +### 16.3.2 Features The serial ports are controlled via direct-memory access (DMA) or programmed I/O. The UARTs share the following features: @@ -2003,7 +2003,7 @@ The UARTs are functionally compatible with the 16550A and 16750 industry standar - Auto baud-rate detection - Auto flow -### 17.3.3 Functional Description +### 16.3.3 Functional Description #### Signal Description @@ -2211,7 +2211,7 @@ The recommended baud rates based on divisor values (Divisor Latch High Byte Regi The divisor reset value is 0x0002. Changing the baud rate (writing to registers Divisor Latch Low Byte Register and Divisor Latch High Byte Register) is not permitted while actively transmitting or receiving data. -### 17.3.4 Register Description +### 16.3.4 Register Description > **Note.** > @@ -2518,19 +2518,19 @@ config the baud_newreg_en to use the new address for DLH, DLL, FCR | 1 | BAUD_SYNC_DONE | RWC | 0x0 | baud_sync_done.
1 = the completion of {DLH, DLL } sync to clk_uart domain from clk_apb domain when <baud_newreg_en> is set previously, can be cleared by writing this resiger(0x38) or full baud divisor register(0x34).
0 = default status. | | 0 | BAUD_NEWREG_EN | RW | 0x0 | baud_newreg_en.
0= no influence with the previous config, except the new read access for FCR in offset=0x34.
1= enable another new address access for {DLH, DLL} in offset= 0x30 and FCR in offset=0x34. The previous access for DLH, DLL, FCR are all blocked. | -## 17.4 GPIO +## 16.4 GPIO -### 17.4.1 Introduction +### 16.4.1 Introduction GPIO is used to capture and generate application-specific input and output. All ports of GPIO are brought out via the alternate function muxing. GPIO unit is in charge of GPIO ports control and status check. When programmed as an input, a GPIO port can also serve as an interrupt source. At the assertion of all resets, all GPIO ports are configured as inputs and remain inputs until they are configured either by the boot process or by user software. -### 17.4.2 Features +### 16.4.2 Features - As inputs, they can be programmed to generate an interrupt at a rising edge, a falling edge or both - As outputs, they can be cleared or set individually - As inputs, the values can be read individually -### 17.4.3 Functional Description +### 16.4.3 Functional Description #### Block Diagram @@ -2567,7 +2567,7 @@ Rising-Edge Detect Enable Register and GPIO Falling-Edge Detect Enable Register. The GPIO information described in this section applies only to the GPIO alternate function. Still, it is possible for a system to use the GPIO functions internally and not actually connect to a physical pin. -### 17.4.4 Register Description +### 16.4.4 Register Description > **Note.** The base address of GPIO registers are tabled below. @@ -2724,9 +2724,9 @@ Bit-wise clear of GPIO rising edge detect enable register. | --- | --- | --- | --- | --- | | 31:0 | CFERn | W | 0x0 | Clear GPIO Falling Edge detect enable n (where n = 0 ~ 31)
0: GPIO Falling-Edge Detect Enable Register bit not affected
1: GPIO Falling-Edge Detect Enable Register bit is cleared | -## 17.5 One-Wire Bus Master Interface +## 16.5 One-Wire Bus Master Interface -### 17.5.1 Introduction +### 16.5.1 Introduction The One-Wire Bus Master Interface Controller is responsible for receiving and transmitting data on the One-Wire bus. It fully controls the One-Wire bus using 8-bit commands. The processor interacts with the controller by loading commands, reading and writing data, and configuring interrupt controls through 5 specific registers. @@ -2738,7 +2738,7 @@ The architecture of the One-Wire Bus Master Interface is depicted below. ![](static/One-Wire.png) -### 17.5.2 Functional Description +### 16.5.2 Functional Description #### Signal @@ -2866,7 +2866,7 @@ Ensure that these values are correct (that is, PDR = 0 if there is a slave and P - Wait for TBE interrupt. In the service routine, mask interrupts, clear the W1IER[ETBE] bit. Read the W1INTR[TBE] bit and ensure that it is set - New data, if needed, can be written into the buffer and interrupts should be unmasked -### 17.5.3 Register Description +### 16.5.3 Register Description > **Note.** The base address of 1-Wire Bus Master Registers is 0xD4011800. @@ -2947,19 +2947,19 @@ This register divides the internal reference clock to generate the One-Wire cloc | 4:2 | DIV | RW | 0x0 | Divider.
The One-Wire bus master interface controller uses the output of the prescaler and divides by the DIV value to produce the One-Wire clocks. This clock must be approximately 1 MHz for correct operation. This value must be set to 0x2. | | 1:0 | PRE | RW | 0x0 | Prescaler value.
The One-Wire bus master interface controller uses the input 24-MHz clock and initially divides by this value before outputting to the divider section. This value must be set to 0x3, selecting a prescale of 7. | -## 17.6 IR-RX +## 16.6 IR-RX -### 17.6.1 Introduction +### 16.6.1 Introduction The IR-RX module is capable of receiving infrared signals and transforming the received signals into digital format. Received data can be accessed through FIFO by checking status or configuring interrupt. -### 17.6.2 Features +### 16.6.2 Features - Infrared input signals are transformed into the Run-Length-Code (RLC) format - Configurable signal width threshold for noise detecting - 32 Bytes FIFO for received data storage -### 17.6.3 Functional Description +### 16.6.3 Functional Description The IR-RX module receives infrared signals and transforms the received information into digital format. The input infrared signals are filtered depending on a configurable noise detecting threshold. The transformed data is written into FIFO as the Run-Length-Code (RLC). Software may read the data from FIFO by checking status or configuring interrupt. @@ -2984,7 +2984,7 @@ For example, the following data in RLC format implies that: - 0x06 --\> Logic '0' has sustained for 0x06 working clock cycles. - 0xFF --\> Logic '1' has sustained for 0x7F working clock cycles. -### 17.6.4 Register Description +### 16.6.4 Register Description > **Note.** > @@ -3080,9 +3080,9 @@ For example, the following data in RLC format implies that: | 1 | PEDGE_INT_FLAG | RW1C | 0x0 | This bit is set to 1 if positive edge of the input infrared signal is detected. Interrupt is generated if PEDGE_INT_EN=1. It can be cleared by writing 0x1 to this bit. | | 0 | NEDGE_INT_FLAG | RW1C | 0x0 | This bit is set to 1 if negtive edge of the input infrared signal is detected. Interrupt is generated if NEDGE_INT_EN=1. It can be cleared by writing 0x1 to this bit. | -## 17.7 PWM +## 16.7 PWM -### 17.7.1 Introduction +### 16.7.1 Introduction K1 contains 20 Pulse-Width Modulation (PWM) channels labeled as PWMx where x=[0,19]. @@ -3094,13 +3094,13 @@ The timing of each PWM channel can be set to run continuously or be adjusted dyn The power-saving mode allows stopping the internal clock of a PWM channel (PSCLK_PWM), resulting to a constant high or low state of the output signal of that PWM channel (PWM_OUT), thus saving power when the output signal of that PWM channel is not needed. -### 17.7.2 Features +### 16.7.2 Features - Support for 50% duty-cycle ranging from 198.4Hz to 6.5MHz (additional duty-cycle options depend on the choice of the preferred frequency) - Enhanced period time controlled through 6-bit clock divider and 10-bit period time counter - 15-bit pulse counter control -### 17.7.3 Register Description +### 16.7.3 Register Description > **Note**. The base address of PWMn (n=1, 2, ... , 20) registers is 0xD401A000 with a stride of 0x400. From 0a04d7b37dae4b46ecdf93eaaf0cea6e6c21b295 Mon Sep 17 00:00:00 2001 From: luojunlin Date: Mon, 15 Jun 2026 09:45:47 +0800 Subject: [PATCH 2/5] updated agent rules --- .github/doc-review-agent/agent.py | 231 +++++++++++++++++++++- .github/doc-review-agent/config.yml | 46 +++++ .github/doc-review-agent/system_prompt.md | 25 ++- 3 files changed, 294 insertions(+), 8 deletions(-) diff --git a/.github/doc-review-agent/agent.py b/.github/doc-review-agent/agent.py index fc10bcc..81dcfe4 100644 --- a/.github/doc-review-agent/agent.py +++ b/.github/doc-review-agent/agent.py @@ -257,6 +257,168 @@ def check_chinese_punctuation(lines: list[str], path: str) -> list[tuple[int, st return issues +def check_consecutive_blank_lines(lines: list[str]) -> list[tuple[int, str, str]]: + """Flag runs of more than 2 consecutive blank lines.""" + issues = [] + blank_run = 0 + run_start = 0 + for i, line in enumerate(lines, 1): + if line.strip() == "": + if blank_run == 0: + run_start = i + blank_run += 1 + else: + if blank_run > 2: + issues.append((run_start, "Suggestion", + f"{blank_run} consecutive blank lines found. " + "Reduce to at most 1 blank line between paragraphs.")) + blank_run = 0 + return issues + + +def check_trailing_whitespace(lines: list[str]) -> list[tuple[int, str, str]]: + """Flag lines with trailing spaces (except intentional line-break double-space).""" + issues = [] + for i, line in enumerate(lines, 1): + stripped = line.rstrip("\n") + if stripped.endswith(" ") and not stripped.endswith(" "): # single trailing space + issues.append((i, "Suggestion", + "Trailing whitespace detected. Remove the trailing space(s).")) + return issues + + +def check_table_structure(lines: list[str]) -> list[tuple[int, str, str]]: + """Check that Markdown tables have a separator row and consistent column counts.""" + issues = [] + i = 0 + while i < len(lines): + line = lines[i] + if re.match(r'^\s*\|', line): + # Collect table block + table_start = i + 1 # 1-based + col_counts = [] + j = i + while j < len(lines) and (re.match(r'^\s*\|', lines[j]) or lines[j].strip() == ""): + if re.match(r'^\s*\|', lines[j]): + col_counts.append(lines[j].count("|")) + j += 1 + + if len(col_counts) >= 2: + # Second row must be a separator (only |, -, :, space) + sep_row = lines[i + 1] if i + 1 < len(lines) else "" + if not re.match(r'^[\s|:\-]+$', sep_row): + issues.append((table_start + 1, "Error", + "Table is missing a separator row (`| --- | --- |`) after the header.")) + # Check consistent column count (ignore separator row) + data_cols = [c for idx, c in enumerate(col_counts) if idx != 1] + if len(set(data_cols)) > 1: + issues.append((table_start, "Warning", + "Table has inconsistent column counts across rows. " + "Ensure every row has the same number of `|` delimiters.")) + i = j + else: + i += 1 + return issues + + +def check_product_name_casing(lines: list[str]) -> list[tuple[int, str, str]]: + """Flag incorrect casing of product names (K1, K3, P1, P1S).""" + issues = [] + # Match variants like k1, K-1, k-1, p1s, P-1S, etc. but not the correct forms + wrong = re.compile(r'\b(k1|k3|p1s|p1|K-1|K-3|P-1S|P-1)\b') + correct_forms = {"k1": "K1", "k3": "K3", "p1s": "P1S", "p1": "P1", + "K-1": "K1", "K-3": "K3", "P-1S": "P1S", "P-1": "P1"} + in_code = False + for i, line in enumerate(lines, 1): + if line.startswith("```"): + in_code = not in_code + if in_code: + continue + for m in wrong.finditer(line): + token = m.group() + correct = correct_forms.get(token, token.upper().replace("-", "")) + issues.append((i, "Warning", + f"Incorrect product name casing: `{token}`. Use `{correct}`.")) + return issues + + +def check_missing_units(lines: list[str]) -> list[tuple[int, str, str]]: + """Flag bare numeric values that are likely missing units.""" + issues = [] + # Match numbers followed by nothing, or a non-unit word — heuristic + # Patterns: a standalone number at end of sentence or before punctuation + bare_num = re.compile( + r'(? list[tuple[int, str, str]]: + """Flag first-person pronouns in en/ formal documentation.""" + if is_zh(file_path): + return [] + issues = [] + # Only flag unambiguous personal pronouns; exclude 'our' in company/product context + pattern = re.compile(r'\b(I|my|me|we|us)\b') + in_code = False + for i, line in enumerate(lines, 1): + if line.startswith("```"): + in_code = not in_code + if in_code or line.startswith(">") or line.startswith("#"): + continue + for m in pattern.finditer(line): + found = m.group() + issues.append((i, "Warning", + f"First-person pronoun `{found}` is not appropriate in formal technical " + "documentation. Rewrite using imperative mood or a subject-neutral construction " + "(e.g., \"The user should…\" or \"Configure the…\").")) + return issues + + +def check_passive_overuse(lines: list[str], file_path: str) -> list[tuple[int, str, str]]: + """Passive voice is acceptable and natural in academic/formal technical EN docs. + This check is intentionally a no-op; kept for config compatibility.""" + return [] + return issues + + +def check_admonition_keywords(lines: list[str], file_path: str) -> list[tuple[int, str, str]]: + """Flag non-standard admonition keywords (NOTE/WARNING/CAUTION/TIP only).""" + issues = [] + # Match blockquote lines that start with bold keyword + admon = re.compile(r'^>\s*\*\*(\w[\w\s]*)\*\*') + valid_en = {"NOTE", "WARNING", "CAUTION", "TIP", "IMPORTANT"} + valid_zh = {"注意", "警告", "提示", "重要", "危险"} + valid = valid_zh if is_zh(file_path) else valid_en + for i, line in enumerate(lines, 1): + m = admon.match(line) + if m: + kw = m.group(1).strip().upper() if not is_zh(file_path) else m.group(1).strip() + if kw not in valid and kw not in {v.upper() for v in valid}: + allowed = "、".join(sorted(valid)) if is_zh(file_path) else ", ".join(sorted(valid)) + issues.append((i, "Suggestion", + f"Non-standard admonition keyword `{m.group(1).strip()}`. " + f"Use one of: {allowed}.")) + return issues + + def check_bilingual_pair(file_path: str) -> list[tuple[int, str, str]]: issues = [] if file_path.startswith("en/"): @@ -274,6 +436,38 @@ def check_bilingual_pair(file_path: str) -> list[tuple[int, str, str]]: return issues +def check_bilingual_sync(file_path: str, pr_file_paths: set[str]) -> list[tuple[int, str, str]]: + """ + If this file was modified in the PR but its language counterpart was NOT, + remind the author to update the paired file to keep both versions in sync. + Only fires when the counterpart file actually exists on disk. + """ + issues = [] + if file_path.startswith("en/"): + pair = "zh/" + file_path[3:] + elif file_path.startswith("zh/"): + pair = "en/" + file_path[3:] + else: + return issues + + pair_full = WORKSPACE / pair + if not pair_full.exists(): + return issues # missing pair is handled by check_bilingual_pair + + if pair not in pr_file_paths: + lang = "英文" if file_path.startswith("zh/") else "Chinese" + pair_lang = "Chinese" if file_path.startswith("en/") else "英文" + if file_path.startswith("zh/"): + issues.append((1, "Warning", + f"此文件已在本 PR 中修改,但对应的{lang}文档 `{pair}` 未同步更新。" + "请同步修改对应文档,确保中英文内容一致。")) + else: + issues.append((1, "Warning", + f"This file was updated in this PR but its {pair_lang} counterpart `{pair}` " + "was not. Update the paired file to keep both language versions in sync.")) + return issues + + # ─── LLM-assisted review ───────────────────────────────────────────────────── def llm_review(content: str, file_path: str, system_prompt: str) -> list[tuple[int, str, str]]: @@ -332,9 +526,12 @@ def llm_review(content: str, file_path: str, system_prompt: str) -> list[tuple[i # ─── Main ───────────────────────────────────────────────────────────────────── -def run_checks(file_path: str, content: str, cfg: dict) -> list[tuple[int, str, str]]: +def run_checks(file_path: str, content: str, cfg: dict, + pr_file_paths: set[str] | None = None) -> list[tuple[int, str, str]]: lines = content.splitlines() issues: list[tuple[int, str, str]] = [] + if pr_file_paths is None: + pr_file_paths = set() if cfg["checks"]["frontmatter"]["enabled"]: issues += check_frontmatter(content, file_path, cfg["checks"]["frontmatter"]) @@ -352,6 +549,35 @@ def run_checks(file_path: str, content: str, cfg: dict) -> list[tuple[int, str, issues += check_chinese_punctuation(lines, file_path) if cfg["checks"]["bilingual_mirror"]["enabled"] and cfg["checks"]["bilingual_mirror"]["flag_missing_pair"]: issues += check_bilingual_pair(file_path) + if cfg["checks"]["bilingual_mirror"]["enabled"] and cfg["checks"]["bilingual_mirror"].get("flag_sync_reminder", True): + issues += check_bilingual_sync(file_path, pr_file_paths) + + # ── New professional technical-writer checks ────────────────────────────── + tw = cfg["checks"].get("technical_writing", {}) + + if tw.get("consecutive_blank_lines", {}).get("enabled", True): + issues += check_consecutive_blank_lines(lines) + + if tw.get("trailing_whitespace", {}).get("enabled", True): + issues += check_trailing_whitespace(lines) + + if tw.get("table_structure", {}).get("enabled", True): + issues += check_table_structure(lines) + + if tw.get("product_name_casing", {}).get("enabled", True): + issues += check_product_name_casing(lines) + + if tw.get("missing_units", {}).get("enabled", True): + issues += check_missing_units(lines) + + if tw.get("first_person", {}).get("enabled", True): + issues += check_first_person(lines, file_path) + + if tw.get("passive_voice", {}).get("enabled", True): + issues += check_passive_overuse(lines, file_path) + + if tw.get("admonition_keywords", {}).get("enabled", True): + issues += check_admonition_keywords(lines, file_path) return issues @@ -438,7 +664,8 @@ def is_included(path: str) -> bool: continue # Rule-based checks - issues = run_checks(fpath, content, cfg) + pr_paths = {f["filename"] for f in pr_files} + issues = run_checks(fpath, content, cfg, pr_file_paths=pr_paths) # LLM-assisted checks llm_issues = llm_review(content, fpath, system_prompt) diff --git a/.github/doc-review-agent/config.yml b/.github/doc-review-agent/config.yml index 8588c7e..d9a9f8e 100644 --- a/.github/doc-review-agent/config.yml +++ b/.github/doc-review-agent/config.yml @@ -38,6 +38,8 @@ checks: enabled: true # Flag if the paired file in the other language is missing flag_missing_pair: true + # Remind author to update the counterpart when only one language file is modified + flag_sync_reminder: true # Flag if heading count differs by more than this threshold heading_count_tolerance: 2 @@ -52,12 +54,56 @@ checks: # Enforce Chinese punctuation in zh/ files chinese_punctuation_in_zh: true + # ─── Professional technical-writer checks ────────────────────────────── + technical_writing: + + # Flag runs of more than 2 consecutive blank lines + consecutive_blank_lines: + enabled: true + + # Flag lines with single trailing whitespace + trailing_whitespace: + enabled: true + + # Validate Markdown table structure (separator row + consistent columns) + table_structure: + enabled: true + + # Enforce correct product name casing: K1, K3, P1, P1S + product_name_casing: + enabled: true + + # Flag bare numeric values in unit-bearing contexts (voltage, frequency, etc.) + missing_units: + enabled: true + + # Flag first-person pronouns (I, me, my, we, us) in formal en/ docs — Warning level + first_person: + enabled: true + + # Passive voice is natural in academic technical EN — disabled + passive_voice: + enabled: false + + # Flag non-standard admonition keywords (only NOTE/WARNING/CAUTION/TIP/IMPORTANT) + admonition_keywords: + enabled: true + # ─── Severity overrides ─────────────────────────────────────────────────────── # Default severities are defined in the agent. Override here if needed. severity_overrides: missing_pair_file: "warning" # missing bilingual pair → warning, not error tbd_in_content: "warning" missing_code_block_lang: "suggestion" + consecutive_blank_lines: "suggestion" + trailing_whitespace: "suggestion" + table_structure_missing_separator: "error" + table_structure_inconsistent_columns: "warning" + product_name_casing: "warning" + missing_units: "suggestion" + first_person: "warning" + passive_voice: "suggestion" # disabled above; severity kept for reference + admonition_keywords: "suggestion" # ─── Output ─────────────────────────────────────────────────────────────────── output: diff --git a/.github/doc-review-agent/system_prompt.md b/.github/doc-review-agent/system_prompt.md index b20a460..e18902d 100644 --- a/.github/doc-review-agent/system_prompt.md +++ b/.github/doc-review-agent/system_prompt.md @@ -101,18 +101,31 @@ You are a professional technical documentation reviewer for a semiconductor comp ### 3. Bilingual Consistency (cross-file check) - The corresponding file in the other language (`en/` ↔ `zh/`) exists. +- If a file is modified in this PR but its language counterpart is **not** in the PR, post a `[Warning]` reminding the author to update the paired file so both versions stay in sync. - Section count (number of `##` headings) matches between language versions. - Product names and model numbers are consistent: K1, K3, P1, P1S (not k1, K-1, etc.). ### 4. Technical Content -- Numerical values include units (V, mA, MHz, °C, etc.). -- Tables have headers and consistent column counts. -- Code blocks specify a language identifier (e.g., ` ```bash `, ` ```c `). +- Numerical values include units (V, mA, MHz, °C, etc.). Flag bare numbers in sentences that mention voltage, current, frequency, temperature, power, or timing. +- Tables have a separator row (`| --- |`) and consistent column counts across all rows. +- Code blocks specify a language identifier (e.g., ` ```bash `, ` ```c `, ` ```python `). +- Product names use correct casing: **K1**, **K3**, **P1**, **P1S** — never `k1`, `K-1`, `p1s`, etc. ### 5. Style -- No "TBD", "TODO", or "FIXME" in content intended for publication. -- No consecutive blank lines (more than 2). -- Chinese punctuation used in `zh/` files (,。;:""instead of , . ; : ""). +- No “TBD”, “TODO”, or “FIXME” in content intended for publication. +- No more than 1 consecutive blank line between paragraphs. +- Chinese punctuation used in `zh/` files (,。;:“” instead of , . ; : ""). +- No trailing whitespace on any line. +- No first-person pronouns (`I`, `me`, `my`, `we`, `us`) in formal technical documentation. Use imperative mood ("Configure the…") or a subject-neutral third-person construction ("The user should…"). This is a **Warning**-level issue. +- Passive voice is acceptable and encouraged where it improves clarity or objectivity (e.g., "The register is reset to 0x00 on power-up"). Do **not** flag passive constructions. +- Admonition blocks must use standard keywords only: + - English: `NOTE`, `WARNING`, `CAUTION`, `TIP`, `IMPORTANT` + - Chinese: `注意`、`警告`、`提示`、`重要`、`危险` + +### 6. Chinese-specific Rules +- Keep English technical terms untranslated and in their canonical form: SoC, PCIe, DDR4, LPDDR4X, GPIO, UART, I²C, SPI, EVB, BOM, SDK, API. +- Units always use the international symbol in both languages: V, mA, MHz, GHz, °C — never “伏特”, “毫安”, etc. +- Numbers in body text follow the pattern: Arabic numerals + unit (e.g., `1.8 V`, `400 MHz`) — no mixing of Chinese numerals (一二三) with units. --- From fca84155a7054d720fc89522367a44a12e561116 Mon Sep 17 00:00:00 2001 From: luojunlin Date: Mon, 15 Jun 2026 10:25:28 +0800 Subject: [PATCH 3/5] Thermal Design Reference draft --- en/key_stone/k3/k3_hw/index.md | 2 +- en/key_stone/k3/k3_hw/k3_sr_thermal_design.md | 7 ---- en/key_stone/k3/k3_hw/k3_thermal_design.md | 36 +++++++++++++++++++ zh/key_stone/k3/k3_hw/index.md | 2 +- zh/key_stone/k3/k3_hw/k3_sr_thermal_design.md | 7 ---- zh/key_stone/k3/k3_hw/k3_thermal_design.md | 34 ++++++++++++++++++ 6 files changed, 72 insertions(+), 16 deletions(-) delete mode 100644 en/key_stone/k3/k3_hw/k3_sr_thermal_design.md create mode 100644 en/key_stone/k3/k3_hw/k3_thermal_design.md delete mode 100644 zh/key_stone/k3/k3_hw/k3_sr_thermal_design.md create mode 100644 zh/key_stone/k3/k3_hw/k3_thermal_design.md diff --git a/en/key_stone/k3/k3_hw/index.md b/en/key_stone/k3/k3_hw/index.md index 357a460..9d96f74 100644 --- a/en/key_stone/k3/k3_hw/index.md +++ b/en/key_stone/k3/k3_hw/index.md @@ -2,7 +2,7 @@ sidebar_position: 2 # Hardware Design Resources & Guide -- [Single-Root Thermal Design Reference](k3_sr_thermal_design.md) +- [Thermal Design Reference](k3_thermal_design.md) - [Hardware Design Guide](k3_hw_design_guide.md) - [Hardware Design Resources](k3_hw_resources.md) - [Hardware FAQ](k3_hw_faq.md) diff --git a/en/key_stone/k3/k3_hw/k3_sr_thermal_design.md b/en/key_stone/k3/k3_hw/k3_sr_thermal_design.md deleted file mode 100644 index bcfb7f9..0000000 --- a/en/key_stone/k3/k3_hw/k3_sr_thermal_design.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -sidebar_position: 1 ---- - -# K3 Single-Root Thermal Design Reference - -> Coming soon ... \ No newline at end of file diff --git a/en/key_stone/k3/k3_hw/k3_thermal_design.md b/en/key_stone/k3/k3_hw/k3_thermal_design.md new file mode 100644 index 0000000..e54ab79 --- /dev/null +++ b/en/key_stone/k3/k3_hw/k3_thermal_design.md @@ -0,0 +1,36 @@ +--- +sidebar_position: 1 +--- + +# K3 Thermal Design Reference + +## PDF Version + +Click to download [K3 Thermal Design Reference (PDF)](#) +> Coming soon ... + +## Revision History + +| Version | Date | Description | +| --- | --- | --- | +| V1.0 | 2026.06.xx | Initial release | + +## Glossary + +| Term (Abbreviation) | Definition | +| --- | --- | +| Thermal Design Power (TDP) | The average heat dissipated by the CPU under maximum sustained workload at the highest operating frequency. | +| Package Long Term Power Limit (PL1) | The sustained power consumption threshold for the CPU during extended operation; approximately equal to TDP. | +| Single-root (SR) | A standalone product form factor built around a single CPU. | +| System on Chip (SoC) | An integrated circuit that consolidates a complete computer system onto a single die, including processing cores, peripheral interfaces, and functional modules. | + +## 1. Scope + +This reference guide provides system-level thermal requirements for the K3 SoC. It is intended for customers designing new K3-based boards or complete systems built on the K3 platform, offering reference thermal dissipation solutions. The heatsink parameters, fan performance data, and thermal interface material properties listed herein are provided as design references. + +In their respective projects, customers are required to: + +- Review this guide and understand the safe operating temperature conditions required by the K3 SoC under maximum load. +- Identify the environmental requirements of their own products, assess the differences from this reference design, and perform appropriate design adjustments along with any necessary thermal simulation. +- Take responsibility for designing and implementing a thermal solution that maintains the K3 junction temperature within the specified safe operating range. +- Validate the thermal solution through system-level thermal testing, verify actual temperature rise against design targets, and debug any deviations to ensure reliable and stable system operation. \ No newline at end of file diff --git a/zh/key_stone/k3/k3_hw/index.md b/zh/key_stone/k3/k3_hw/index.md index 8fa6d14..8d6504d 100644 --- a/zh/key_stone/k3/k3_hw/index.md +++ b/zh/key_stone/k3/k3_hw/index.md @@ -2,7 +2,7 @@ sidebar_position: 2 # 硬件设计资源与指南 -- [单路热设计参考方案](k3_sr_thermal_design.md) +- [热设计参考方案](k3_thermal_design.md) - [硬件设计指南](k3_hw_design_guide.md) - [硬件设计资源](k3_hw_resources.md) - [硬件方案 FAQ](k3_hw_faq.md) diff --git a/zh/key_stone/k3/k3_hw/k3_sr_thermal_design.md b/zh/key_stone/k3/k3_hw/k3_sr_thermal_design.md deleted file mode 100644 index b6e3c5b..0000000 --- a/zh/key_stone/k3/k3_hw/k3_sr_thermal_design.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -sidebar_position: 1 ---- - -# K3 单路热设计参考方案 - -> Coming soon... \ No newline at end of file diff --git a/zh/key_stone/k3/k3_hw/k3_thermal_design.md b/zh/key_stone/k3/k3_hw/k3_thermal_design.md new file mode 100644 index 0000000..80773ff --- /dev/null +++ b/zh/key_stone/k3/k3_hw/k3_thermal_design.md @@ -0,0 +1,34 @@ +--- +sidebar_position: 1 +--- + +# K3 热设计参考方案 + +## PDF 版本下载 + +点击下载 [K3 热设计参考方案(PDF)](#) +> Coming soon ... + +## 版本 + +| 版本 | 日期 | 修订说明 | +| --- | --- | --- | +| V1.0 | 2026.06.xx | 首版 | + +## 术语定义 + +| 中文 | 英文及缩写 | 定义和解释 | +| --- | --- | --- | +| 热设计功率 | Thermal Design Power (TDP) | CPU 在最高可工作频率、满负荷使用状态下,目标负载持续运行时间窗口内,产生的热功率平均值。 | +| 长时功耗限制 | Package Long Term Power Limit (PL1) | CPU 在长时间运行下的持续功耗阈值,约等于 TDP | +| 单路 | Single-root (SR) | 一个独立产品,由单个 CPU 构建的形态架构 | +| 片上系统芯片 | System on Chip (SoC) | 一种将计算机系统集成到单一芯片上的集成电路,它包含了处理核心、外围接口及其他功能模块,能够独立实现复杂系统功能 | + +## 1. 适用范围 + +本参考指南的目的是提供 K3 系统级的热学要求,为新设计的 K3 单板或以 K3 主板构建的整机产品的客户提供散热参考方案。所列举的散热器参数、风扇性能、导热材料属性等信息可供借鉴使用。在实际项目中,客户有必要遵循以下要求: + +- 阅读本指南内容,理解 K3 系统级芯片(SoC)在最大负荷下所需的安全工作温度条件; +- 客户应明确自身项目产品的环境要求,清晰了解实际环境要求与本参考方案的差异内容,进行相应的设计调整并开展必要的热仿真实验; +- 客户负责设计和实施散热系统解决方案,确保该方案能使 K3 温度维持在安全工作范围内; +- 客户负责对散热系统进行验证鉴定,通过具体整机热测试,验证实际温升情况,并针对未达到预期的问题进行调试修正,以保障整机可靠稳定运行。 \ No newline at end of file From 803e5445c140e7e5f933da5806884c4fe0a3380d Mon Sep 17 00:00:00 2001 From: luojunlin Date: Tue, 16 Jun 2026 14:02:10 +0800 Subject: [PATCH 4/5] updated agent --- .../__pycache__/agent.cpython-313.pyc | Bin 0 -> 44770 bytes .github/doc-review-agent/agent.py | 248 +++++++++++++++++- .github/workflows/doc-review.yml | 2 +- 3 files changed, 244 insertions(+), 6 deletions(-) create mode 100644 .github/doc-review-agent/__pycache__/agent.cpython-313.pyc diff --git a/.github/doc-review-agent/__pycache__/agent.cpython-313.pyc b/.github/doc-review-agent/__pycache__/agent.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2b5586fd895b32bea5f8b6c4d912efad456bdb98 GIT binary patch literal 44770 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 literal 0 HcmV?d00001 diff --git a/.github/doc-review-agent/agent.py b/.github/doc-review-agent/agent.py index 81dcfe4..b26fbf7 100644 --- a/.github/doc-review-agent/agent.py +++ b/.github/doc-review-agent/agent.py @@ -103,6 +103,23 @@ def get_pr_head_sha() -> str: return pr["head"]["sha"] +def get_pr_info() -> dict: + """Return the full PR object (title, body, head sha, etc.).""" + return gh_get(f"/repos/{REPO}/pulls/{PR_NUMBER}") + + +def update_pr_description(new_body: str) -> None: + """Prepend the auto-generated summary block to the PR description.""" + import requests as _req + r = _req.patch( + f"{GH_API}/repos/{REPO}/pulls/{PR_NUMBER}", + headers=GH_HEADERS, + json={"body": new_body}, + timeout=30, + ) + r.raise_for_status() + + def post_review(comments: list[ReviewComment], summary_body: str, commit_sha: str) -> None: """Post all inline comments + summary as a single PR review.""" gh_comments = [] @@ -229,6 +246,46 @@ def check_links(lines: list[str], file_path: str) -> list[tuple[int, str, str]]: return issues +def check_links_to_deleted(all_md_files: list[pathlib.Path], + deleted_paths: set[str]) -> list[tuple[str, int, str, str]]: + """ + Scan every tracked Markdown file for links that now point to a file + deleted/renamed in this PR. + Returns list of (file_path, line, severity, message). + """ + if not deleted_paths: + return [] + + link_pattern = re.compile(r'\[.*?\]\(([^)#]+)(?:#[^)]*)?\)') + results: list[tuple[str, int, str, str]] = [] + + for md_file in all_md_files: + try: + rel = md_file.relative_to(WORKSPACE).as_posix() + except ValueError: + continue + try: + lines = md_file.read_text(encoding="utf-8").splitlines() + except Exception: + continue + base_dir = md_file.parent + for i, line in enumerate(lines, 1): + for m in link_pattern.finditer(line): + target = m.group(1).strip() + if target.startswith("http"): + continue + resolved = (base_dir / target).resolve() + try: + resolved_rel = resolved.relative_to(WORKSPACE.resolve()).as_posix() + except ValueError: + continue + if resolved_rel in deleted_paths: + results.append((rel, i, "Error", + f"Link `{target}` points to `{resolved_rel}` which was " + "deleted or renamed in this PR. Update or remove the link.")) + return results + + def check_code_blocks(lines: list[str]) -> list[tuple[int, str, str]]: issues = [] for i, line in enumerate(lines, 1): @@ -436,6 +493,51 @@ def check_bilingual_pair(file_path: str) -> list[tuple[int, str, str]]: return issues +def build_translation_sync_table(pr_files: list[dict]) -> str: + """ + Build a Markdown table listing every changed doc file whose bilingual + counterpart was NOT also changed in this PR. + Returns an empty string if everything is in sync. + """ + md_files = [f["filename"] for f in pr_files + if f.get("status") != "removed" + and (f["filename"].startswith("en/") or f["filename"].startswith("zh/")) + and f["filename"].endswith(".md")] + pr_paths = set(md_files) + + out_of_sync: list[tuple[str, str, str]] = [] # (changed, counterpart, exists_on_disk) + seen: set[str] = set() + + for fpath in md_files: + if fpath in seen: + continue + if fpath.startswith("en/"): + pair = "zh/" + fpath[3:] + else: + pair = "en/" + fpath[3:] + + if pair not in pr_paths: + exists = "✅ exists" if (WORKSPACE / pair).exists() else "❌ missing" + out_of_sync.append((fpath, pair, exists)) + seen.add(fpath) + seen.add(pair) + + if not out_of_sync: + return "" + + rows = "\n".join( + f"| `{changed}` | `{counterpart}` | {status} |" + for changed, counterpart, status in out_of_sync + ) + return ( + "### 🌐 Translation Sync\n\n" + "The following files were changed without updating their language counterpart:\n\n" + "| Changed file | Counterpart not in PR | Counterpart on disk |\n" + "|---|---|---|\n" + f"{rows}\n" + ) + + def check_bilingual_sync(file_path: str, pr_file_paths: set[str]) -> list[tuple[int, str, str]]: """ If this file was modified in the PR but its language counterpart was NOT, @@ -587,8 +689,111 @@ def build_comment_body(sev: str, message: str, zh: bool) -> str: return f"{lbl} {message}\n\n" -def build_summary(result: ReviewResult, zh: bool) -> str: +def build_pr_description_block(pr_files: list[dict]) -> str: + """ + Auto-generate a structured summary block describing what changed in this PR. + Covers: which chips, which doc types, EN/ZH or both. + """ + chip_map = {"k1": "K1", "k3": "K3", "p1s": "P1S", "p1": "P1"} + doctype_map = { + "_usermanual": "User Manual", + "_hw": "Hardware Design", + "_sw": "Software / SDK", + "_docs": "Product Docs", + "_ds": "Datasheet", + "_hw_faq": "HW FAQ", + "_sw_faq": "SW FAQ", + } + + chips: set[str] = set() + doctypes: set[str] = set() + langs: set[str] = set() + added = added_count = 0 + modified = modified_count = 0 + removed_count = 0 + + for f in pr_files: + fpath = f["filename"] + status = f.get("status", "modified") + + if not fpath.endswith(".md"): + continue + + if fpath.startswith("en/"): + langs.add("EN") + elif fpath.startswith("zh/"): + langs.add("ZH") + + parts = fpath.lower().split("/") + for part in parts: + for key, label in chip_map.items(): + if part == key or part.startswith(key + "_") or part.startswith(key + "/"): + chips.add(label) + for part in parts: + for key, label in doctype_map.items(): + if key in part: + doctypes.add(label) + + if status == "added": + added_count += 1 + elif status == "removed": + removed_count += 1 + else: + modified_count += 1 + + chips_str = ", ".join(sorted(chips)) if chips else "—" + doctypes_str = ", ".join(sorted(doctypes)) if doctypes else "—" + langs_str = " + ".join(sorted(langs)) if langs else "—" + + change_parts = [] + if added_count: + change_parts.append(f"{added_count} added") + if modified_count: + change_parts.append(f"{modified_count} modified") + if removed_count: + change_parts.append(f"{removed_count} removed") + changes_str = ", ".join(change_parts) if change_parts else "no changes" + + return textwrap.dedent(f""" + + ## 📝 Auto-generated PR Summary + + | Field | Value | + |-------------|-------| + | Chips | {chips_str} | + | Doc types | {doctypes_str} | + | Languages | {langs_str} | + | Files | {changes_str} | + + > *Generated by doc-review-agent. Edit below this block to add context.* + + """).strip() + + +def update_pr_description_with_summary(pr_info: dict, pr_files: list[dict]) -> None: + """ + Prepend (or replace) the auto-summary block in the PR description. + Preserves any human-written content that follows the block. + """ + block = build_pr_description_block(pr_files) + existing_body = pr_info.get("body") or "" + + # Strip previous auto-block if present + start_marker = "" + end_marker = "" + if start_marker in existing_body and end_marker in existing_body: + s = existing_body.index(start_marker) + e = existing_body.index(end_marker) + len(end_marker) + existing_body = (existing_body[:s] + existing_body[e:]).strip() + + new_body = block + ("\n\n" + existing_body if existing_body else "") + update_pr_description(new_body) + + +def build_summary(result: ReviewResult, zh: bool, + sync_table: str = "") -> str: bot_tag = "" + sync_section = ("\n\n" + sync_table.strip()) if sync_table else "" if zh: return textwrap.dedent(f""" ## 📋 文档审阅摘要 @@ -601,7 +806,7 @@ def build_summary(result: ReviewResult, zh: bool) -> str: > 错误项表示信息缺失或有误,建议在合并前处理。 > 警告和建议仅供参考,最终合并决策由人工审阅者决定。 - + {sync_section} {bot_tag} """).strip() else: @@ -616,7 +821,7 @@ def build_summary(result: ReviewResult, zh: bool) -> str: > Errors indicate missing or incorrect information that should be addressed before merge. > Warnings and suggestions are advisory — human reviewer makes the final call. - + {sync_section} {bot_tag} """).strip() @@ -626,7 +831,8 @@ def main() -> None: system_prompt = PROMPT_PATH.read_text(encoding="utf-8") pr_files = get_pr_files() - commit_sha = get_pr_head_sha() + pr_info = get_pr_info() + commit_sha = pr_info["head"]["sha"] # Filter to Markdown doc files only include_re = [re.compile(p.replace("**", ".*").replace("*", "[^/]*")) @@ -638,10 +844,39 @@ def is_included(path: str) -> bool: return (any(r.fullmatch(path) for r in include_re) and not any(r.fullmatch(path) for r in exclude_re)) + # ── Item 5: Auto-update PR description ─────────────────────────────────── + print("Updating PR description with auto-summary...") + try: + update_pr_description_with_summary(pr_info, pr_files) + except Exception as e: + print(f" PR description update failed (non-fatal): {e}", file=sys.stderr) + + # ── Item 2: Check links pointing to deleted/renamed files ───────────────── + deleted_paths = { + f["filename"] for f in pr_files + if f.get("status") in ("removed", "renamed") + } + all_md_files = list(WORKSPACE.rglob("*.md")) + dangling_link_issues = check_links_to_deleted(all_md_files, deleted_paths) + result = ReviewResult() all_comments: list[ReviewComment] = [] has_zh = False + # Add dangling-link issues as review comments + for fpath, line_no, sev, msg in dangling_link_issues: + zh = is_zh(fpath) + if zh: + has_zh = True + body = build_comment_body(sev, msg, zh) + all_comments.append(ReviewComment(path=fpath, line=line_no, body=body, severity=sev)) + if sev.lower() in ("error", "错误"): + result.errors += 1 + elif sev.lower() in ("warning", "警告"): + result.warnings += 1 + else: + result.suggestions += 1 + for f in pr_files: fpath = f["filename"] status = f.get("status", "") @@ -683,7 +918,10 @@ def is_included(path: str) -> bool: else: result.suggestions += 1 - summary = build_summary(result, zh=has_zh) + # ── Item 1: Build PR-level translation sync table for the summary ───────── + sync_table = build_translation_sync_table(pr_files) + + summary = build_summary(result, zh=has_zh, sync_table=sync_table) if all_comments or result.errors + result.warnings + result.suggestions > 0: print(f"\nPosting review: {result.errors} errors, {result.warnings} warnings, " diff --git a/.github/workflows/doc-review.yml b/.github/workflows/doc-review.yml index 66bf6bb..ca93a9e 100644 --- a/.github/workflows/doc-review.yml +++ b/.github/workflows/doc-review.yml @@ -8,7 +8,7 @@ on: - "zh/**/*.md" permissions: - pull-requests: write # post review comments + pull-requests: write # post review comments and update PR description contents: read jobs: From 7490be88f578e72881bc4653287482c81dd43b54 Mon Sep 17 00:00:00 2001 From: luojunlin Date: Wed, 17 Jun 2026 11:38:43 +0800 Subject: [PATCH 5/5] syncup review --- .../k3/k3_docs/k3_usermanual/01_overview.md | 2 +- en/key_stone/k3/k3_docs/root_overview.md | 21 +++++---- zh/key_stone/k3/k3_docs/root_overview.md | 47 ++++++++++--------- 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/en/key_stone/k3/k3_docs/k3_usermanual/01_overview.md b/en/key_stone/k3/k3_docs/k3_usermanual/01_overview.md index 5a8cd5e..3577110 100644 --- a/en/key_stone/k3/k3_docs/k3_usermanual/01_overview.md +++ b/en/key_stone/k3/k3_docs/k3_usermanual/01_overview.md @@ -60,7 +60,7 @@ The K3 series chips are mainly used in AI consumer hardware, such as AI smart ho - 8 × PCIe Gen3 lanes (8 Gbps/lane) with RC & EP modes, hot-plug supported - 3 × USB 3.0 Host, 1 × USB 3.0 DRD (Type-C), 1 × USB 2.0 Host - 4 × GMAC (RGMII, RMII, MII) with TSN protocol support -- 6 × SPI, 2 × eSPI, 17 × UART, 10 × CAN, 9 × I²C, 30 × PWM +- 6 × SPI, 2 × eSPI, 17 × UART, 10 × CAN-FD, 9 × I²C, 30 × PWM **Power** diff --git a/en/key_stone/k3/k3_docs/root_overview.md b/en/key_stone/k3/k3_docs/root_overview.md index 8996596..37e1efe 100644 --- a/en/key_stone/k3/k3_docs/root_overview.md +++ b/en/key_stone/k3/k3_docs/root_overview.md @@ -1,3 +1,8 @@ +--- +title: K3 Brief +sidebar_position: 1 +--- + # K3 Brief Click to download **[K3 Brief (PDF)](https://cdn-resource.spacemit.com/file/chip/K3/K3_brief_en.pdf)** @@ -10,7 +15,7 @@ SpacemiT K3 series chips adopt RISC-V homogeneous integrated computing technolog The K3 series chips are mainly used in AI consumer hardware, such as AI smart home devices, AI-powered conference and office solutions, AI content creation tools, AI-driven e-commerce and retail systems, and other fields. - **Exceptional CPU Performance** - 8 high-performance X100 cores, up to 2.4 GHz, delivering 130 K DMIPS + 8 high-performance X100 cores, up to 2.4 GHz, delivering 130 KDMIPS RVA23 profile compliant, with single-core SPECint2006 > 9.0/GHz (comparable to Arm Cortex-A76) - **General-Purpose AI Performance** @@ -38,7 +43,7 @@ The K3 series chips are mainly used in AI consumer hardware, such as AI smart ho - 8× X100™ 64-bit RISC-V AI processor cores - X100™ is a quad-issue, out-of-order high-performance core - 8 MB shared L2 cache per 8-core cluster -- **60TOPS General-Purpose AI Compute** +- **60 TOPS General-Purpose AI Compute** - 8-core A100™ delivers up to 60 TOPS AI performance - Model throughput > 10 Tokens/s @ 30B - Supports FP16, BF16, FP8, INT8, and INT4 data formats @@ -53,8 +58,8 @@ The K3 series chips are mainly used in AI consumer hardware, such as AI smart ho - Hardware acceleration for AES / SHA / RSA / SM2 / SM3 / SM4 - Full product lifecycle security management support - **Memory** - - 64-bit LPDDR5 - 6400Mbps - - 64-bit LPDDR4x - 4266Mbps + - 64-bit LPDDR5 - 6400 Mbps + - 64-bit LPDDR4x - 4266 Mbps - Up to 32 GB capacity, with bandwidth up to 51 GB/s - **Storage** - SPI flash @@ -70,7 +75,7 @@ The K3 series chips are mainly used in AI consumer hardware, such as AI smart ho - 4K@180fps decoding for H.265, H.264, VP9, and other formats - 4K@90fps encoding for H.265, H.264, and other formats - Dual 3840×2160@60fps display outputs - - MIPI-DSI 8-lane display output, 4.5Gbps/lane, supporting: + - MIPI-DSI 8-lane display output, 4.5 Gbps/lane, supporting: - 3840×2160@60fps - 2560×1440@90fps - 1920×1080@60fps, etc. @@ -80,7 +85,7 @@ The K3 series chips are mainly used in AI consumer hardware, such as AI smart ho - 4× MIPI-CSI, 12 lanes: 4 + 4 + (2 + 2) - Supports up to 12 camera inputs - **Interfaces** - - 8× PCIe lanes (8Gbps per lane), across 5 PCIe controllers + - 8× PCIe lanes (8 Gbps per lane), across 5 PCIe controllers - PCIe x8 supports both RC and EP modes - Hot-plug supported - 3× USB 3.0 Host (combo with PCIe, includes USB 2.0) @@ -88,10 +93,10 @@ The K3 series chips are mainly used in AI consumer hardware, such as AI smart ho - 1× USB 2.0 Host - 4× GMAC (RGMII & RMII & MII) - TSN protocol support - - 6× SPI, 2× eSPI, 17× UART, 10× CAN-FD, 9× I2C, 30× PWM + - 6× SPI, 2× eSPI, 17× UART, 10× CAN-FD, 9× I²C, 30× PWM - **Power** - TDP: 15W–25W ## Block Diagram -![](./static/k3_block_diagram.png) +![K3 Block Diagram](./static/k3_block_diagram.png) diff --git a/zh/key_stone/k3/k3_docs/root_overview.md b/zh/key_stone/k3/k3_docs/root_overview.md index e63528d..4dae86d 100644 --- a/zh/key_stone/k3/k3_docs/root_overview.md +++ b/zh/key_stone/k3/k3_docs/root_overview.md @@ -1,4 +1,7 @@ +--- +title: K3 产品简介 sidebar_position: 1 +--- # K3 产品简介 @@ -8,15 +11,15 @@ sidebar_position: 1 **K3 是运行 30B 大模型的高性能 RISC-V AI CPU** -SpacemiT Key Stone K3 系列芯片采用 RISC-V 同构融合计算技术,集成进迭时空的 8 个高性能计算大核 X100 及 8 个超宽并行计算 AI 核 A100,可提供 130 KDMIPS 通用算力及 60TOPS 通用 AI 算力,可流畅运行 300 亿参数模型。 +SpacemiT Key Stone K3 系列芯片采用 RISC-V 同构融合计算技术,集成进迭时空的 8 个高性能计算大核 X100 及 8 个超宽并行计算 AI 核 A100,可提供 130 KDMIPS 通用算力及 60 TOPS 通用 AI 算力,可流畅运行 300 亿参数模型。 K3 系列芯片主要应用在 AI 消费硬件如 AI 智慧家居、AI 会议办公、AI 内容创作、AI 电商零售等领域。 - **卓越的 CPU 性能** - 8 个高性能计算大核 X100,最大主频 2.4GHz,130KDMIPS 算力 - 支持完整 RVA23 Profile,单核Specint2006>9.0/GHz,等效ARM-A76 + 8 个高性能计算大核 X100,最大主频 2.4 GHz,130 KDMIPS 算力 + 支持完整 RVA23 Profile,单核 SpecINT2006 > 9.0/GHz,等效 ARM A76 - **通用 AI 算力** - 60TOPS AI算力,支持 BF16、FP16、FP8、INT8、INT4 等数据类型 + 60 TOPS AI 算力,支持 BF16、FP16、FP8、INT8、INT4 等数据类型 可流畅运行 30B 本地模型,综合智力性能是 235B 模型的 84% - **最新的 RISC-V 架构,超级并行计算能力** @@ -32,7 +35,7 @@ K3 系列芯片主要应用在 AI 消费硬件如 AI 智慧家居、AI 会议办 支持国密 SM2/3/4 硬件安全技术 - **符合工业级标准** - 在 -40˚C ~ 85˚C 的温度下仍能提供稳定可靠的持续算力输出,满足工业应用的苛刻环境需求 + 在 –40°C ~ 85°C 的温度下仍能提供稳定可靠的持续算力输出,满足工业应用的苛刻环境需求 ## 产品特性 @@ -40,9 +43,9 @@ K3 系列芯片主要应用在 AI 消费硬件如 AI 智慧家居、AI 会议办 - 8 核 X100™ 64 位 RISC-V AI 处理器 - X100™ 是四发射乱序执行的高性能计算大核 - 每 8 核共享 8MB L2 Cache -- **60TOPS 通用 AI 算力** - - 8 核 A100™ 提供 60TOPS AI 算力 - - 模型性能 > 10Tokens/S@30B +- **60 TOPS 通用 AI 算力** + - 8 核 A100™ 提供 60 TOPS AI 算力 + - 模型性能 > 10 Tokens/s @ 30B - 支持 FP16、BF16、FP8、INT8、INT4 等数据格式 - 支持所有 AI 算法和模型部署 - **RISC-V 硬件虚拟化** @@ -55,9 +58,9 @@ K3 系列芯片主要应用在 AI 消费硬件如 AI 智慧家居、AI 会议办 - 支持 AES/SHA/RSA/SM2/SM3/SM4 等算法 - 支持产品生命周期安全管理 - **内存** - - 64-bit LPDDR5 - 6400Mbps - - 64-bit LPDDR4x - 4266Mbps - - 最大支持 32GB 内存容量,带宽可达 51GB/s + - 64-bit LPDDR5 - 6400 Mbps + - 64-bit LPDDR4x - 4266 Mbps + - 最大支持 32 GB 内存容量,带宽可达 51 GB/s - **存储** - 支持 SPI 闪存 - 支持 eMMC 5.1 @@ -71,18 +74,18 @@ K3 系列芯片主要应用在 AI 消费硬件如 AI 智慧家居、AI 会议办 - 集成 3D 图形引擎支持 Vulkan、OpenCL、OpenGL ES - 4K@180fps H.265/H.264/VP9 等格式解码能力 - 4K@90fps H.265/H.264 等格式编码能力 - - 2 路 3840*2160@60fps 屏异显 - - MIPI-DSI 8Lane 显示输出,4.5Gbps/Lane - - 支持 3840*2160@60fps - - 支持 2560*1440@90fps - - 支持 1920*1080@60fps 等 + - 2 路 3840×2160@60fps 屏异显 + - MIPI-DSI 8Lane 显示输出,4.5 Gbps/Lane + - 支持 3840×2160@60fps + - 支持 2560×1440@90fps + - 支持 1920×1080@60fps 等 - 2 路 DP/eDP 显示输出 - - 支持 3840*2160@60fps - - 支持 2560*1440@144fps 等 - - 4 路 MIPI-CSI 12Lanes 4 + 4 +( 2 + 2 ) + - 支持 3840×2160@60fps + - 支持 2560×1440@144fps 等 + - 4 路 MIPI-CSI 12 Lanes 4 + 4 +( 2 + 2 ) - 支持 12 路摄像头输入 - **接口** - - 8×PCIe lanes(8Gbps/Lane),5 套 PCIe 控制器 + - 8× PCIe lanes(8 Gbps/Lane),5 套 PCIe 控制器 - PCIe x8 支持 RC&EP 模式 - 支持热插拔功能 - 3× USB3.0 Host(Combo with PCIe、含 USB2.0) @@ -90,10 +93,10 @@ K3 系列芯片主要应用在 AI 消费硬件如 AI 智慧家居、AI 会议办 - 1× USB2.0 Host - 4× GMAC(RGMII & RMII & MII) - 支持 TSN 协议 - - 6× SPI、2× eSPI、17× UART、10× Can-FD、9× I2C、30× PWM + - 6× SPI、2× eSPI、17× UART、10× CAN-FD、9× I²C、30× PWM - **功耗** - TDP:15W~25W ## 框图 -![](./static/k3_block_diagram.png) +![K3 框图](./static/k3_block_diagram.png)