mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
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:
18 files changed
+436
-80
No files matched your search
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user