chore: enhance RTL style checks and enforce naming conventions

- Updated  to enforce stricter naming rules for new owned RTL.
- Expanded documentation on RTL coding style to include detailed naming contracts.
- Refactored RTL code to comply with new naming conventions, including:
  - Renaming typedefs to use  suffix for enums and  for other types.
  - Ensuring module names use lower snake case and ports have appropriate direction suffixes.
  - Adjusting instance names to start with  and ensuring state variables use  and  suffixes.
- Modified scripts to include checks for new naming rules and updated tests to validate these changes.
- Updated  to reflect new naming guidelines and exceptions.
This commit is contained in:
maksyuki committed 2026-08-13 12:01:38 +08:00
1 parent b67f4d3d9c
commit 1e37314a99
18 files changed
+436 -80

No files matched your search

+1 -1
View File
@@ -441,7 +441,7 @@ rtl-migrate-names:
rtl-style-check:
python3 $(ROOT_PATH)/scripts/check_rtl_style.py --root $(ROOT_PATH) \
--profile owned --changed-only --verible-verilog-lint $(VERIBLE_LINT)
--profile owned --changed-only --enforce-naming --verible-verilog-lint $(VERIBLE_LINT)
rtl-style-check-all:
python3 $(ROOT_PATH)/scripts/check_rtl_style.py --root $(ROOT_PATH) \
+151 -2
View File
@@ -61,6 +61,152 @@ Use `clk_i`, `rst_n_i`, `_i`, `_o`, and `_io` for ports; `s_` for local signals;
`u_` for instances; and adjacent `s_<name>_d`/`s_<name>_q` for state. A state
write enable is `s_<name>_en`.
The staged naming contract is checked for changed owned RTL by
`rtl-style-check`. It is intentionally stricter for new code than the
historical baseline:
| Object | Required form | Example |
| --- | --- | --- |
| module | lower snake case | `axi4_interconnect` |
| port | semantic name plus direction suffix | `addr_i`, `resp_err_o`, `data_io` |
| low-active port | polarity before direction | `irq_n_o`; project reset spelling is `rst_n_i` |
| local signal | `s_` plus semantic name | `s_cmd_valid`, `s_timeout_q` |
| instance | `u_` plus role or function | `u_rx_fifo`, `u_cfg_ribp` |
| next/current state | `_d` / `_q` | `s_state_d`, `s_state_q` |
| pipeline state | `_q2` / `_q3` | `s_addr_q2` |
| enum typedef | lower snake case plus `_e` | `uart_state_e` |
| other typedef | lower snake case plus `_t` | `sysctrl_offset_t` |
| parameter | typed UpperCamelCase | `DataWidth`, `TimeoutCycles` |
| global macro | namespaced ALL_CAPS | `RETROSOC_UART__BAUD` |
Protocol and technology names are stable interface vocabulary, not local
abbreviations. Keep standard AXI/APB/RIBP fields such as `valid`, `ready`,
`wdata`, `wstrb`, `rdata`, and `resp` unchanged. Do not mechanically rename
software-visible register macros, PDK pins, generated bindings, or managed IP.
A public rename requires a compatibility wrapper, a filelist update, and an
affected simulation/formal review.
Use the approved local aliases in `rtl/rtl_style_manifest.json` only when they
remain unambiguous (`cmd`, `req`, `resp`, `addr`, `cfg`, `stat`, `cnt`, and
similar). New aliases require a manifest update and review. Instance names
must identify their function rather than their order: use `cfg_ribp` or
`u_sdram_data_ribp_if`, not `if0` or `bus1`.
New FSMs use a typed enum with an `_e` type and descriptive values; opcode,
response, register offset, and field constants remain ALL_CAPS because they
are protocol or software-facing values. Existing `FSM_*`, `SOC_*`, `RIB_*`,
and `RIBP_*` names are compatibility baseline and migrate only in isolated,
reviewed batches.
### 4.1 Identifier grammar and semantic order
For a new internal identifier, construct the name from the meaningful parts in
this order: `<block>_<channel>_<role>_<property>_<state>_<latency>_<polarity>_<direction>`.
Only include parts that add information. For example, use
`s_uart_rx_fifo_full`, `s_sdram_read_pending_q`, or `irq_n_o`; do not create
names such as `signal_a`, `tmp2`, or `data_new_new`.
The following rules are normative:
1. Use lower snake case for RTL identifiers. Do not mix camel case, hyphens,
or arbitrary capitalization in a single naming family.
2. Use the same name at adjacent hierarchy boundaries when a signal is passed
through unchanged. Rename only at a semantic conversion boundary.
3. Put polarity before direction: `ready_n_o`, `reset_n_i`, and
`data_io`. The project exception is the established spelling `rst_n_i`;
do not introduce `rst_ni`.
4. Name clocks `clk_i` for the default domain and `clk_<domain>_i` for other
domains, such as `clk_axi_i` or `clk_aud_i`. Name their resets
`rst_n_i` or `rst_<domain>_n_i`.
5. Use `_d` for next-state combinational storage and `_q` for registered
current state. Use `_q2` and `_q3` for explicit pipeline stages, not for
arbitrary copies. Pair declarations adjacently.
6. Use `_en` only for a state/register update enable. Use `_pulse` or `_evt`
for a one-cycle event, `_sticky_q` for software-cleared sticky state,
`_pending_q` for an issued-but-not-completed transaction, `_busy_q` for a
resource that cannot accept work, and `_cnt_q` or `_idx_q` for counters and
indices.
7. Keep `valid`, `ready`, `accept`, `pending`, `busy`, `pulse`, and `sticky`
distinct. For a handshake, prefer `s_req_valid`, `s_req_ready`, and
`s_req_accept` (`valid && ready`) over an ambiguous `s_req` signal.
8. Use full words in public APIs and the approved short aliases only for local
signals. The whitelist is `cmd`, `req`, `resp`, `addr`, `en`, `err`,
`stat`, `cnt`, `cfg`, `src`, `dst`, `evt`, `tgt`, `sel`, and `len`.
9. Name errors by the failing contract: `cfg_err`, `access_err`, `resp_err`,
`timeout_err`, `pll_err`, and `overflow_err` are different conditions and
MUST NOT be collapsed into a generic `error` signal.
### 4.2 Modules, interfaces, and instances
1. Use lower snake case for modules and keep the source file and primary
module aligned. Use established functional suffixes: `_wrapper`, `_ctrl`
or `_controller`, `_reg`, `_core`, `_if`, `_pkg`, `_formal`,
`_formal_props`, and `_tb`.
2. Name bus adapters with the repository's numeric `2` convention:
`rib2apb`, `rib2ram`, `ribp2axi4`, `axi42ribp`, and `axi42ram`. New code
MUST NOT introduce another `_to_` spelling for the same conversion.
3. Use function-qualified interface instances such as `cfg_ribp`,
`mem_ribp`, `dma_axi`, and `apb_periph`. Do not use `if0`, `bus0`,
`interface_a`, or names based only on declaration order.
4. Keep standard protocol field names unchanged. AXI, APB, RIB, and RIBP
fields such as `valid`, `ready`, `wdata`, `wstrb`, `rdata`, `resp`, and
`resp_err` are interoperability vocabulary, not local style debt.
5. Existing generic public modules such as `rcu` or `bus` are not renamed in
place. New functionality should use a qualified name such as
`soc_rcu`, `pll_rcu_controller`, `rib_bus`, or `axi4_bus`; migrate an
existing public name only through a wrapper and a reviewed filelist change.
### 4.3 Parameters, types, FSMs, and constants
1. Name module parameters with typed UpperCamelCase: `DataWidth`, `AddrWidth`,
`NumMasters`, `NumTargets`, `FifoDepth`, `TimeoutCycles`, `ExtClkHz`, and
`ResetValue`. Do not use unqualified `DW`, `AW`, `N`, `NUM`, or `PARAM1`.
2. Use UpperCamelCase for module-private `localparam` values as well. Put
protocol ABI and register-map constants in a package or reviewed header;
keep implementation-only masks and timing constants local.
3. Name typedefs with lower snake case and `_t`; name enum typedefs with `_e`.
New FSMs use a distinct typed enum for each state machine, for example:
```systemverilog
typedef enum logic [2:0] {
Idle,
ReadReq,
ReadResp,
ErrorResp
} state_e;
state_e s_state_d, s_state_q;
```
Use descriptive UpperCamelCase values for states. Use ALL_CAPS for
opcodes, response codes, register offsets, field positions, and other
protocol/software-visible constants.
4. Name global macros with a block namespace and a double underscore, such as
`RETROSOC_RIB__RESP_OK`, `RETROSOC_RIBP_UART__BAUD`, and
`RETROSOC_GPIO__PIN_COUNT`. Use include guards such as
`RETROSOC_<FILE>_SVH`. Local macros use a single leading underscore and
MUST be undefined after use.
5. Do not use a macro for a value that can be expressed as a parameter,
localparam, package constant, function, or enum. Existing `SOC_*`,
`RIB_*`, `RIBP_*`, and standard protocol macro names remain compatibility
exceptions until their consumers are migrated.
### 4.4 Compatibility and migration boundaries
1. Apply the strict naming rules to new or modified self-owned RTL first. The
existing tree is migrated in module-sized batches with a reviewed baseline;
a new change MUST NOT increase the legacy naming debt.
2. Do not mechanically rename ClusterIP Common, PDK, generated, managed,
vendored, or third-party sources. Update those through their upstream or
locked integration flow.
3. Do not change software-visible register macros, memory-map symbols,
protocol fields, module filelist entries, or technology pin names as part of
a local style cleanup. Preserve the old public name with a compatibility
wrapper or alias until every consumer has moved.
4. A public rename is complete only after the wrapper/filelist migration,
Verible and Verilator checks, affected simulation and formal proofs, and a
review of generated artifacts. Keep public renames separate from functional
changes so equivalence and regression failures are attributable.
Public interfaces, protocol fields, register fields, and technology pins keep
their required names. Local signals MAY use only approved concise aliases:
@@ -83,8 +229,11 @@ their required names. Local signals MAY use only approved concise aliases:
| length | `len` |
New abbreviations require review and an update to `rtl/rtl_style_manifest.json`.
Use `_t` for typedefs, `_e` for enum types, `ALL_CAPS` for macros, and the
repository's established parameter naming convention.
Use `_t` for typedefs, `_e` for enum types, `ALL_CAPS` for macros, and typed
UpperCamelCase for new parameters. Global macros use the
`RETROSOC_<BLOCK>__<NAME>` namespace; IP register macros use
`RIBP_<IP>__<REGISTER>`. Prefer a package, enum, localparam, or parameter over
a macro when the value does not cross a preprocessing boundary.
Declare explicit widths and signedness for parameters, ports, constants, and
internal signals. Use sized literals such as `8'h00` and `16'd42`. Do not rely
+9 -1
View File
@@ -38,12 +38,20 @@ RTL and local changes to existing RTL must preserve them:
or `_VALUE` suffixes; keep implementation-only masks and constant values as
typed `localparam` declarations in the owning module.
Changed owned RTL is also checked for module lower snake case, `u_` instance
names, port direction suffixes, `_d/_q` state pairs, `_e/_t` typedef suffixes,
typed UpperCamelCase parameters, and namespaced macros. This staged check does
not apply mechanically to protocol fields, PDK pins, generated bindings, or
managed/third-party sources. Use compatibility wrappers for public renames.
Run `verible-verilog-format --flagfile=.verible-format` on changed RTL. Where
the formatter cannot preserve required macro or port-column alignment, use a
narrow `// verilog_format: off/on` region and align the enclosed declarations
manually.
The complete ownership-aware rules, short-name abbreviations, and migration
The complete ownership-aware rules, including semantic signal grammar,
handshake/error vocabulary, module/interface/instance suffixes, parameter and
macro namespaces, FSM examples, compatibility boundaries, and migration
policy are documented in [RTL Coding Style](../docs/rtl-coding-style.md).
`make rtl-style-check` rejects new positional connections and legacy
constructs in changed self-owned RTL while the existing migration backlog is
+19 -19
View File
@@ -66,14 +66,14 @@ module i2c_core (
FSM_RECOVERY_LOW,
FSM_RECOVERY_HIGH_WAIT,
FSM_RECOVERY_HIGH
} i2c_fsm_t;
} i2c_fsm_e;
typedef enum logic [1:0] {
BIT_TX,
BIT_RX,
BIT_TARGET_ACK,
BIT_CONTROLLER_ACK
} i2c_bit_action_t;
} i2c_bit_action_e;
typedef enum logic [2:0] {
BYTE_ADDR_7,
@@ -82,16 +82,16 @@ module i2c_core (
BYTE_ADDR_10_HEADER_R,
BYTE_WRITE,
BYTE_READ
} i2c_byte_role_t;
} i2c_byte_role_e;
localparam int unsigned COUNTER_WIDTH = 24;
localparam int unsigned CounterWidth = 24;
i2c_fsm_t s_fsm_d, s_fsm_q;
i2c_bit_action_t s_bit_action_d, s_bit_action_q;
i2c_byte_role_t s_byte_role_d, s_byte_role_q;
logic [$bits(i2c_fsm_t)-1:0] s_fsm_d_raw, s_fsm_q_raw;
logic [$bits(i2c_bit_action_t)-1:0] s_bit_action_d_raw, s_bit_action_q_raw;
logic [$bits(i2c_byte_role_t)-1:0] s_byte_role_d_raw, s_byte_role_q_raw;
i2c_fsm_e s_fsm_d, s_fsm_q;
i2c_bit_action_e s_bit_action_d, s_bit_action_q;
i2c_byte_role_e s_byte_role_d, s_byte_role_q;
logic [$bits(i2c_fsm_e)-1:0] s_fsm_d_raw, s_fsm_q_raw;
logic [$bits(i2c_bit_action_e)-1:0] s_bit_action_d_raw, s_bit_action_q_raw;
logic [$bits(i2c_byte_role_e)-1:0] s_byte_role_d_raw, s_byte_role_q_raw;
logic [23:0] s_phase_count_d, s_phase_count_q;
logic [23:0] s_stretch_count_d, s_stretch_count_q;
logic [23:0] s_cmd_count_d, s_cmd_count_q;
@@ -124,11 +124,11 @@ module i2c_core (
assign s_data_low_cycles = {1'b0, data_hold_cycles_i} + {1'b0, data_setup_cycles_i};
assign s_fsm_d_raw = s_fsm_d;
assign s_fsm_q = i2c_fsm_t'(s_fsm_q_raw);
assign s_fsm_q = i2c_fsm_e'(s_fsm_q_raw);
assign s_bit_action_d_raw = s_bit_action_d;
assign s_bit_action_q = i2c_bit_action_t'(s_bit_action_q_raw);
assign s_bit_action_q = i2c_bit_action_e'(s_bit_action_q_raw);
assign s_byte_role_d_raw = s_byte_role_d;
assign s_byte_role_q = i2c_byte_role_t'(s_byte_role_q_raw);
assign s_byte_role_q = i2c_byte_role_e'(s_byte_role_q_raw);
assign s_tx_bit = s_tx_byte_q[s_bit_index_q];
assign s_phase_done = timing_expired(s_phase_count_q, scl_high_cycles_i);
assign s_low_phase_done = timing_expired(
@@ -670,7 +670,7 @@ module i2c_core (
end
dffr #(
.DATA_WIDTH($bits(i2c_fsm_t))
.DATA_WIDTH($bits(i2c_fsm_e))
) u_fsm_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
@@ -679,7 +679,7 @@ module i2c_core (
);
dffr #(
.DATA_WIDTH($bits(i2c_bit_action_t))
.DATA_WIDTH($bits(i2c_bit_action_e))
) u_bit_action_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
@@ -688,7 +688,7 @@ module i2c_core (
);
dffr #(
.DATA_WIDTH($bits(i2c_byte_role_t))
.DATA_WIDTH($bits(i2c_byte_role_e))
) u_byte_role_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
@@ -697,7 +697,7 @@ module i2c_core (
);
dffr #(
.DATA_WIDTH(COUNTER_WIDTH)
.DATA_WIDTH(CounterWidth)
) u_phase_count_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
@@ -706,7 +706,7 @@ module i2c_core (
);
dffr #(
.DATA_WIDTH(COUNTER_WIDTH)
.DATA_WIDTH(CounterWidth)
) u_stretch_count_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
@@ -715,7 +715,7 @@ module i2c_core (
);
dffr #(
.DATA_WIDTH(COUNTER_WIDTH)
.DATA_WIDTH(CounterWidth)
) u_command_count_dffr (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
+6 -6
View File
@@ -13,22 +13,22 @@ module uart_baudgen (
// verilog_format: on
);
localparam logic [31:0] SAMPLE_STEP = 32'd4096;
localparam logic [31:0] SampleStep = 32'd4096;
logic [31:0] s_period;
logic [31:0] s_phase_d, s_phase_q;
logic [3:0] s_sample_count_d, s_sample_count_q;
logic [32:0] s_phase_sum;
assign s_period = {baud_int_i, baud_frac_i};
assign s_phase_sum = {1'b0, s_phase_q} + {1'b0, SAMPLE_STEP};
assign sample_tick_o = enable_i && (s_period >= SAMPLE_STEP) && (s_phase_sum >= {1'b0, s_period});
assign bit_tick_o = sample_tick_o && (s_sample_count_q == 4'd15);
assign s_period = {baud_int_i, baud_frac_i};
assign s_phase_sum = {1'b0, s_phase_q} + {1'b0, SampleStep};
assign sample_tick_o = enable_i && (s_period >= SampleStep) && (s_phase_sum >= {1'b0, s_period});
assign bit_tick_o = sample_tick_o && (s_sample_count_q == 4'd15);
always_comb begin
s_phase_d = s_phase_q;
s_sample_count_d = s_sample_count_q;
if (!enable_i || (s_period < SAMPLE_STEP)) begin
if (!enable_i || (s_period < SampleStep)) begin
s_phase_d = '0;
s_sample_count_d = '0;
end else if (sample_tick_o) begin
+3 -3
View File
@@ -37,9 +37,9 @@ module ws2812_core (
WS_IDLE,
WS_XFER,
WS_RESET
} ws_state_t;
} ws_state_e;
ws_state_t s_state_d, s_state_q;
ws_state_e s_state_d, s_state_q;
logic [15:0] s_bit_cycles_d, s_bit_cycles_q;
logic [15:0] s_t0h_cycles_d, s_t0h_cycles_q;
logic [15:0] s_t1h_cycles_d, s_t1h_cycles_q;
@@ -181,7 +181,7 @@ module ws2812_core (
end
dffercn #(
.REG_TYPE (ws_state_t),
.REG_TYPE (ws_state_e),
.RESET_VAL(WS_IDLE)
) u_state_dffercn (
.clk_i (clk_i),
+2 -2
View File
@@ -58,9 +58,9 @@ module i2c_formal_design (
assign rib_resp_err = ribp.resp_err;
assign command_count = u_dut.u_i2c_reg.s_cmd_count;
assign rx_count = u_dut.u_i2c_reg.s_rx_count;
assign error_status = u_dut.u_i2c_reg.s_error_status_q;
assign error_status = u_dut.u_i2c_reg.s_err_stat_q;
assign intr_state = u_dut.u_i2c_reg.s_intr_state_q;
assign intr_enable = u_dut.u_i2c_reg.s_intr_enable_q;
assign intr_enable = u_dut.u_i2c_reg.s_intr_en_q;
assign command_valid = u_dut.s_cmd_valid;
assign command_pop = u_dut.s_cmd_pop;
assign rx_full = u_dut.s_rx_full;
+2 -2
View File
@@ -42,12 +42,12 @@ module pll_rcu_formal_design (
assign pll_capable = f_pll_capable;
assign state = u_dut.s_state_q;
assign pll_apply = u_dut.s_pll_apply_q;
assign select_ext_clk = u_dut.s_select_ext_clk_q;
assign select_ext_clk = u_dut.s_sel_ext_clk_q;
assign active_valid = u_dut.s_active_valid_q;
assign safe_clk = u_dut.s_safe_clk_q;
assign active_lock = u_dut.s_lock_q;
assign lock_seen_low = u_dut.s_lock_seen_low_q;
assign error = u_dut.s_error_q;
assign error = u_dut.s_err_q;
// Both controller domains share the formal clock. This proves the control
// FSM and the production cdc_2phase composition, not asynchronous CDC timing.
+6 -6
View File
@@ -62,16 +62,16 @@ module uart_formal_design (
assign rib_resp_err = ribp.resp_err;
assign tx_count = u_dut.u_uart_reg.s_tx_count;
assign rx_count = u_dut.u_uart_reg.s_rx_count;
assign error_status = u_dut.u_uart_reg.s_error_status_q;
assign error_status = u_dut.u_uart_reg.s_err_stat_q;
assign intr_state = u_dut.u_uart_reg.s_intr_state_q;
assign intr_enable = u_dut.u_uart_reg.s_intr_enable_q;
assign tx_enable = u_dut.s_tx_enable;
assign rx_enable = u_dut.s_rx_enable;
assign intr_enable = u_dut.u_uart_reg.s_intr_en_q;
assign tx_enable = u_dut.s_tx_en;
assign rx_enable = u_dut.s_rx_en;
assign tx_busy = u_dut.s_tx_busy;
assign rx_active = u_dut.s_rx_active;
assign break_active = u_dut.s_break;
assign auto_cts_enable = u_dut.s_auto_cts_enable;
assign auto_rts_enable = u_dut.s_auto_rts_enable;
assign auto_cts_enable = u_dut.s_auto_cts_en;
assign auto_rts_enable = u_dut.s_auto_rts_en;
assign cts_asserted = u_dut.s_cts_asserted;
assign rts_asserted = u_dut.s_rts_asserted;
assign tx_start_allowed = u_dut.s_tx_start_allowed;
+2 -2
View File
@@ -61,9 +61,9 @@ module ws2812_formal_design (
assign aborted = u_dut.s_aborted;
assign fifo_level = u_dut.u_ws2812_reg.s_tx_count;
assign load_remaining = u_dut.u_ws2812_reg.s_load_remaining_q;
assign error_status = u_dut.u_ws2812_reg.s_error_status_q;
assign error_status = u_dut.u_ws2812_reg.s_err_stat_q;
assign intr_state = u_dut.u_ws2812_reg.s_intr_state_q;
assign intr_enable = u_dut.u_ws2812_reg.s_intr_enable_q;
assign intr_enable = u_dut.u_ws2812_reg.s_intr_en_q;
ribp_ws2812 u_dut (
.clk_i (clk_i),
@@ -152,15 +152,15 @@ def render_core_bindings(extensions: ExtensionMap) -> str:
[
"",
" always_comb begin",
" rib.cmd_valid = '0;",
" rib.cmd_addr = '0;",
" rib.cmd_write = '0;",
" rib.cmd_len = '0;",
" rib.w_valid = '0;",
" rib.wdata = '0;",
" rib.wstrb = '0;",
" rib.wlast = '0;",
" rib.rsp_ready = '0;",
" u_user_rib_if.cmd_valid = '0;",
" u_user_rib_if.cmd_addr = '0;",
" u_user_rib_if.cmd_write = '0;",
" u_user_rib_if.cmd_len = '0;",
" u_user_rib_if.w_valid = '0;",
" u_user_rib_if.wdata = '0;",
" u_user_rib_if.wstrb = '0;",
" u_user_rib_if.wlast = '0;",
" u_user_rib_if.rsp_ready = '0;",
]
)
for target in extensions.core_targets:
@@ -182,23 +182,23 @@ def render_core_bindings(extensions: ExtensionMap) -> str:
lines.extend(
[
f" {extensions.core_selector_width}'d{target.slot}: begin",
f" rib.cmd_valid = u_user_{target.slot}_rib_if.cmd_valid;",
f" rib.cmd_addr = u_user_{target.slot}_rib_if.cmd_addr;",
f" rib.cmd_write = u_user_{target.slot}_rib_if.cmd_write;",
f" rib.cmd_len = u_user_{target.slot}_rib_if.cmd_len;",
f" rib.w_valid = u_user_{target.slot}_rib_if.w_valid;",
f" rib.wdata = u_user_{target.slot}_rib_if.wdata;",
f" rib.wstrb = u_user_{target.slot}_rib_if.wstrb;",
f" rib.wlast = u_user_{target.slot}_rib_if.wlast;",
f" rib.rsp_ready = u_user_{target.slot}_rib_if.rsp_ready;",
f" u_user_{target.slot}_rib_if.cmd_ready = rib.cmd_ready;",
f" u_user_{target.slot}_rib_if.w_ready = rib.w_ready;",
f" u_user_{target.slot}_rib_if.rsp_valid = rib.rsp_valid;",
f" u_user_{target.slot}_rib_if.rdata = rib.rdata;",
f" u_user_{target.slot}_rib_if.resp_err = rib.resp_err;",
f" u_user_{target.slot}_rib_if.resp_code = rib.resp_code;",
f" u_user_{target.slot}_rib_if.rsp_beat = rib.rsp_beat;",
f" u_user_{target.slot}_rib_if.rsp_last = rib.rsp_last;",
f" u_user_rib_if.cmd_valid = u_user_{target.slot}_rib_if.cmd_valid;",
f" u_user_rib_if.cmd_addr = u_user_{target.slot}_rib_if.cmd_addr;",
f" u_user_rib_if.cmd_write = u_user_{target.slot}_rib_if.cmd_write;",
f" u_user_rib_if.cmd_len = u_user_{target.slot}_rib_if.cmd_len;",
f" u_user_rib_if.w_valid = u_user_{target.slot}_rib_if.w_valid;",
f" u_user_rib_if.wdata = u_user_{target.slot}_rib_if.wdata;",
f" u_user_rib_if.wstrb = u_user_{target.slot}_rib_if.wstrb;",
f" u_user_rib_if.wlast = u_user_{target.slot}_rib_if.wlast;",
f" u_user_rib_if.rsp_ready = u_user_{target.slot}_rib_if.rsp_ready;",
f" u_user_{target.slot}_rib_if.cmd_ready = u_user_rib_if.cmd_ready;",
f" u_user_{target.slot}_rib_if.w_ready = u_user_rib_if.w_ready;",
f" u_user_{target.slot}_rib_if.rsp_valid = u_user_rib_if.rsp_valid;",
f" u_user_{target.slot}_rib_if.rdata = u_user_rib_if.rdata;",
f" u_user_{target.slot}_rib_if.resp_err = u_user_rib_if.resp_err;",
f" u_user_{target.slot}_rib_if.resp_code = u_user_rib_if.resp_code;",
f" u_user_{target.slot}_rib_if.rsp_beat = u_user_rib_if.rsp_beat;",
f" u_user_{target.slot}_rib_if.rsp_last = u_user_rib_if.rsp_last;",
f" s_user_{target.slot}_irq = irq_i;",
" end",
]
+2 -2
View File
@@ -21,7 +21,7 @@ module user_core_top (
// verilog_format: on
);
rib_if rib ();
rib_if u_user_rib_if ();
// Generated bindings preserve scalar user-core interfaces and isolation.
`include "user_core_bindings.svh"
@@ -30,7 +30,7 @@ module user_core_top (
rib2axi4 u_rib2axi4 (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.rib (rib),
.rib (u_user_rib_if),
.axi4 (axi4)
);
// verilog_format: on
+55 -1
View File
@@ -58,5 +58,59 @@
"select": "sel",
"length": "len"
},
"reset_suffix": "rst_n_i"
"reset_suffix": "rst_n_i",
"naming": {
"signal_case": "lower_snake_case",
"port_suffixes": [
"_i",
"_o",
"_io"
],
"active_low_suffixes": [
"_n_i",
"_n_o",
"_n_io"
],
"internal_prefix": "s_",
"instance_prefix": "u_",
"state_suffixes": [
"_d",
"_q",
"_q2",
"_q3"
],
"typedef_suffixes": {
"enum": "_e",
"other": "_t"
},
"parameter_case": "UpperCamelCase",
"macro_prefix": "RETROSOC_",
"macro_namespace_separator": "__",
"interface_name_exceptions": [
"rib",
"ribp",
"axi4",
"apb",
"gpio",
"uart",
"spi",
"i2c",
"i2s",
"ws2812",
"sysctrl",
"pll_ctrl",
"sdram",
"sdio",
"dvp",
"psram",
"opipsram"
],
"macro_namespace_exceptions": [
"SOC_",
"RIB_",
"RIBP_",
"AXI4_",
"APB_"
]
}
}
+4 -3
View File
@@ -34,9 +34,10 @@ limitations.
`check_rtl_style.py` applies the ownership-aware RTL style rules from
[`../rtl/rtl_style_manifest.json`](../rtl/rtl_style_manifest.json). The CI
target checks changed self-owned RTL for positional module connections and
legacy constructs. Existing findings are migrated in module-sized batches and
must not be expanded by a new change.
target checks changed self-owned RTL for positional module connections, legacy
constructs, and the staged naming contract. `rtl-style-check-all` retains the
historical full-tree structural baseline while naming debt is migrated in
module-sized batches. Existing findings must not be expanded by a new change.
`migrate_rtl_connections.py` is the conservative migration helper for legacy
positional instances. It discovers ANSI-style module declarations in the
+101 -1
View File
@@ -37,6 +37,22 @@ LONG_LOCAL_RE = re.compile(
r"\b[sr]_[A-Za-z0-9_$]*(?:command|request|response|address|enable|error|status|"
r"counter|configuration|select|length)(?:_|$)"
)
MODULE_RE = re.compile(r"^\s*module\s+(?P<name>[A-Za-z_][A-Za-z0-9_$]*)\b")
PORT_RE = re.compile(
r"^\s*(?:input|output|inout)\b.*?\b(?P<name>[A-Za-z_][A-Za-z0-9_$]*)\s*(?:[,)]|$)"
)
INTERFACE_INSTANCE_RE = re.compile(
r"^\s*(?:[A-Za-z_][A-Za-z0-9_$]*_if|axi4_if|axi4_stream_if)\s+"
r"(?P<name>[A-Za-z_][A-Za-z0-9_$]*)\s*\("
)
MACRO_RE = re.compile(r"^\s*`define\s+(?P<name>[A-Za-z_][A-Za-z0-9_$]*)\b")
ENUM_TYPE_RE = re.compile(r"typedef\s+enum\b[\s\S]*?}\s*(?P<name>[A-Za-z_][A-Za-z0-9_$]*)\s*;")
PARAM_RE = re.compile(
r"\b(?:localparam|parameter)\b(?:\s+(?:bit|logic|int|integer|unsigned|signed)"
r"|\s*\[[^\]]+\])*\s+(?P<name>[A-Za-z_][A-Za-z0-9_$]*)\s*(?:=|,|$)"
)
UPPER_CAMEL_RE = re.compile(r"^[A-Z][A-Za-z0-9]*$")
LOWER_SNAKE_RE = re.compile(r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$")
def tracked_files(root: Path) -> list[Path]:
@@ -49,7 +65,7 @@ def tracked_files(root: Path) -> list[Path]:
def changed_files(root: Path) -> set[Path]:
"""Return tracked RTL files changed in the worktree or current commit."""
"""Return RTL files changed in the worktree, including untracked files."""
names = subprocess.run(
["git", "-C", str(root), "diff", "--name-only", "--diff-filter=ACMR"],
check=True,
@@ -62,6 +78,12 @@ def changed_files(root: Path) -> set[Path]:
text=True,
stdout=subprocess.PIPE,
).stdout.splitlines()
names += subprocess.run(
["git", "-C", str(root), "ls-files", "--others", "--exclude-standard"],
check=True,
text=True,
stdout=subprocess.PIPE,
).stdout.splitlines()
return {Path(name) for name in names}
@@ -85,6 +107,23 @@ def selected_files(root: Path, manifest: dict[str, object]) -> dict[str, list[Pa
return files
def add_changed_profile_files(
root: Path, profile_files: list[Path], profile: dict[str, object], changed: set[Path]
) -> list[Path]:
"""Include new, untracked owned sources in changed-only checks."""
roots = profile.get("roots", [])
suffixes = set(profile.get("suffixes", []))
root_paths = [Path(item) for item in roots]
candidates = {
path
for path in changed
if path.suffix in suffixes
and any(path == candidate or candidate in path.parents for candidate in root_paths)
and (root / path).is_file()
}
return sorted(set(profile_files) | candidates)
def instance_issues(path: Path, source: str) -> list[str]:
lines = source.splitlines()
issues: list[str] = []
@@ -144,6 +183,53 @@ def check_file(path: Path, source: str, profile: str) -> list[str]:
return issues
def naming_issues(path: Path, source: str, manifest: dict[str, object]) -> list[str]:
"""Check the staged naming contract without touching protocol ABI fields."""
naming = manifest.get("naming", {})
if not isinstance(naming, dict):
return []
issues: list[str] = []
lines = source.splitlines()
for number, line in enumerate(lines, start=1):
code = line.split("//", 1)[0]
module_match = MODULE_RE.match(code)
if module_match and not LOWER_SNAKE_RE.fullmatch(module_match.group("name")):
issues.append(f"{path}:{number}: module name must use lower_snake_case")
port_match = PORT_RE.match(code)
if port_match:
name = port_match.group("name")
if not name.endswith(("_i", "_o", "_io")):
issues.append(f"{path}:{number}: port '{name}' needs _i, _o, or _io suffix")
interface_match = INTERFACE_INSTANCE_RE.match(code)
if interface_match and not interface_match.group("name").startswith("u_"):
issues.append(f"{path}:{number}: interface instance must start with u_")
macro_match = MACRO_RE.match(code)
if macro_match:
name = macro_match.group("name")
exceptions = naming.get("macro_namespace_exceptions", [])
if not isinstance(exceptions, list):
exceptions = []
if not name.startswith("RETROSOC_") and not any(
name.startswith(str(prefix)) for prefix in exceptions
):
issues.append(f"{path}:{number}: macro '{name}' needs a reviewed namespace")
if re.search(r"\brst_ni\b", code):
issues.append(f"{path}:{number}: use project reset spelling rst_n_i")
for match in ENUM_TYPE_RE.finditer(source):
if not match.group("name").endswith("_e"):
line = source.count("\n", 0, match.start()) + 1
issues.append(f"{path}:{line}: enum typedef must end in _e")
for number, line in enumerate(lines, start=1):
code = line.split("//", 1)[0]
if re.search(r"\b(?:parameter|localparam)\b", code):
for match in PARAM_RE.finditer(code):
if not UPPER_CAMEL_RE.fullmatch(match.group("name")):
issues.append(
f"{path}:{number}: parameter '{match.group('name')}' must use UpperCamelCase"
)
return issues
def verible_issues(tool: str, paths: list[Path], root: Path) -> list[str]:
if not paths:
return []
@@ -177,6 +263,11 @@ def main() -> int:
action="store_true",
help="check only files changed in the worktree or current commit",
)
parser.add_argument(
"--enforce-naming",
action="store_true",
help="enforce the staged naming rules for the selected files",
)
args = parser.parse_args()
root = args.root.resolve()
manifest_path = args.manifest or root / "rtl/rtl_style_manifest.json"
@@ -184,12 +275,21 @@ def main() -> int:
profile_files = selected_files(root, manifest)[args.profile]
if args.changed_only:
changed = changed_files(root)
raw_profile = manifest["profiles"][args.profile]
assert isinstance(raw_profile, dict)
profile_files = add_changed_profile_files(root, profile_files, raw_profile, changed)
profile_files = [path for path in profile_files if path in changed]
issues = [
issue
for path in profile_files
for issue in check_file(path, (root / path).read_text(encoding="utf-8"), args.profile)
]
if args.profile == "owned" and (args.changed_only or args.enforce_naming):
issues.extend(
naming_issues(path, (root / path).read_text(encoding="utf-8"), manifest)
for path in profile_files
)
issues = [issue for group in issues for issue in (group if isinstance(group, list) else [group])]
if args.profile == "owned":
issues.extend(verible_issues(args.verible_verilog_lint, profile_files, root))
baseline: set[str] = set()
+2
View File
@@ -8,6 +8,8 @@ tooling.
archive, and regression-helper behavior.
- `test_rtl_readiness.py` covers the machine-readable RTL maturity and
synthesis-intent checks.
- `test_rtl_style.py` covers ownership, named connections, and staged naming
rules for new owned RTL.
Add a host C test when changing deterministic runtime or media logic; add a
Python test when changing scripts or build policy. Run:
+44 -2
View File
@@ -10,7 +10,9 @@ ROOT = Path(__file__).resolve().parents[1]
SCRIPT = ROOT / "scripts/check_rtl_style.py"
def run_style(tmp_path: Path, source: str) -> subprocess.CompletedProcess[str]:
def run_style(
tmp_path: Path, source: str, *, enforce_naming: bool = False
) -> subprocess.CompletedProcess[str]:
(tmp_path / "rtl/ip/test.sv").parent.mkdir(parents=True)
(tmp_path / "rtl/ip/test.sv").write_text(source, encoding="utf-8")
(tmp_path / "rtl/rtl_style_manifest.json").write_text(
@@ -31,8 +33,11 @@ def run_style(tmp_path: Path, source: str) -> subprocess.CompletedProcess[str]:
)
subprocess.run(["git", "init", "-q", str(tmp_path)], check=True)
subprocess.run(["git", "-C", str(tmp_path), "add", "."], check=True)
command = [sys.executable, str(SCRIPT), "--root", str(tmp_path), "--profile", "owned"]
if enforce_naming:
command.append("--enforce-naming")
return subprocess.run(
[sys.executable, str(SCRIPT), "--root", str(tmp_path), "--profile", "owned"],
command,
text=True,
capture_output=True,
check=False,
@@ -88,3 +93,40 @@ def test_owned_style_rejects_non_ascii_and_long_lines(tmp_path: Path) -> None:
assert result.returncode == 1
assert "non-ASCII" in result.stderr
assert "100 characters" in result.stderr
def test_owned_style_enforces_staged_naming_contract(tmp_path: Path) -> None:
result = run_style(
tmp_path,
"`define BAD_MACRO 1\n"
"module badModule(\n"
" input logic clk,\n"
" output logic data_o\n"
");\n"
" logic s_state_q;\n"
" bad_if if0 ();\n"
"endmodule\n",
enforce_naming=True,
)
assert result.returncode == 1
assert "module name" in result.stderr
assert "port 'clk'" in result.stderr
assert "interface instance" in result.stderr
assert "macro 'BAD_MACRO'" in result.stderr
def test_owned_style_accepts_project_naming_contract(tmp_path: Path) -> None:
result = run_style(
tmp_path,
"`define RETROSOC_TEST__VALUE 1\n"
"module test_module(\n"
" input logic clk_i,\n"
" output logic data_o\n"
");\n"
" logic s_state_d;\n"
" logic s_state_q;\n"
" test_if u_test_if ();\n"
"endmodule\n",
enforce_naming=True,
)
assert result.returncode == 0, result.stderr
+1 -1
View File
@@ -54,7 +54,7 @@ def test_extensions_generate_isolated_scalar_bindings(tmp_path: Path) -> None:
assert core.count("ribp2rib #(") == 6
assert core.count(".SYNC_RESET(1'b1)") == 3
assert core.count(".SYNC_RESET(1'b0)") == 3
assert "rib.cmd_valid = '0;" in core
assert "u_user_rib_if.cmd_valid = '0;" in core
assert "5'd0: begin" in core
assert "5'd4: begin" in core
assert "5'd5: begin" in core