mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
feat: enhance SoC topology and user extensions with APB targets and validation scripts
- Added APB target definitions in soc_topology.json for various peripherals. - Introduced user_extensions.json to define user core and IP targets. - Updated generate_filelist.py to include SVA paths. - Refactored ip_apb_wrapper.sv to utilize generated APB interfaces and bridges. - Simplified user_core_top.sv and user_ip_top.sv by including generated bindings. - Implemented check_clock_reset_domains.py to validate clock/reset domains and CDC crossings. - Updated regress.py to include SVA support in simulation commands. - Added tests for clock/reset domain validation and user extensions generation. - Enhanced test coverage for topology generation and APB target ownership validation.
This commit is contained in:
22 files changed
+1118
-561
No files matched your search
@@ -45,7 +45,7 @@ jobs:
|
||||
run: python3 scripts/dependency_lock.py --lock config/dependencies.lock.json
|
||||
|
||||
- name: Validate SoC integration topology
|
||||
run: make check-soc-topology
|
||||
run: make check-soc-topology check-user-extensions check-clock-reset-domains
|
||||
|
||||
- name: Lint Python
|
||||
run: ruff check .
|
||||
|
||||
@@ -167,7 +167,8 @@ endif
|
||||
clean-all purge-cache manifest check-warnings metrics check-metrics package \
|
||||
regress-pr regress-nightly sim-asm format format-check sw-format sw-format-check mk-format \
|
||||
mk-format-check rtl-format rtl-format-check sw-policy-check sw-host-test \
|
||||
pin-map check-pin-map soc-topology check-soc-topology
|
||||
pin-map check-pin-map soc-topology check-soc-topology user-extensions check-user-extensions \
|
||||
check-clock-reset-domains
|
||||
.NOTPARALLEL: setup
|
||||
|
||||
help:
|
||||
@@ -188,6 +189,9 @@ help:
|
||||
' check-pin-map validate the canonical SoC pin map' \
|
||||
' soc-topology generate the selected internal SoC integration artifacts' \
|
||||
' check-soc-topology validate the canonical internal SoC integration map' \
|
||||
' user-extensions generate the selected scalar user-extension bindings' \
|
||||
' check-user-extensions validate the canonical user-extension map' \
|
||||
' check-clock-reset-domains validate the root clock/reset and CDC inventory' \
|
||||
' check-warnings | metrics analyze flow logs and reports' \
|
||||
' check-metrics apply the committed metrics policy' \
|
||||
' format format self-owned C, Makefile, and RTL sources' \
|
||||
|
||||
@@ -15,18 +15,7 @@ module nmi2apb (
|
||||
input logic clk_i,
|
||||
input logic rst_n_i,
|
||||
nmi_if.slave nmi,
|
||||
`ifdef IP_MDD
|
||||
apb4_pure_if.master user_ip,
|
||||
`endif
|
||||
apb4_pure_if.master archinfo,
|
||||
apb4_pure_if.master rng,
|
||||
apb4_pure_if.master uart,
|
||||
apb4_pure_if.master pwm,
|
||||
apb4_pure_if.master ps2,
|
||||
apb4_pure_if.master rtc,
|
||||
apb4_pure_if.master wdg,
|
||||
apb4_pure_if.master crc,
|
||||
apb4_pure_if.master tmr
|
||||
`include "soc_apb_ports.svh"
|
||||
// verilog_format: on
|
||||
);
|
||||
|
||||
@@ -35,109 +24,25 @@ module nmi2apb (
|
||||
localparam FSM_ENAB = 2'd2;
|
||||
|
||||
logic [31:0] s_rd_data;
|
||||
logic [31:0] s_rd_data_base;
|
||||
logic s_xfer_valid, s_xfer_ready;
|
||||
logic s_xfer_ready_base;
|
||||
logic s_mem_valid_re;
|
||||
logic [1:0] s_fsm_d, s_fsm_q;
|
||||
|
||||
// Registered slave-select one-hot for response mux
|
||||
`ifdef IP_MDD
|
||||
localparam NSLV = 10;
|
||||
`else
|
||||
localparam NSLV = 9;
|
||||
`endif
|
||||
logic [NSLV-1:0] s_psel_comb, s_psel_d, s_psel_q;
|
||||
`include "soc_apb_declarations.svh"
|
||||
|
||||
|
||||
// Generated APB request routing retains the scalar interface protocol.
|
||||
`include "soc_apb_request_routes.svh"
|
||||
|
||||
// verilog_format: off
|
||||
assign archinfo.paddr = nmi.addr;
|
||||
assign rng.paddr = nmi.addr;
|
||||
assign uart.paddr = nmi.addr;
|
||||
assign pwm.paddr = nmi.addr;
|
||||
assign ps2.paddr = nmi.addr;
|
||||
assign rtc.paddr = nmi.addr;
|
||||
assign wdg.paddr = nmi.addr;
|
||||
assign crc.paddr = nmi.addr;
|
||||
assign tmr.paddr = nmi.addr;
|
||||
|
||||
assign archinfo.pprot = '0;
|
||||
assign rng.pprot = '0;
|
||||
assign uart.pprot = '0;
|
||||
assign pwm.pprot = '0;
|
||||
assign ps2.pprot = '0;
|
||||
assign rtc.pprot = '0;
|
||||
assign wdg.pprot = '0;
|
||||
assign crc.pprot = '0;
|
||||
assign tmr.pprot = '0;
|
||||
|
||||
assign archinfo.psel = s_xfer_valid && `SOC_ADDR_IS_APB_ARCHINFO(nmi.addr);
|
||||
assign rng.psel = s_xfer_valid && `SOC_ADDR_IS_APB_RNG(nmi.addr);
|
||||
assign uart.psel = s_xfer_valid && `SOC_ADDR_IS_APB_UART1(nmi.addr);
|
||||
assign pwm.psel = s_xfer_valid && `SOC_ADDR_IS_APB_PWM(nmi.addr);
|
||||
assign ps2.psel = s_xfer_valid && `SOC_ADDR_IS_APB_PS2(nmi.addr);
|
||||
assign rtc.psel = s_xfer_valid && `SOC_ADDR_IS_APB_RTC(nmi.addr);
|
||||
assign wdg.psel = s_xfer_valid && `SOC_ADDR_IS_APB_WDG(nmi.addr);
|
||||
assign crc.psel = s_xfer_valid && `SOC_ADDR_IS_APB_CRC(nmi.addr);
|
||||
assign tmr.psel = s_xfer_valid && `SOC_ADDR_IS_APB_TMR(nmi.addr);
|
||||
|
||||
assign archinfo.penable = s_fsm_q == FSM_ENAB;
|
||||
assign rng.penable = s_fsm_q == FSM_ENAB;
|
||||
assign uart.penable = s_fsm_q == FSM_ENAB;
|
||||
assign pwm.penable = s_fsm_q == FSM_ENAB;
|
||||
assign ps2.penable = s_fsm_q == FSM_ENAB;
|
||||
assign rtc.penable = s_fsm_q == FSM_ENAB;
|
||||
assign wdg.penable = s_fsm_q == FSM_ENAB;
|
||||
assign crc.penable = s_fsm_q == FSM_ENAB;
|
||||
assign tmr.penable = s_fsm_q == FSM_ENAB;
|
||||
|
||||
assign archinfo.pwrite = |nmi.wstrb;
|
||||
assign rng.pwrite = |nmi.wstrb;
|
||||
assign uart.pwrite = |nmi.wstrb;
|
||||
assign pwm.pwrite = |nmi.wstrb;
|
||||
assign ps2.pwrite = |nmi.wstrb;
|
||||
assign rtc.pwrite = |nmi.wstrb;
|
||||
assign wdg.pwrite = |nmi.wstrb;
|
||||
assign crc.pwrite = |nmi.wstrb;
|
||||
assign tmr.pwrite = |nmi.wstrb;
|
||||
|
||||
assign archinfo.pwdata = nmi.wdata;
|
||||
assign rng.pwdata = nmi.wdata;
|
||||
assign uart.pwdata = nmi.wdata;
|
||||
assign pwm.pwdata = nmi.wdata;
|
||||
assign ps2.pwdata = nmi.wdata;
|
||||
assign rtc.pwdata = nmi.wdata;
|
||||
assign wdg.pwdata = nmi.wdata;
|
||||
assign crc.pwdata = nmi.wdata;
|
||||
assign tmr.pwdata = nmi.wdata;
|
||||
|
||||
assign archinfo.pstrb = nmi.wstrb;
|
||||
assign rng.pstrb = nmi.wstrb;
|
||||
assign uart.pstrb = nmi.wstrb;
|
||||
assign pwm.pstrb = nmi.wstrb;
|
||||
assign ps2.pstrb = nmi.wstrb;
|
||||
assign rtc.pstrb = nmi.wstrb;
|
||||
assign wdg.pstrb = nmi.wstrb;
|
||||
assign crc.pstrb = nmi.wstrb;
|
||||
assign tmr.pstrb = nmi.wstrb;
|
||||
|
||||
`ifdef IP_MDD
|
||||
assign user_ip.paddr = nmi.addr;
|
||||
assign user_ip.pprot = '0;
|
||||
assign user_ip.psel = s_xfer_valid && `SOC_ADDR_IS_APB_USER_IP(nmi.addr);
|
||||
assign user_ip.penable = s_fsm_q == FSM_ENAB;
|
||||
assign user_ip.pwrite = |nmi.wstrb;
|
||||
assign user_ip.pwdata = nmi.wdata;
|
||||
assign user_ip.pstrb = nmi.wstrb;
|
||||
`endif
|
||||
// verilog_format: on
|
||||
|
||||
edge_det_sync_re #(1) u_mem_valid_edge_det_sync_re (
|
||||
clk_i,
|
||||
rst_n_i,
|
||||
nmi.valid,
|
||||
s_mem_valid_re
|
||||
);
|
||||
// verilog_format: on
|
||||
|
||||
assign s_xfer_valid = ((s_fsm_q == FSM_IDLE) && s_mem_valid_re) ||
|
||||
(s_fsm_q == FSM_SETP) || (s_fsm_q == FSM_ENAB);
|
||||
@@ -158,26 +63,13 @@ module nmi2apb (
|
||||
s_fsm_q
|
||||
);
|
||||
|
||||
assign nmi.ready = nmi.valid && (s_fsm_q == FSM_ENAB) && s_xfer_ready;
|
||||
assign nmi.rdata = {32{nmi.ready}} & s_rd_data;
|
||||
assign nmi.ready = nmi.valid && (s_fsm_q == FSM_ENAB) && s_xfer_ready;
|
||||
assign nmi.rdata = {32{nmi.ready}} & s_rd_data;
|
||||
|
||||
// Capture slave-select one-hot at FSM_SETP for cleaner response mux
|
||||
// verilog_format: off
|
||||
assign s_psel_comb[0] = `SOC_ADDR_IS_APB_ARCHINFO(nmi.addr);
|
||||
assign s_psel_comb[1] = `SOC_ADDR_IS_APB_RNG(nmi.addr);
|
||||
assign s_psel_comb[2] = `SOC_ADDR_IS_APB_UART1(nmi.addr);
|
||||
assign s_psel_comb[3] = `SOC_ADDR_IS_APB_PWM(nmi.addr);
|
||||
assign s_psel_comb[4] = `SOC_ADDR_IS_APB_PS2(nmi.addr);
|
||||
assign s_psel_comb[5] = `SOC_ADDR_IS_APB_RTC(nmi.addr);
|
||||
assign s_psel_comb[6] = `SOC_ADDR_IS_APB_WDG(nmi.addr);
|
||||
assign s_psel_comb[7] = `SOC_ADDR_IS_APB_CRC(nmi.addr);
|
||||
assign s_psel_comb[8] = `SOC_ADDR_IS_APB_TMR(nmi.addr);
|
||||
`ifdef IP_MDD
|
||||
assign s_psel_comb[9] = `SOC_ADDR_IS_APB_USER_IP(nmi.addr);
|
||||
`endif
|
||||
// Capture slave-select one-hot at FSM_SETP for cleaner response mux.
|
||||
`include "soc_apb_select_routes.svh"
|
||||
|
||||
assign s_psel_d = (s_fsm_q == FSM_IDLE && s_mem_valid_re) ? s_psel_comb : s_psel_q;
|
||||
// verilog_format: on
|
||||
dffr #(NSLV) u_psel_dffr (
|
||||
clk_i,
|
||||
rst_n_i,
|
||||
@@ -185,33 +77,7 @@ module nmi2apb (
|
||||
s_psel_q
|
||||
);
|
||||
|
||||
// Response mux using registered slave-select (cleaner timing)
|
||||
assign s_rd_data_base = ({32{s_psel_q[0]}} & archinfo.prdata) |
|
||||
({32{s_psel_q[1]}} & rng.prdata) |
|
||||
({32{s_psel_q[2]}} & uart.prdata) |
|
||||
({32{s_psel_q[3]}} & pwm.prdata) |
|
||||
({32{s_psel_q[4]}} & ps2.prdata) |
|
||||
({32{s_psel_q[5]}} & rtc.prdata) |
|
||||
({32{s_psel_q[6]}} & wdg.prdata) |
|
||||
({32{s_psel_q[7]}} & crc.prdata) |
|
||||
({32{s_psel_q[8]}} & tmr.prdata);
|
||||
|
||||
assign s_xfer_ready_base = (s_psel_q[0] & archinfo.pready) |
|
||||
(s_psel_q[1] & rng.pready) |
|
||||
(s_psel_q[2] & uart.pready) |
|
||||
(s_psel_q[3] & pwm.pready) |
|
||||
(s_psel_q[4] & ps2.pready) |
|
||||
(s_psel_q[5] & rtc.pready) |
|
||||
(s_psel_q[6] & wdg.pready) |
|
||||
(s_psel_q[7] & crc.pready) |
|
||||
(s_psel_q[8] & tmr.pready);
|
||||
|
||||
`ifdef IP_MDD
|
||||
assign s_rd_data = s_rd_data_base | ({32{s_psel_q[9]}} & user_ip.prdata);
|
||||
assign s_xfer_ready = s_xfer_ready_base | (s_psel_q[9] & user_ip.pready);
|
||||
`else
|
||||
assign s_rd_data = s_rd_data_base;
|
||||
assign s_xfer_ready = s_xfer_ready_base;
|
||||
`endif
|
||||
// Generated response mux uses the registered one-hot target selection.
|
||||
`include "soc_apb_response_mux.svh"
|
||||
|
||||
endmodule
|
||||
+54
-29
@@ -1,27 +1,34 @@
|
||||
GENERATED_FL_DIR := $(FLOW_FILELIST_DIR)
|
||||
MEMORY_MAP_SOURCE := $(RTL_PATH)/address_map/memory_map.json
|
||||
MEMORY_MAP_SCRIPT := $(RTL_PATH)/address_map/generate_memory_map.py
|
||||
MEMORY_MAP_DIR := $(VARIANT_ROOT)/generated/memory_map
|
||||
MEMORY_MAP_STAMP := $(MEMORY_MAP_DIR)/.stamp
|
||||
MEMORY_MAP_RTL_DIR := $(MEMORY_MAP_DIR)/rtl
|
||||
MEMORY_MAP_C_DIR := $(MEMORY_MAP_DIR)/include
|
||||
MEMORY_MAP_LINKER_DIR := $(MEMORY_MAP_DIR)/linker
|
||||
MEMORY_MAP_FILELIST := $(MEMORY_MAP_DIR)/memory_map.fl
|
||||
PIN_MAP_SOURCE := $(RTL_PATH)/pin_map/pin_map.json
|
||||
PIN_MAP_SCRIPT := $(RTL_PATH)/pin_map/generate_pin_map.py
|
||||
PIN_MAP_DIR := $(VARIANT_ROOT)/generated/pin_map
|
||||
PIN_MAP_STAMP := $(PIN_MAP_DIR)/.stamp
|
||||
PIN_MAP_FILELIST := $(PIN_MAP_DIR)/pin_map.fl
|
||||
SOC_TOPOLOGY_SOURCE := $(RTL_PATH)/integration/soc_topology.json
|
||||
SOC_TOPOLOGY_SCRIPT := $(RTL_PATH)/integration/generate_soc_topology.py
|
||||
SOC_TOPOLOGY_DIR := $(VARIANT_ROOT)/generated/soc_topology
|
||||
SOC_TOPOLOGY_STAMP := $(SOC_TOPOLOGY_DIR)/.stamp
|
||||
SOC_TOPOLOGY_FILELIST := $(SOC_TOPOLOGY_DIR)/soc_topology.fl
|
||||
MPW_STAMP := $(CACHE_ROOT)/stamps/mpw-$(LOCK_DIGEST).prepared
|
||||
MPW_OUTPUT_DIR := $(VARIANT_ROOT)/generated/mpw/$(SIM_TOOL_NAME)
|
||||
MPW_VARIANT_STAMP := $(MPW_OUTPUT_DIR)/.stamp
|
||||
FILELIST_STAMP := $(GENERATED_FL_DIR)/.stamp
|
||||
FILELIST_TEMPLATES := $(sort $(wildcard $(ROOT_PATH)/rtl/filelist/pdk_*.fl) \
|
||||
GENERATED_FL_DIR := $(FLOW_FILELIST_DIR)
|
||||
MEMORY_MAP_SOURCE := $(RTL_PATH)/address_map/memory_map.json
|
||||
MEMORY_MAP_SCRIPT := $(RTL_PATH)/address_map/generate_memory_map.py
|
||||
MEMORY_MAP_DIR := $(VARIANT_ROOT)/generated/memory_map
|
||||
MEMORY_MAP_STAMP := $(MEMORY_MAP_DIR)/.stamp
|
||||
MEMORY_MAP_RTL_DIR := $(MEMORY_MAP_DIR)/rtl
|
||||
MEMORY_MAP_C_DIR := $(MEMORY_MAP_DIR)/include
|
||||
MEMORY_MAP_LINKER_DIR := $(MEMORY_MAP_DIR)/linker
|
||||
MEMORY_MAP_FILELIST := $(MEMORY_MAP_DIR)/memory_map.fl
|
||||
PIN_MAP_SOURCE := $(RTL_PATH)/pin_map/pin_map.json
|
||||
PIN_MAP_SCRIPT := $(RTL_PATH)/pin_map/generate_pin_map.py
|
||||
PIN_MAP_DIR := $(VARIANT_ROOT)/generated/pin_map
|
||||
PIN_MAP_STAMP := $(PIN_MAP_DIR)/.stamp
|
||||
PIN_MAP_FILELIST := $(PIN_MAP_DIR)/pin_map.fl
|
||||
SOC_TOPOLOGY_SOURCE := $(RTL_PATH)/integration/soc_topology.json
|
||||
SOC_TOPOLOGY_SCRIPT := $(RTL_PATH)/integration/generate_soc_topology.py
|
||||
SOC_TOPOLOGY_DIR := $(VARIANT_ROOT)/generated/soc_topology
|
||||
SOC_TOPOLOGY_STAMP := $(SOC_TOPOLOGY_DIR)/.stamp
|
||||
SOC_TOPOLOGY_FILELIST := $(SOC_TOPOLOGY_DIR)/soc_topology.fl
|
||||
USER_EXTENSIONS_SOURCE := $(RTL_PATH)/integration/user_extensions.json
|
||||
USER_EXTENSIONS_SCRIPT := $(RTL_PATH)/integration/generate_user_extensions.py
|
||||
USER_EXTENSIONS_DIR := $(VARIANT_ROOT)/generated/user_extensions
|
||||
USER_EXTENSIONS_STAMP := $(USER_EXTENSIONS_DIR)/.stamp
|
||||
USER_EXTENSIONS_FILELIST := $(USER_EXTENSIONS_DIR)/user_extensions.fl
|
||||
CLOCK_RESET_DOMAINS_SOURCE := $(RTL_PATH)/integration/clock_reset_domains.json
|
||||
CLOCK_RESET_DOMAINS_SCRIPT := $(ROOT_PATH)/scripts/check_clock_reset_domains.py
|
||||
MPW_STAMP := $(CACHE_ROOT)/stamps/mpw-$(LOCK_DIGEST).prepared
|
||||
MPW_OUTPUT_DIR := $(VARIANT_ROOT)/generated/mpw/$(SIM_TOOL_NAME)
|
||||
MPW_VARIANT_STAMP := $(MPW_OUTPUT_DIR)/.stamp
|
||||
FILELIST_STAMP := $(GENERATED_FL_DIR)/.stamp
|
||||
FILELIST_TEMPLATES := $(sort $(wildcard $(ROOT_PATH)/rtl/filelist/pdk_*.fl) \
|
||||
$(wildcard $(RTL_PATH)/filelist/*.fl) $(RTL_PATH)/lint.msg)
|
||||
|
||||
$(MPW_STAMP):
|
||||
@@ -64,17 +71,33 @@ check-pin-map:
|
||||
|
||||
$(SOC_TOPOLOGY_STAMP): $(SOC_TOPOLOGY_SOURCE) $(SOC_TOPOLOGY_SCRIPT) $(MEMORY_MAP_SOURCE)
|
||||
python3 $(SOC_TOPOLOGY_SCRIPT) --map $(SOC_TOPOLOGY_SOURCE) \
|
||||
--memory-map $(MEMORY_MAP_SOURCE) --output-dir $(SOC_TOPOLOGY_DIR)
|
||||
--memory-map $(MEMORY_MAP_SOURCE) --ip $(IP) --output-dir $(SOC_TOPOLOGY_DIR)
|
||||
@touch $@
|
||||
|
||||
soc-topology: $(SOC_TOPOLOGY_STAMP)
|
||||
|
||||
check-soc-topology:
|
||||
python3 $(SOC_TOPOLOGY_SCRIPT) --map $(SOC_TOPOLOGY_SOURCE) \
|
||||
--memory-map $(MEMORY_MAP_SOURCE) --check
|
||||
--memory-map $(MEMORY_MAP_SOURCE) --ip NONE --check
|
||||
python3 $(SOC_TOPOLOGY_SCRIPT) --map $(SOC_TOPOLOGY_SOURCE) \
|
||||
--memory-map $(MEMORY_MAP_SOURCE) --ip MDD --check
|
||||
|
||||
$(USER_EXTENSIONS_STAMP): $(USER_EXTENSIONS_SOURCE) $(USER_EXTENSIONS_SCRIPT)
|
||||
python3 $(USER_EXTENSIONS_SCRIPT) --map $(USER_EXTENSIONS_SOURCE) \
|
||||
--output-dir $(USER_EXTENSIONS_DIR)
|
||||
@touch $@
|
||||
|
||||
user-extensions: $(USER_EXTENSIONS_STAMP)
|
||||
|
||||
check-user-extensions:
|
||||
python3 $(USER_EXTENSIONS_SCRIPT) --map $(USER_EXTENSIONS_SOURCE) --check
|
||||
|
||||
check-clock-reset-domains:
|
||||
python3 $(CLOCK_RESET_DOMAINS_SCRIPT) --map $(CLOCK_RESET_DOMAINS_SOURCE) --root $(ROOT_PATH)
|
||||
|
||||
$(FILELIST_STAMP): $(FILELIST_TEMPLATES) $(RTL_PATH)/script/generate_filelist.py \
|
||||
$(RTL_PATH)/script/filelist.py $(MEMORY_MAP_STAMP) $(PIN_MAP_STAMP) $(SOC_TOPOLOGY_STAMP)
|
||||
$(RTL_PATH)/script/filelist.py $(MEMORY_MAP_STAMP) $(PIN_MAP_STAMP) $(SOC_TOPOLOGY_STAMP) \
|
||||
$(USER_EXTENSIONS_STAMP)
|
||||
python3 $(RTL_PATH)/script/generate_filelist.py \
|
||||
--output-dir $(GENERATED_FL_DIR) \
|
||||
$(foreach define,$(DEF_LIST),--define $(define))
|
||||
@@ -101,7 +124,8 @@ NET_FLIST += -f $(GENERATED_FL_DIR)/sys_def.fl
|
||||
NET_FLIST += -f $(GENERATED_FL_DIR)/commonip.fl
|
||||
NET_FLIST += -f $(GENERATED_FL_DIR)/inc.fl
|
||||
|
||||
RTL_FLIST := -f $(MEMORY_MAP_FILELIST) -f $(PIN_MAP_FILELIST) -f $(SOC_TOPOLOGY_FILELIST) $(NET_FLIST)
|
||||
RTL_FLIST := -f $(MEMORY_MAP_FILELIST) -f $(PIN_MAP_FILELIST) -f $(SOC_TOPOLOGY_FILELIST) \
|
||||
-f $(USER_EXTENSIONS_FILELIST) $(NET_FLIST)
|
||||
RTL_FLIST += -f $(GENERATED_FL_DIR)/clusterip.fl
|
||||
RTL_FLIST += -f $(GENERATED_FL_DIR)/ip.fl
|
||||
RTL_FLIST += -f $(GENERATED_FL_DIR)/tech.fl
|
||||
@@ -130,4 +154,5 @@ else ifeq ($(SIMU), IVERILOG)
|
||||
endif
|
||||
|
||||
.PHONY: gen_mpw_code generate_filelist memory-map check-memory-map pin-map check-pin-map \
|
||||
soc-topology check-soc-topology firmware
|
||||
soc-topology check-soc-topology user-extensions check-user-extensions \
|
||||
check-clock-reset-domains firmware
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
|
||||
// retroSoC is licensed under Mulan PSL v2.
|
||||
// You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
// You may obtain a copy of Mulan PSL v2 at:
|
||||
// http://license.coscl.org.cn/MulanPSL2
|
||||
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// See the Mulan PSL v2 for more details.
|
||||
|
||||
`ifndef SV_ASSRT_DISABLE
|
||||
`ifndef SYNTHESIS
|
||||
|
||||
module soc_bus_sva (
|
||||
input logic clk_i,
|
||||
input logic rst_n_i,
|
||||
input logic fault_sel_i,
|
||||
input logic fault_valid_i,
|
||||
input logic arb_locked_i,
|
||||
input logic arb_dma_owner_i,
|
||||
input logic core_valid_i,
|
||||
input logic dma_valid_i
|
||||
);
|
||||
|
||||
// A decode miss must be reported and terminate the transaction locally.
|
||||
assert property (@(posedge clk_i) disable iff (!rst_n_i) fault_sel_i |-> fault_valid_i);
|
||||
|
||||
// Retain the documented DMA-over-core arbitration policy while idle.
|
||||
assert property (@(posedge clk_i) disable iff (!rst_n_i)
|
||||
!arb_locked_i && dma_valid_i |=> arb_locked_i && arb_dma_owner_i);
|
||||
|
||||
// A sole core request must acquire ownership rather than being dropped.
|
||||
assert property (@(posedge clk_i) disable iff (!rst_n_i)
|
||||
!arb_locked_i && core_valid_i && !dma_valid_i |=> arb_locked_i && !arb_dma_owner_i);
|
||||
|
||||
endmodule
|
||||
|
||||
bind bus soc_bus_sva u_soc_bus_sva (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i (rst_n_i),
|
||||
.fault_sel_i (s_fault_sel),
|
||||
.fault_valid_i (fault_valid_o),
|
||||
.arb_locked_i (s_mstr_lock_q),
|
||||
.arb_dma_owner_i(s_mstr_id_q),
|
||||
.core_valid_i (core_nmi.valid),
|
||||
.dma_valid_i (dma_nmi.valid)
|
||||
);
|
||||
|
||||
module soc_nmi2apb_sva #(
|
||||
parameter int NSLV = 1
|
||||
) (
|
||||
input logic clk_i,
|
||||
input logic rst_n_i,
|
||||
input logic [NSLV-1:0] psel_comb_i,
|
||||
input logic [NSLV-1:0] psel_q_i,
|
||||
input logic nmi_ready_i,
|
||||
input logic xfer_ready_i
|
||||
);
|
||||
|
||||
// Address regions are disjoint and only the registered select drives a response.
|
||||
assert property (@(posedge clk_i) disable iff (!rst_n_i) $onehot0(psel_comb_i));
|
||||
assert property (@(posedge clk_i) disable iff (!rst_n_i) $onehot0(psel_q_i));
|
||||
assert property (@(posedge clk_i) disable iff (!rst_n_i) nmi_ready_i |-> xfer_ready_i);
|
||||
|
||||
endmodule
|
||||
|
||||
bind nmi2apb soc_nmi2apb_sva #(
|
||||
.NSLV(NSLV)
|
||||
) u_soc_nmi2apb_sva (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i (rst_n_i),
|
||||
.psel_comb_i (s_psel_comb),
|
||||
.psel_q_i (s_psel_q),
|
||||
.nmi_ready_i (nmi.ready),
|
||||
.xfer_ready_i(s_xfer_ready)
|
||||
);
|
||||
|
||||
module soc_pll_rcu_sva (
|
||||
input logic clk_i,
|
||||
input logic rst_n_i,
|
||||
input logic [2:0] state_i,
|
||||
input logic pll_capable_i,
|
||||
input logic pll_apply_i,
|
||||
input logic select_ext_clk_i
|
||||
);
|
||||
|
||||
localparam logic [2:0] PLL_SAFE = 3'd1;
|
||||
localparam logic [2:0] PLL_APPLY = 3'd2;
|
||||
localparam logic [2:0] PLL_WAIT_LOCK = 3'd3;
|
||||
localparam logic [2:0] PLL_RESPOND = 3'd5;
|
||||
|
||||
// The output remains on the external clock until the PLL switch stage completes.
|
||||
assert property (@(posedge clk_i) disable iff (!rst_n_i)
|
||||
state_i == PLL_SAFE |-> select_ext_clk_i);
|
||||
assert property (@(posedge clk_i) disable iff (!rst_n_i)
|
||||
pll_apply_i |-> state_i inside {PLL_APPLY, PLL_WAIT_LOCK});
|
||||
assert property (@(posedge clk_i) disable iff (!rst_n_i)
|
||||
state_i == PLL_SAFE && !pll_capable_i |=> state_i == PLL_RESPOND);
|
||||
|
||||
endmodule
|
||||
|
||||
bind pll_rcu_controller soc_pll_rcu_sva u_soc_pll_rcu_sva (
|
||||
.clk_i (ext_clk_i),
|
||||
.rst_n_i (ext_rst_n_i),
|
||||
.state_i (s_state_q),
|
||||
.pll_capable_i (pll_capable_i),
|
||||
.pll_apply_i (pll_apply_o),
|
||||
.select_ext_clk_i(select_ext_clk_o)
|
||||
);
|
||||
|
||||
`ifdef IP_MDD
|
||||
module soc_gpio_user_handoff_sva #(
|
||||
parameter int DATA_WIDTH = 1
|
||||
) (
|
||||
input logic clk_i,
|
||||
input logic rst_n_i,
|
||||
input logic [DATA_WIDTH-1:0] user_sel_i,
|
||||
input logic [DATA_WIDTH-1:0] user_handoff_i,
|
||||
input logic [DATA_WIDTH-1:0] user_oe_i,
|
||||
input logic [DATA_WIDTH-1:0] user_do_i,
|
||||
input logic [DATA_WIDTH-1:0] native_oe_i,
|
||||
input logic [DATA_WIDTH-1:0] native_do_i,
|
||||
input logic [DATA_WIDTH-1:0] gpio_oe_i,
|
||||
input logic [DATA_WIDTH-1:0] gpio_do_i
|
||||
);
|
||||
|
||||
// A changing owner first drives output-enable low for a full handoff cycle.
|
||||
assert property (@(posedge clk_i) disable iff (!rst_n_i)
|
||||
gpio_oe_i == ((~user_handoff_i & user_sel_i & user_oe_i) |
|
||||
(~user_handoff_i & ~user_sel_i & native_oe_i)));
|
||||
assert property (@(posedge clk_i) disable iff (!rst_n_i)
|
||||
gpio_do_i == ((user_sel_i & user_do_i) | (~user_sel_i & native_do_i)));
|
||||
|
||||
endmodule
|
||||
|
||||
bind nmi_gpio soc_gpio_user_handoff_sva #(
|
||||
.DATA_WIDTH(`NMI_GPIO_NUM)
|
||||
) u_soc_gpio_user_handoff_sva (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i (rst_n_i),
|
||||
.user_sel_i (s_gpio_user_sel_q),
|
||||
.user_handoff_i(s_gpio_user_handoff_q),
|
||||
.user_oe_i (user_gpio.oe_o),
|
||||
.user_do_i (user_gpio.do_o),
|
||||
.native_oe_i (s_gpio_native_oe),
|
||||
.native_do_i (s_gpio_native_out),
|
||||
.gpio_oe_i (gpio.oe_o),
|
||||
.gpio_do_i (gpio.do_o)
|
||||
);
|
||||
`endif
|
||||
`endif
|
||||
|
||||
`endif
|
||||
@@ -15,3 +15,4 @@
|
||||
/top/user_ip_design_template.sv
|
||||
/top/user_ip_top.sv
|
||||
/top/user_ip_wrapper.sv
|
||||
/sva/soc_protocol_sva.sv
|
||||
@@ -3,9 +3,11 @@
|
||||
`soc_topology.json` is the source of truth for internal Mini SoC integration
|
||||
that is not part of the software address-map ABI or the package pad map.
|
||||
|
||||
The topology generator validates the native NMI target ownership against
|
||||
The topology generator validates native NMI and APB target ownership against
|
||||
`../address_map/memory_map.json` and emits generated SystemVerilog include
|
||||
files for `ip_nmi_wrapper.sv` and `retrosoc.sv`.
|
||||
files for `ip_nmi_wrapper.sv`, `ip_apb_wrapper.sv`, `nmi2apb.sv`, and
|
||||
`retrosoc.sv`. The output retains scalar interface instances and ports, so it
|
||||
does not depend on interface-array support in FPGA or netlist simulation tools.
|
||||
|
||||
The `native_targets` list uses stable response slots. Every active native
|
||||
memory-map region must appear exactly once. Disabled targets keep their slot
|
||||
@@ -16,12 +18,33 @@ Each `gpio_alt_functions` entry defines both alternate modes for one GPIO pin.
|
||||
peripheral output path. Values are restricted to scalar signal references and
|
||||
one-bit constants so malformed expressions are rejected before RTL generation.
|
||||
|
||||
The `apb_targets` list defines the stable APB response slot, timed/pure APB
|
||||
interface names, and corresponding active APB address-map region. A target that
|
||||
declares `requires_ip: "MDD"` is generated only for `IP=MDD`; its presence is
|
||||
checked against the same condition in the canonical memory map.
|
||||
|
||||
`user_extensions.json` separately defines the supported scalar user-core and
|
||||
user-IP slots. `generate_user_extensions.py` creates isolated default routing
|
||||
and one explicit instance per selected slot for `user_core_top.sv` and
|
||||
`user_ip_top.sv`. Adding an extension therefore requires a reviewed JSON entry
|
||||
rather than manual edits to the SoC muxes. The module port contract remains the
|
||||
existing user design template contract.
|
||||
|
||||
`clock_reset_domains.json` is the checked inventory of root clock domains,
|
||||
reset synchronizers, and RCU PLL control crossings. It records the primitive,
|
||||
source file, and instance name so an RTL refactor cannot silently remove the
|
||||
declared reset or CDC structure. It complements, but does not replace, a
|
||||
signoff CDC/RDC analysis tool.
|
||||
|
||||
Use these commands to validate or generate the selected build artifacts:
|
||||
|
||||
```sh
|
||||
make check-soc-topology
|
||||
make soc-topology
|
||||
make check-user-extensions
|
||||
make user-extensions
|
||||
make check-clock-reset-domains
|
||||
```
|
||||
|
||||
Generated files are written under `build/<variant>/generated/soc_topology/` and
|
||||
must not be committed.
|
||||
`build/<variant>/generated/user_extensions/`; they must not be committed.
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"domains": [
|
||||
{
|
||||
"name": "external",
|
||||
"clock": "s_ext_clk_buf",
|
||||
"reset": "s_ext_rst_n_sync",
|
||||
"reset_source": "ext_rst_n_i",
|
||||
"reset_primitive": "rst_sync",
|
||||
"path": "rtl/mini/top/rcu.sv",
|
||||
"instance": "u_ext_rst_sync"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"clock": "sys_clk_o",
|
||||
"reset": "s_sys_rst_n_sync",
|
||||
"reset_source": "ext_rst_n_i",
|
||||
"reset_primitive": "rst_sync",
|
||||
"path": "rtl/mini/top/rcu.sv",
|
||||
"instance": "u_sys_rst_sync"
|
||||
},
|
||||
{
|
||||
"name": "audio",
|
||||
"clock": "s_aud_clk_buf",
|
||||
"reset": "s_aud_rst_n_sync",
|
||||
"reset_source": "ext_rst_n_i",
|
||||
"reset_primitive": "rst_sync",
|
||||
"path": "rtl/mini/top/rcu.sv",
|
||||
"instance": "u_aud_rst_sync"
|
||||
}
|
||||
],
|
||||
"crossings": [
|
||||
{
|
||||
"name": "pll_request",
|
||||
"source": "system",
|
||||
"destination": "external",
|
||||
"primitive": "cdc_2phase",
|
||||
"path": "rtl/mini/top/rcu.sv",
|
||||
"instance": "u_pll_request_cdc"
|
||||
},
|
||||
{
|
||||
"name": "pll_response",
|
||||
"source": "external",
|
||||
"destination": "system",
|
||||
"primitive": "cdc_2phase",
|
||||
"path": "rtl/mini/top/rcu.sv",
|
||||
"instance": "u_pll_response_cdc"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -14,9 +14,7 @@ from typing import Any
|
||||
|
||||
|
||||
SV_IDENTIFIER_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*$")
|
||||
SV_REFERENCE_RE = re.compile(
|
||||
r"[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*(?:\[[0-9]+\])?$"
|
||||
)
|
||||
SV_REFERENCE_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*(?:\[[0-9]+\])?$")
|
||||
SV_CONSTANT_RE = re.compile(r"(?:1'[bB][01]|'[01])$")
|
||||
|
||||
|
||||
@@ -29,6 +27,16 @@ class NativeTarget:
|
||||
disabled: bool
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ApbTarget:
|
||||
slot: int
|
||||
name: str
|
||||
timed_interface: str
|
||||
pure_interface: str
|
||||
region: str
|
||||
requires_ip: str | None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GpioMode:
|
||||
inputs: tuple[str, ...]
|
||||
@@ -114,7 +122,17 @@ def parse_regions(value: Any, field: str) -> tuple[str, ...]:
|
||||
return regions
|
||||
|
||||
|
||||
def parse_native_targets(value: Any, memory_regions: dict[str, dict[str, Any]]) -> list[NativeTarget]:
|
||||
def parse_requires_ip(value: Any, field: str) -> str | None:
|
||||
if value is None:
|
||||
return None
|
||||
if value != "MDD":
|
||||
raise ValueError(f"{field} must be MDD when present")
|
||||
return value
|
||||
|
||||
|
||||
def parse_native_targets(
|
||||
value: Any, memory_regions: dict[str, dict[str, Any]]
|
||||
) -> list[NativeTarget]:
|
||||
if not isinstance(value, list) or not value:
|
||||
raise ValueError("native_targets must be a non-empty list")
|
||||
targets: list[NativeTarget] = []
|
||||
@@ -180,7 +198,92 @@ def parse_native_targets(value: Any, memory_regions: dict[str, dict[str, Any]])
|
||||
details.append(f"missing {', '.join(missing)}")
|
||||
if extra:
|
||||
details.append(f"extra {', '.join(extra)}")
|
||||
raise ValueError(f"native target regions do not cover the memory map ({'; '.join(details)})")
|
||||
raise ValueError(
|
||||
f"native target regions do not cover the memory map ({'; '.join(details)})"
|
||||
)
|
||||
return ordered
|
||||
|
||||
|
||||
def parse_apb_targets(
|
||||
value: Any, memory_regions: dict[str, dict[str, Any]], ip: str
|
||||
) -> list[ApbTarget]:
|
||||
if not isinstance(value, list) or not value:
|
||||
raise ValueError("apb_targets must be a non-empty list")
|
||||
targets: list[ApbTarget] = []
|
||||
names: set[str] = set()
|
||||
timed_interfaces: set[str] = set()
|
||||
pure_interfaces: set[str] = set()
|
||||
regions: set[str] = set()
|
||||
slots: set[int] = set()
|
||||
for index, entry in enumerate(value):
|
||||
target = require_object(entry, f"apb_targets[{index}]")
|
||||
slot = target.get("slot")
|
||||
if not isinstance(slot, int) or slot < 0:
|
||||
raise ValueError(f"apb_targets[{index}].slot must be a non-negative integer")
|
||||
name = require_identifier(target.get("name"), f"apb_targets[{index}].name")
|
||||
timed_interface = require_identifier(
|
||||
target.get("timed_interface"), f"apb_targets[{index}].timed_interface"
|
||||
)
|
||||
pure_interface = require_identifier(
|
||||
target.get("pure_interface"), f"apb_targets[{index}].pure_interface"
|
||||
)
|
||||
region_name = require_identifier(target.get("region"), f"apb_targets[{index}].region")
|
||||
requires_ip = parse_requires_ip(
|
||||
target.get("requires_ip"), f"apb_targets[{index}].requires_ip"
|
||||
)
|
||||
region = memory_regions.get(region_name)
|
||||
if region is None:
|
||||
raise ValueError(f"apb_targets[{index}] references unknown region {region_name}")
|
||||
if region.get("route") != "apb" or region.get("kind") != "active":
|
||||
raise ValueError(
|
||||
f"apb_targets[{index}] region {region_name} is not an active APB region"
|
||||
)
|
||||
if requires_ip != region.get("requires_ip"):
|
||||
raise ValueError(
|
||||
f"apb_targets[{index}] requires_ip does not match region {region_name}"
|
||||
)
|
||||
if name in names:
|
||||
raise ValueError(f"APB target name {name} is duplicated")
|
||||
if timed_interface in timed_interfaces:
|
||||
raise ValueError(f"APB timed interface {timed_interface} is duplicated")
|
||||
if pure_interface in pure_interfaces:
|
||||
raise ValueError(f"APB pure interface {pure_interface} is duplicated")
|
||||
if region_name in regions:
|
||||
raise ValueError(f"APB region {region_name} has multiple targets")
|
||||
if slot in slots:
|
||||
raise ValueError(f"APB target slot {slot} is duplicated")
|
||||
names.add(name)
|
||||
timed_interfaces.add(timed_interface)
|
||||
pure_interfaces.add(pure_interface)
|
||||
regions.add(region_name)
|
||||
slots.add(slot)
|
||||
targets.append(
|
||||
ApbTarget(slot, name, timed_interface, pure_interface, region_name, requires_ip)
|
||||
)
|
||||
|
||||
enabled = [
|
||||
target for target in targets if target.requires_ip is None or target.requires_ip == ip
|
||||
]
|
||||
ordered = sorted(enabled, key=lambda item: item.slot)
|
||||
if [target.slot for target in ordered] != list(range(len(ordered))):
|
||||
raise ValueError("enabled APB target slots must be contiguous from zero")
|
||||
active_regions = {
|
||||
symbol
|
||||
for symbol, region in memory_regions.items()
|
||||
if region.get("route") == "apb"
|
||||
and region.get("kind") == "active"
|
||||
and (region.get("requires_ip") is None or region.get("requires_ip") == ip)
|
||||
}
|
||||
claimed_regions = {target.region for target in ordered}
|
||||
if claimed_regions != active_regions:
|
||||
missing = sorted(active_regions - claimed_regions)
|
||||
extra = sorted(claimed_regions - active_regions)
|
||||
details: list[str] = []
|
||||
if missing:
|
||||
details.append(f"missing {', '.join(missing)}")
|
||||
if extra:
|
||||
details.append(f"extra {', '.join(extra)}")
|
||||
raise ValueError(f"APB target regions do not cover the memory map ({'; '.join(details)})")
|
||||
return ordered
|
||||
|
||||
|
||||
@@ -228,8 +331,8 @@ def parse_gpio_functions(value: Any, gpio_pins: int) -> list[GpioFunction]:
|
||||
|
||||
|
||||
def read_topology(
|
||||
topology_path: Path, memory_map_path: Path
|
||||
) -> tuple[list[NativeTarget], list[GpioFunction]]:
|
||||
topology_path: Path, memory_map_path: Path, ip: str
|
||||
) -> tuple[list[NativeTarget], list[ApbTarget], list[GpioFunction]]:
|
||||
document = require_object(json.loads(topology_path.read_text(encoding="utf-8")), "topology")
|
||||
if document.get("schema_version") != 1:
|
||||
raise ValueError("schema_version must be 1")
|
||||
@@ -239,6 +342,7 @@ def read_topology(
|
||||
memory_regions = read_memory_regions(memory_map_path)
|
||||
return (
|
||||
parse_native_targets(document.get("native_targets"), memory_regions),
|
||||
parse_apb_targets(document.get("apb_targets"), memory_regions, ip),
|
||||
parse_gpio_functions(document.get("gpio_alt_functions"), gpio_pins),
|
||||
)
|
||||
|
||||
@@ -271,9 +375,7 @@ def render_nmi_routes(
|
||||
)
|
||||
for target in targets:
|
||||
if not target.disabled:
|
||||
lines.append(
|
||||
f" logic [{len(target.regions) - 1}:0] s_{target.name}_region_sel;"
|
||||
)
|
||||
lines.append(f" logic [{len(target.regions) - 1}:0] s_{target.name}_region_sel;")
|
||||
lines.append("")
|
||||
for target in targets:
|
||||
if target.disabled:
|
||||
@@ -322,6 +424,100 @@ def render_nmi_routes(
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def render_apb_interfaces(targets: list[ApbTarget]) -> str:
|
||||
lines = ["// Generated by rtl/mini/integration/generate_soc_topology.py; do not edit."]
|
||||
for target in targets:
|
||||
lines.extend(
|
||||
[
|
||||
f" apb4_if {target.timed_interface} (clk_i, rst_n_i);",
|
||||
f" apb4_pure_if {target.pure_interface} ();",
|
||||
]
|
||||
)
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def render_apb_bridges(targets: list[ApbTarget]) -> str:
|
||||
lines = ["// Generated by rtl/mini/integration/generate_soc_topology.py; do not edit."]
|
||||
for target in targets:
|
||||
lines.extend(
|
||||
[
|
||||
f" apb4_if_bridge u_{target.name}_apb_bridge (",
|
||||
f" .apb_pure({target.pure_interface}),",
|
||||
f" .timed ({target.timed_interface})",
|
||||
" );",
|
||||
"",
|
||||
]
|
||||
)
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def render_apb_ports(targets: list[ApbTarget]) -> str:
|
||||
lines = ["// Generated by rtl/mini/integration/generate_soc_topology.py; do not edit."]
|
||||
for index, target in enumerate(targets):
|
||||
separator = "," if index != len(targets) - 1 else ""
|
||||
lines.append(f" apb4_pure_if.master {target.name}{separator}")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def render_apb_connections(targets: list[ApbTarget]) -> str:
|
||||
lines = ["// Generated by rtl/mini/integration/generate_soc_topology.py; do not edit."]
|
||||
for index, target in enumerate(targets):
|
||||
separator = "," if index != len(targets) - 1 else ""
|
||||
lines.append(f" .{target.name}({target.pure_interface}){separator}")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def render_apb_declarations(targets: list[ApbTarget]) -> str:
|
||||
count = len(targets)
|
||||
return "\n".join(
|
||||
[
|
||||
"// Generated by rtl/mini/integration/generate_soc_topology.py; do not edit.",
|
||||
f" localparam int NSLV = {count};",
|
||||
" logic [NSLV-1:0] s_psel_comb, s_psel_d, s_psel_q;",
|
||||
"",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def render_apb_request_routes(targets: list[ApbTarget]) -> str:
|
||||
lines = ["// Generated by rtl/mini/integration/generate_soc_topology.py; do not edit."]
|
||||
for target in targets:
|
||||
lines.extend(
|
||||
[
|
||||
f" assign {target.name}.paddr = nmi.addr;",
|
||||
f" assign {target.name}.pprot = '0;",
|
||||
f" assign {target.name}.psel = s_xfer_valid && `SOC_ADDR_IS_{target.region}(nmi.addr);",
|
||||
f" assign {target.name}.penable = s_fsm_q == FSM_ENAB;",
|
||||
f" assign {target.name}.pwrite = |nmi.wstrb;",
|
||||
f" assign {target.name}.pwdata = nmi.wdata;",
|
||||
f" assign {target.name}.pstrb = nmi.wstrb;",
|
||||
"",
|
||||
]
|
||||
)
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def render_apb_select_routes(targets: list[ApbTarget]) -> str:
|
||||
lines = ["// Generated by rtl/mini/integration/generate_soc_topology.py; do not edit."]
|
||||
lines.extend(
|
||||
f" assign s_psel_comb[{target.slot}] = `SOC_ADDR_IS_{target.region}(nmi.addr);"
|
||||
for target in targets
|
||||
)
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def render_apb_response_mux(targets: list[ApbTarget]) -> str:
|
||||
lines = ["// Generated by rtl/mini/integration/generate_soc_topology.py; do not edit."]
|
||||
for signal, expression in (("s_rd_data", "prdata"), ("s_xfer_ready", "pready")):
|
||||
terms = [
|
||||
f"({{{'32' if signal == 's_rd_data' else '1'}{{s_psel_q[{target.slot}]}}}} & "
|
||||
f"{target.name}.{expression})"
|
||||
for target in targets
|
||||
]
|
||||
lines.append(f" assign {signal} = {' | '.join(terms)};")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def render_gpio_bindings(functions: list[GpioFunction]) -> str:
|
||||
lines = ["// Generated by rtl/mini/integration/generate_soc_topology.py; do not edit."]
|
||||
for function in functions:
|
||||
@@ -341,12 +537,20 @@ def render_gpio_bindings(functions: list[GpioFunction]) -> str:
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def generate(topology_path: Path, memory_map_path: Path, output_dir: Path) -> None:
|
||||
targets, gpio_functions = read_topology(topology_path, memory_map_path)
|
||||
def generate(topology_path: Path, memory_map_path: Path, output_dir: Path, ip: str) -> None:
|
||||
targets, apb_targets, gpio_functions = read_topology(topology_path, memory_map_path, ip)
|
||||
memory_regions = read_memory_regions(memory_map_path)
|
||||
rtl_dir = output_dir / "rtl"
|
||||
atomic_write(rtl_dir / "soc_nmi_interfaces.svh", render_nmi_interfaces(targets))
|
||||
atomic_write(rtl_dir / "soc_nmi_routes.svh", render_nmi_routes(targets, memory_regions))
|
||||
atomic_write(rtl_dir / "soc_apb_interfaces.svh", render_apb_interfaces(apb_targets))
|
||||
atomic_write(rtl_dir / "soc_apb_bridges.svh", render_apb_bridges(apb_targets))
|
||||
atomic_write(rtl_dir / "soc_apb_ports.svh", render_apb_ports(apb_targets))
|
||||
atomic_write(rtl_dir / "soc_apb_connections.svh", render_apb_connections(apb_targets))
|
||||
atomic_write(rtl_dir / "soc_apb_declarations.svh", render_apb_declarations(apb_targets))
|
||||
atomic_write(rtl_dir / "soc_apb_request_routes.svh", render_apb_request_routes(apb_targets))
|
||||
atomic_write(rtl_dir / "soc_apb_select_routes.svh", render_apb_select_routes(apb_targets))
|
||||
atomic_write(rtl_dir / "soc_apb_response_mux.svh", render_apb_response_mux(apb_targets))
|
||||
atomic_write(rtl_dir / "soc_gpio_alt_bindings.svh", render_gpio_bindings(gpio_functions))
|
||||
atomic_write(output_dir / "soc_topology.fl", f"+incdir+{rtl_dir}\n")
|
||||
|
||||
@@ -356,15 +560,16 @@ def main() -> int:
|
||||
parser.add_argument("--map", required=True, type=Path)
|
||||
parser.add_argument("--memory-map", required=True, type=Path)
|
||||
parser.add_argument("--output-dir", type=Path)
|
||||
parser.add_argument("--ip", choices=("NONE", "MDD"), default="NONE")
|
||||
parser.add_argument("--check", action="store_true", help="validate only; write nothing")
|
||||
arguments = parser.parse_args()
|
||||
try:
|
||||
if arguments.check:
|
||||
read_topology(arguments.map, arguments.memory_map)
|
||||
read_topology(arguments.map, arguments.memory_map, arguments.ip)
|
||||
else:
|
||||
if arguments.output_dir is None:
|
||||
parser.error("--output-dir is required unless --check is used")
|
||||
generate(arguments.map, arguments.memory_map, arguments.output_dir)
|
||||
generate(arguments.map, arguments.memory_map, arguments.output_dir, arguments.ip)
|
||||
except (OSError, ValueError, json.JSONDecodeError) as error:
|
||||
parser.error(str(error))
|
||||
return 0
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate scalar user core and user IP integration bindings."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
IDENTIFIER_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*$")
|
||||
APB_REQUEST_SIGNALS = ("paddr", "pprot", "psel", "penable", "pwrite", "pwdata", "pstrb")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ExtensionTarget:
|
||||
slot: int
|
||||
module: str
|
||||
instance: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ExtensionMap:
|
||||
core_selector_width: int
|
||||
ip_selector_width: int
|
||||
gpio_width: int
|
||||
core_targets: tuple[ExtensionTarget, ...]
|
||||
ip_targets: tuple[ExtensionTarget, ...]
|
||||
|
||||
|
||||
def atomic_write(path: Path, content: str) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
if path.exists() and path.read_text(encoding="utf-8") == content:
|
||||
return
|
||||
descriptor, temporary_name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
|
||||
try:
|
||||
with os.fdopen(descriptor, "w", encoding="utf-8", newline="\n") as output:
|
||||
output.write(content)
|
||||
os.replace(temporary_name, path)
|
||||
except BaseException:
|
||||
Path(temporary_name).unlink(missing_ok=True)
|
||||
raise
|
||||
|
||||
|
||||
def require_object(value: Any, field: str) -> dict[str, Any]:
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError(f"{field} must be an object")
|
||||
return value
|
||||
|
||||
|
||||
def require_identifier(value: Any, field: str) -> str:
|
||||
if not isinstance(value, str) or IDENTIFIER_RE.fullmatch(value) is None:
|
||||
raise ValueError(f"{field} must be a SystemVerilog identifier")
|
||||
return value
|
||||
|
||||
|
||||
def require_positive_integer(value: Any, field: str) -> int:
|
||||
if not isinstance(value, int) or value <= 0:
|
||||
raise ValueError(f"{field} must be a positive integer")
|
||||
return value
|
||||
|
||||
|
||||
def parse_targets(value: Any, field: str, selector_width: int) -> tuple[ExtensionTarget, ...]:
|
||||
if not isinstance(value, list) or not value:
|
||||
raise ValueError(f"{field} must be a non-empty list")
|
||||
targets: list[ExtensionTarget] = []
|
||||
slots: set[int] = set()
|
||||
instances: set[str] = set()
|
||||
for index, entry in enumerate(value):
|
||||
target = require_object(entry, f"{field}[{index}]")
|
||||
slot = target.get("slot")
|
||||
if not isinstance(slot, int) or not 0 < slot < (1 << selector_width):
|
||||
raise ValueError(f"{field}[{index}].slot must be within the selector range")
|
||||
module = require_identifier(target.get("module"), f"{field}[{index}].module")
|
||||
instance = require_identifier(target.get("instance"), f"{field}[{index}].instance")
|
||||
if slot in slots:
|
||||
raise ValueError(f"{field} slot {slot} is duplicated")
|
||||
if instance in instances:
|
||||
raise ValueError(f"{field} instance {instance} is duplicated")
|
||||
slots.add(slot)
|
||||
instances.add(instance)
|
||||
targets.append(ExtensionTarget(slot, module, instance))
|
||||
ordered = tuple(sorted(targets, key=lambda item: item.slot))
|
||||
if [target.slot for target in ordered] != list(range(1, len(ordered) + 1)):
|
||||
raise ValueError(f"{field} slots must be contiguous from one")
|
||||
return ordered
|
||||
|
||||
|
||||
def read_extensions(path: Path) -> ExtensionMap:
|
||||
document = require_object(json.loads(path.read_text(encoding="utf-8")), "user extensions")
|
||||
if document.get("schema_version") != 1:
|
||||
raise ValueError("schema_version must be 1")
|
||||
core_selector_width = require_positive_integer(
|
||||
document.get("core_selector_width"), "core_selector_width"
|
||||
)
|
||||
ip_selector_width = require_positive_integer(
|
||||
document.get("ip_selector_width"), "ip_selector_width"
|
||||
)
|
||||
gpio_width = require_positive_integer(document.get("gpio_width"), "gpio_width")
|
||||
return ExtensionMap(
|
||||
core_selector_width,
|
||||
ip_selector_width,
|
||||
gpio_width,
|
||||
parse_targets(document.get("core_targets"), "core_targets", core_selector_width),
|
||||
parse_targets(document.get("ip_targets"), "ip_targets", ip_selector_width),
|
||||
)
|
||||
|
||||
|
||||
def render_core_bindings(extensions: ExtensionMap) -> str:
|
||||
lines = ["// Generated by rtl/mini/integration/generate_user_extensions.py; do not edit."]
|
||||
for target in extensions.core_targets:
|
||||
lines.extend(
|
||||
[
|
||||
f" nmi_if u_user_{target.slot}_nmi_if ();",
|
||||
f" logic [31:0] s_user_{target.slot}_irq;",
|
||||
]
|
||||
)
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
" always_comb begin",
|
||||
" nmi.valid = '0;",
|
||||
" nmi.addr = '0;",
|
||||
" nmi.wdata = '0;",
|
||||
" nmi.wstrb = '0;",
|
||||
]
|
||||
)
|
||||
for target in extensions.core_targets:
|
||||
lines.extend(
|
||||
[
|
||||
f" u_user_{target.slot}_nmi_if.rdata = '0;",
|
||||
f" u_user_{target.slot}_nmi_if.ready = '0;",
|
||||
f" s_user_{target.slot}_irq = '0;",
|
||||
]
|
||||
)
|
||||
lines.append(" unique case (sel_i)")
|
||||
for target in extensions.core_targets:
|
||||
lines.extend(
|
||||
[
|
||||
f" {extensions.core_selector_width}'d{target.slot}: begin",
|
||||
f" nmi.valid = u_user_{target.slot}_nmi_if.valid;",
|
||||
f" nmi.addr = u_user_{target.slot}_nmi_if.addr;",
|
||||
f" nmi.wdata = u_user_{target.slot}_nmi_if.wdata;",
|
||||
f" nmi.wstrb = u_user_{target.slot}_nmi_if.wstrb;",
|
||||
f" u_user_{target.slot}_nmi_if.rdata = nmi.rdata;",
|
||||
f" u_user_{target.slot}_nmi_if.ready = nmi.ready;",
|
||||
f" s_user_{target.slot}_irq = irq_i;",
|
||||
" end",
|
||||
]
|
||||
)
|
||||
lines.extend([" default: ;", " endcase", " end", ""])
|
||||
for target in extensions.core_targets:
|
||||
lines.extend(
|
||||
[
|
||||
f" {target.module} #({target.slot}) {target.instance} (",
|
||||
" .clk_i (clk_i),",
|
||||
" .rst_n_i(rst_n_i),",
|
||||
f" .irq_i (s_user_{target.slot}_irq),",
|
||||
f" .nmi (u_user_{target.slot}_nmi_if)",
|
||||
" );",
|
||||
"",
|
||||
]
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def render_ip_bindings(extensions: ExtensionMap) -> str:
|
||||
lines = ["// Generated by rtl/mini/integration/generate_user_extensions.py; do not edit."]
|
||||
for target in extensions.ip_targets:
|
||||
lines.extend(
|
||||
[
|
||||
f" user_gpio_if #(`USER_GPIO_NUM) u_user_{target.slot}_gpio_if ();",
|
||||
f" apb4_if u_user_{target.slot}_apb_if (clk_i, rst_n_i);",
|
||||
]
|
||||
)
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
" always_comb begin",
|
||||
" gpio.do_o = '0;",
|
||||
" gpio.oe_o = '0;",
|
||||
" apb.pready = '0;",
|
||||
" apb.prdata = '0;",
|
||||
" apb.pslverr = '0;",
|
||||
]
|
||||
)
|
||||
for target in extensions.ip_targets:
|
||||
lines.extend(
|
||||
f" u_user_{target.slot}_apb_if.{signal} = '0;" for signal in APB_REQUEST_SIGNALS
|
||||
)
|
||||
lines.append(" unique case (sel_i)")
|
||||
for target in extensions.ip_targets:
|
||||
lines.extend(
|
||||
[
|
||||
f" {extensions.ip_selector_width}'d{target.slot}: begin",
|
||||
f" gpio.do_o = u_user_{target.slot}_gpio_if.do_o;",
|
||||
f" gpio.oe_o = u_user_{target.slot}_gpio_if.oe_o;",
|
||||
f" apb.pready = u_user_{target.slot}_apb_if.pready;",
|
||||
f" apb.prdata = u_user_{target.slot}_apb_if.prdata;",
|
||||
f" apb.pslverr = u_user_{target.slot}_apb_if.pslverr;",
|
||||
]
|
||||
)
|
||||
lines.extend(
|
||||
f" u_user_{target.slot}_apb_if.{signal} = apb.{signal};"
|
||||
for signal in APB_REQUEST_SIGNALS
|
||||
)
|
||||
lines.append(" end")
|
||||
lines.extend([" default: ;", " endcase", " end", ""])
|
||||
for target in extensions.ip_targets:
|
||||
lines.append(f" assign u_user_{target.slot}_gpio_if.di_i = gpio.di_i;")
|
||||
lines.append("")
|
||||
for target in extensions.ip_targets:
|
||||
lines.extend(
|
||||
[
|
||||
f" {target.module} #({target.slot}) {target.instance} (",
|
||||
" .clk_i (clk_i),",
|
||||
" .rst_n_i(rst_n_i),",
|
||||
f" .gpio (u_user_{target.slot}_gpio_if),",
|
||||
f" .apb (u_user_{target.slot}_apb_if)",
|
||||
" );",
|
||||
"",
|
||||
]
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate(source: Path, output_dir: Path) -> None:
|
||||
extensions = read_extensions(source)
|
||||
rtl_dir = output_dir / "rtl"
|
||||
atomic_write(rtl_dir / "user_core_bindings.svh", render_core_bindings(extensions))
|
||||
atomic_write(rtl_dir / "user_ip_bindings.svh", render_ip_bindings(extensions))
|
||||
atomic_write(output_dir / "user_extensions.fl", f"+incdir+{rtl_dir}\n")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--map", required=True, type=Path)
|
||||
parser.add_argument("--output-dir", type=Path)
|
||||
parser.add_argument("--check", action="store_true", help="validate only; write nothing")
|
||||
arguments = parser.parse_args()
|
||||
try:
|
||||
if arguments.check:
|
||||
read_extensions(arguments.map)
|
||||
else:
|
||||
if arguments.output_dir is None:
|
||||
parser.error("--output-dir is required unless --check is used")
|
||||
generate(arguments.map, arguments.output_dir)
|
||||
except (OSError, ValueError, json.JSONDecodeError) as error:
|
||||
parser.error(str(error))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -21,6 +21,18 @@
|
||||
{"slot": 16, "name": "opipsram", "interface": "u_opipsram_nmi_if", "disabled": true},
|
||||
{"slot": 17, "name": "i2c1", "interface": "u_i2c1_nmi_if", "regions": ["NMI_I2C1"]}
|
||||
],
|
||||
"apb_targets": [
|
||||
{"slot": 0, "name": "archinfo", "timed_interface": "u_archinfo_apb_if", "pure_interface": "u_archinfo_apb_pure_if", "region": "APB_ARCHINFO"},
|
||||
{"slot": 1, "name": "rng", "timed_interface": "u_rng_apb_if", "pure_interface": "u_rng_apb_pure_if", "region": "APB_RNG"},
|
||||
{"slot": 2, "name": "uart", "timed_interface": "u_uart1_apb_if", "pure_interface": "u_uart1_apb_pure_if", "region": "APB_UART1"},
|
||||
{"slot": 3, "name": "pwm", "timed_interface": "u_pwm_apb_if", "pure_interface": "u_pwm_apb_pure_if", "region": "APB_PWM"},
|
||||
{"slot": 4, "name": "ps2", "timed_interface": "u_ps2_apb_if", "pure_interface": "u_ps2_apb_pure_if", "region": "APB_PS2"},
|
||||
{"slot": 5, "name": "rtc", "timed_interface": "u_rtc_apb_if", "pure_interface": "u_rtc_apb_pure_if", "region": "APB_RTC"},
|
||||
{"slot": 6, "name": "wdg", "timed_interface": "u_wdg_apb_if", "pure_interface": "u_wdg_apb_pure_if", "region": "APB_WDG"},
|
||||
{"slot": 7, "name": "crc", "timed_interface": "u_crc_apb_if", "pure_interface": "u_crc_apb_pure_if", "region": "APB_CRC"},
|
||||
{"slot": 8, "name": "tmr", "timed_interface": "u_tmr_apb_if", "pure_interface": "u_tmr_apb_pure_if", "region": "APB_TMR"},
|
||||
{"slot": 9, "name": "user_ip", "timed_interface": "u_user_ip_apb_if", "pure_interface": "u_user_ip_apb_pure_if", "region": "APB_USER_IP", "requires_ip": "MDD"}
|
||||
],
|
||||
"gpio_alt_functions": [
|
||||
{"pin": 0, "alt0": {"inputs": ["u_uart1_if.rx_i"], "do": "1'b0", "oe": "1'b0"}, "alt1": {"inputs": ["u_ps2_if.ps2_clk_i"], "do": "1'b0", "oe": "1'b0"}},
|
||||
{"pin": 1, "alt0": {"inputs": [], "do": "u_uart1_if.tx_o", "oe": "1'b1"}, "alt1": {"inputs": ["u_ps2_if.ps2_dat_i"], "do": "1'b0", "oe": "1'b0"}},
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"core_selector_width": 5,
|
||||
"ip_selector_width": 8,
|
||||
"gpio_width": 32,
|
||||
"core_targets": [
|
||||
{"slot": 1, "module": "user_core_design_username1", "instance": "u_user_core_design_username1"},
|
||||
{"slot": 2, "module": "user_core_design_username2", "instance": "u_user_core_design_username2"},
|
||||
{"slot": 3, "module": "user_core_design_username3", "instance": "u_user_core_design_username3"},
|
||||
{"slot": 4, "module": "user_core_design_username4", "instance": "u_user_core_design_username4"},
|
||||
{"slot": 5, "module": "user_core_design_username5", "instance": "u_user_core_design_username5"},
|
||||
{"slot": 6, "module": "user_core_design_username6", "instance": "u_user_core_design_username6"}
|
||||
],
|
||||
"ip_targets": [
|
||||
{"slot": 1, "module": "user_ip_design_username1", "instance": "u_user_ip_design_username1"},
|
||||
{"slot": 2, "module": "user_ip_design_username2", "instance": "u_user_ip_design_username2"}
|
||||
]
|
||||
}
|
||||
@@ -26,6 +26,7 @@ PATH_PREFIXES = {
|
||||
"picorv32": RTL_DIR / "managed" / "picorv32" / "rtl",
|
||||
"tb": MINI_DIR / "dv" / "tb",
|
||||
"model": MINI_DIR / "dv" / "model",
|
||||
"sva": MINI_DIR / "dv" / "sva",
|
||||
"top": MINI_DIR / "top",
|
||||
}
|
||||
|
||||
@@ -101,9 +102,7 @@ def parse_args() -> argparse.Namespace:
|
||||
parser.add_argument(
|
||||
"--output-dir", type=Path, default=DEFAULT_OUTPUT_DIR, help="output directory"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--define", action="append", default=[], help="filelist +define+ token"
|
||||
)
|
||||
parser.add_argument("--define", action="append", default=[], help="filelist +define+ token")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
|
||||
@@ -33,85 +33,20 @@ module ip_apb_wrapper (
|
||||
// verilog_format: on
|
||||
);
|
||||
|
||||
// verilog_format: off
|
||||
apb4_if u_archinfo_apb_if (clk_i, rst_n_i);
|
||||
apb4_if u_rng_apb_if (clk_i, rst_n_i);
|
||||
apb4_if u_uart1_apb_if (clk_i, rst_n_i);
|
||||
apb4_if u_pwm_apb_if (clk_i, rst_n_i);
|
||||
apb4_if u_ps2_apb_if (clk_i, rst_n_i);
|
||||
apb4_if u_rtc_apb_if (clk_i, rst_n_i);
|
||||
apb4_if u_wdg_apb_if (clk_i, rst_n_i);
|
||||
apb4_if u_crc_apb_if (clk_i, rst_n_i);
|
||||
apb4_if u_tmr_apb_if (clk_i, rst_n_i);
|
||||
// Generated timed and pure scalar APB interfaces preserve FPGA compatibility.
|
||||
`include "soc_apb_interfaces.svh"
|
||||
|
||||
// NOTE: for FPGA-compatible
|
||||
apb4_pure_if u_archinfo_apb_pure_if ();
|
||||
apb4_pure_if u_rng_apb_pure_if ();
|
||||
apb4_pure_if u_uart1_apb_pure_if ();
|
||||
apb4_pure_if u_pwm_apb_pure_if ();
|
||||
apb4_pure_if u_ps2_apb_pure_if ();
|
||||
apb4_pure_if u_rtc_apb_pure_if ();
|
||||
apb4_pure_if u_wdg_apb_pure_if ();
|
||||
apb4_pure_if u_crc_apb_pure_if ();
|
||||
apb4_pure_if u_tmr_apb_pure_if ();
|
||||
|
||||
// low freq clock perip
|
||||
rtc_if u_rtc_if (clk_aud_i, rst_aud_n_i);
|
||||
// Low-frequency peripherals retain their dedicated interfaces.
|
||||
rtc_if u_rtc_if (
|
||||
clk_aud_i,
|
||||
rst_aud_n_i
|
||||
);
|
||||
wdg_if u_wdg_if (clk_aud_i);
|
||||
tmr_if u_tmr_if (clk_aud_i);
|
||||
|
||||
assign u_tmr_if.capch_i = tmr_capch_i;
|
||||
// verilog_format: on
|
||||
|
||||
// verilog_format: off
|
||||
`ifdef IP_MDD
|
||||
apb4_if u_user_ip_apb_if (clk_i, rst_n_i);
|
||||
apb4_pure_if u_user_ip_apb_pure_if ();
|
||||
`endif
|
||||
// verilog_format: on
|
||||
|
||||
apb4_if_bridge u_archinfo_apb_bridge (
|
||||
.apb_pure(u_archinfo_apb_pure_if),
|
||||
.timed (u_archinfo_apb_if)
|
||||
);
|
||||
apb4_if_bridge u_rng_apb_bridge (
|
||||
.apb_pure(u_rng_apb_pure_if),
|
||||
.timed (u_rng_apb_if)
|
||||
);
|
||||
apb4_if_bridge u_uart1_apb_bridge (
|
||||
.apb_pure(u_uart1_apb_pure_if),
|
||||
.timed (u_uart1_apb_if)
|
||||
);
|
||||
apb4_if_bridge u_pwm_apb_bridge (
|
||||
.apb_pure(u_pwm_apb_pure_if),
|
||||
.timed (u_pwm_apb_if)
|
||||
);
|
||||
apb4_if_bridge u_ps2_apb_bridge (
|
||||
.apb_pure(u_ps2_apb_pure_if),
|
||||
.timed (u_ps2_apb_if)
|
||||
);
|
||||
apb4_if_bridge u_rtc_apb_bridge (
|
||||
.apb_pure(u_rtc_apb_pure_if),
|
||||
.timed (u_rtc_apb_if)
|
||||
);
|
||||
apb4_if_bridge u_wdg_apb_bridge (
|
||||
.apb_pure(u_wdg_apb_pure_if),
|
||||
.timed (u_wdg_apb_if)
|
||||
);
|
||||
apb4_if_bridge u_crc_apb_bridge (
|
||||
.apb_pure(u_crc_apb_pure_if),
|
||||
.timed (u_crc_apb_if)
|
||||
);
|
||||
apb4_if_bridge u_tmr_apb_bridge (
|
||||
.apb_pure(u_tmr_apb_pure_if),
|
||||
.timed (u_tmr_apb_if)
|
||||
);
|
||||
`ifdef IP_MDD
|
||||
apb4_if_bridge u_user_ip_apb_bridge (
|
||||
.apb_pure(u_user_ip_apb_pure_if),
|
||||
.timed (u_user_ip_apb_if)
|
||||
);
|
||||
`endif
|
||||
`include "soc_apb_bridges.svh"
|
||||
|
||||
// verilog_format: off
|
||||
apb4_archinfo u_apb4_archinfo (u_archinfo_apb_if);
|
||||
@@ -135,21 +70,10 @@ module ip_apb_wrapper (
|
||||
assign irq_o[6] = 1'b0;
|
||||
|
||||
nmi2apb u_nmi2apb (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i (rst_n_i),
|
||||
.nmi (nmi),
|
||||
`ifdef IP_MDD
|
||||
.user_ip (u_user_ip_apb_pure_if),
|
||||
`endif
|
||||
.archinfo(u_archinfo_apb_pure_if),
|
||||
.rng (u_rng_apb_pure_if),
|
||||
.uart (u_uart1_apb_pure_if),
|
||||
.pwm (u_pwm_apb_pure_if),
|
||||
.ps2 (u_ps2_apb_pure_if),
|
||||
.rtc (u_rtc_apb_pure_if),
|
||||
.wdg (u_wdg_apb_pure_if),
|
||||
.crc (u_crc_apb_pure_if),
|
||||
.tmr (u_tmr_apb_pure_if)
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i(rst_n_i),
|
||||
.nmi (nmi),
|
||||
`include "soc_apb_connections.svh"
|
||||
);
|
||||
|
||||
`ifdef IP_MDD
|
||||
|
||||
@@ -20,168 +20,7 @@ module user_core_top (
|
||||
// verilog_format: on
|
||||
);
|
||||
|
||||
// ====== GENERATED BY SCRIPT ======
|
||||
// verilog_format: off
|
||||
nmi_if u_user_1_nmi_if ();
|
||||
nmi_if u_user_2_nmi_if ();
|
||||
nmi_if u_user_3_nmi_if ();
|
||||
nmi_if u_user_4_nmi_if ();
|
||||
nmi_if u_user_5_nmi_if ();
|
||||
nmi_if u_user_6_nmi_if ();
|
||||
logic [31:0] s_user_1_irq;
|
||||
logic [31:0] s_user_2_irq;
|
||||
logic [31:0] s_user_3_irq;
|
||||
logic [31:0] s_user_4_irq;
|
||||
logic [31:0] s_user_5_irq;
|
||||
logic [31:0] s_user_6_irq;
|
||||
// ...
|
||||
// verilog_format: on
|
||||
always_comb begin
|
||||
nmi.valid = '0;
|
||||
nmi.addr = '0;
|
||||
nmi.wdata = '0;
|
||||
nmi.wstrb = '0;
|
||||
u_user_1_nmi_if.rdata = '0;
|
||||
u_user_1_nmi_if.ready = '0;
|
||||
s_user_1_irq = '0;
|
||||
u_user_2_nmi_if.rdata = '0;
|
||||
u_user_2_nmi_if.ready = '0;
|
||||
s_user_2_irq = '0;
|
||||
u_user_3_nmi_if.rdata = '0;
|
||||
u_user_3_nmi_if.ready = '0;
|
||||
s_user_3_irq = '0;
|
||||
u_user_4_nmi_if.rdata = '0;
|
||||
u_user_4_nmi_if.ready = '0;
|
||||
s_user_4_irq = '0;
|
||||
u_user_5_nmi_if.rdata = '0;
|
||||
u_user_5_nmi_if.ready = '0;
|
||||
s_user_5_irq = '0;
|
||||
u_user_6_nmi_if.rdata = '0;
|
||||
u_user_6_nmi_if.ready = '0;
|
||||
s_user_6_irq = '0;
|
||||
unique case (sel_i)
|
||||
5'd1: begin
|
||||
nmi.valid = u_user_1_nmi_if.valid;
|
||||
nmi.addr = u_user_1_nmi_if.addr;
|
||||
nmi.wdata = u_user_1_nmi_if.wdata;
|
||||
nmi.wstrb = u_user_1_nmi_if.wstrb;
|
||||
u_user_1_nmi_if.rdata = nmi.rdata;
|
||||
u_user_1_nmi_if.ready = nmi.ready;
|
||||
s_user_1_irq = irq_i;
|
||||
end
|
||||
5'd2: begin
|
||||
nmi.valid = u_user_2_nmi_if.valid;
|
||||
nmi.addr = u_user_2_nmi_if.addr;
|
||||
nmi.wdata = u_user_2_nmi_if.wdata;
|
||||
nmi.wstrb = u_user_2_nmi_if.wstrb;
|
||||
u_user_2_nmi_if.rdata = nmi.rdata;
|
||||
u_user_2_nmi_if.ready = nmi.ready;
|
||||
s_user_2_irq = irq_i;
|
||||
end
|
||||
5'd3: begin
|
||||
nmi.valid = u_user_3_nmi_if.valid;
|
||||
nmi.addr = u_user_3_nmi_if.addr;
|
||||
nmi.wdata = u_user_3_nmi_if.wdata;
|
||||
nmi.wstrb = u_user_3_nmi_if.wstrb;
|
||||
u_user_3_nmi_if.rdata = nmi.rdata;
|
||||
u_user_3_nmi_if.ready = nmi.ready;
|
||||
s_user_3_irq = irq_i;
|
||||
end
|
||||
5'd4: begin
|
||||
nmi.valid = u_user_4_nmi_if.valid;
|
||||
nmi.addr = u_user_4_nmi_if.addr;
|
||||
nmi.wdata = u_user_4_nmi_if.wdata;
|
||||
nmi.wstrb = u_user_4_nmi_if.wstrb;
|
||||
u_user_4_nmi_if.rdata = nmi.rdata;
|
||||
u_user_4_nmi_if.ready = nmi.ready;
|
||||
s_user_4_irq = irq_i;
|
||||
end
|
||||
5'd5: begin
|
||||
nmi.valid = u_user_5_nmi_if.valid;
|
||||
nmi.addr = u_user_5_nmi_if.addr;
|
||||
nmi.wdata = u_user_5_nmi_if.wdata;
|
||||
nmi.wstrb = u_user_5_nmi_if.wstrb;
|
||||
u_user_5_nmi_if.rdata = nmi.rdata;
|
||||
u_user_5_nmi_if.ready = nmi.ready;
|
||||
s_user_5_irq = irq_i;
|
||||
end
|
||||
5'd6: begin
|
||||
nmi.valid = u_user_6_nmi_if.valid;
|
||||
nmi.addr = u_user_6_nmi_if.addr;
|
||||
nmi.wdata = u_user_6_nmi_if.wdata;
|
||||
nmi.wstrb = u_user_6_nmi_if.wstrb;
|
||||
u_user_6_nmi_if.rdata = nmi.rdata;
|
||||
u_user_6_nmi_if.ready = nmi.ready;
|
||||
s_user_6_irq = irq_i;
|
||||
end
|
||||
default: begin
|
||||
nmi.valid = '0;
|
||||
nmi.addr = '0;
|
||||
nmi.wdata = '0;
|
||||
nmi.wstrb = '0;
|
||||
u_user_1_nmi_if.rdata = '0;
|
||||
u_user_1_nmi_if.ready = '0;
|
||||
s_user_1_irq = '0;
|
||||
u_user_2_nmi_if.rdata = '0;
|
||||
u_user_2_nmi_if.ready = '0;
|
||||
s_user_2_irq = '0;
|
||||
u_user_3_nmi_if.rdata = '0;
|
||||
u_user_3_nmi_if.ready = '0;
|
||||
s_user_3_irq = '0;
|
||||
u_user_4_nmi_if.rdata = '0;
|
||||
u_user_4_nmi_if.ready = '0;
|
||||
s_user_4_irq = '0;
|
||||
u_user_5_nmi_if.rdata = '0;
|
||||
u_user_5_nmi_if.ready = '0;
|
||||
s_user_5_irq = '0;
|
||||
u_user_6_nmi_if.rdata = '0;
|
||||
u_user_6_nmi_if.ready = '0;
|
||||
s_user_6_irq = '0;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
|
||||
|
||||
user_core_design_username1 #(1) u_user_core_design_username1 (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i(rst_n_i),
|
||||
.irq_i (s_user_1_irq),
|
||||
.nmi (u_user_1_nmi_if)
|
||||
);
|
||||
|
||||
user_core_design_username2 #(2) u_user_core_design_username2 (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i(rst_n_i),
|
||||
.irq_i (s_user_2_irq),
|
||||
.nmi (u_user_2_nmi_if)
|
||||
);
|
||||
|
||||
user_core_design_username3 #(3) u_user_core_design_username3 (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i(rst_n_i),
|
||||
.irq_i (s_user_3_irq),
|
||||
.nmi (u_user_3_nmi_if)
|
||||
);
|
||||
|
||||
user_core_design_username4 #(4) u_user_core_design_username4 (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i(rst_n_i),
|
||||
.irq_i (s_user_4_irq),
|
||||
.nmi (u_user_4_nmi_if)
|
||||
);
|
||||
|
||||
user_core_design_username5 #(5) u_user_core_design_username5 (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i(rst_n_i),
|
||||
.irq_i (s_user_5_irq),
|
||||
.nmi (u_user_5_nmi_if)
|
||||
);
|
||||
|
||||
user_core_design_username6 #(6) u_user_core_design_username6 (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i(rst_n_i),
|
||||
.irq_i (s_user_6_irq),
|
||||
.nmi (u_user_6_nmi_if)
|
||||
);
|
||||
// Generated bindings preserve scalar user-core interfaces and isolation.
|
||||
`include "user_core_bindings.svh"
|
||||
|
||||
endmodule
|
||||
+2
-106
@@ -21,111 +21,7 @@ module user_ip_top (
|
||||
// verilog_format: on
|
||||
);
|
||||
|
||||
// ====== GENERATED BY SCRIPT ======
|
||||
// verilog_format: off
|
||||
user_gpio_if #(`USER_GPIO_NUM) u_user_1_gpio_if();
|
||||
user_gpio_if #(`USER_GPIO_NUM) u_user_2_gpio_if();
|
||||
|
||||
apb4_if u_user_1_apb_if(clk_i, rst_n_i);
|
||||
apb4_if u_user_2_apb_if(clk_i, rst_n_i);
|
||||
// ...
|
||||
// verilog_format: on
|
||||
always_comb begin
|
||||
// gpio
|
||||
gpio.do_o = '0;
|
||||
gpio.oe_o = '0;
|
||||
// apb
|
||||
apb.pready = '0;
|
||||
apb.prdata = '0;
|
||||
apb.pslverr = '0;
|
||||
u_user_1_apb_if.paddr = '0;
|
||||
u_user_1_apb_if.pprot = '0;
|
||||
u_user_1_apb_if.psel = '0;
|
||||
u_user_1_apb_if.penable = '0;
|
||||
u_user_1_apb_if.pwrite = '0;
|
||||
u_user_1_apb_if.pwdata = '0;
|
||||
u_user_1_apb_if.pstrb = '0;
|
||||
u_user_2_apb_if.paddr = '0;
|
||||
u_user_2_apb_if.pprot = '0;
|
||||
u_user_2_apb_if.psel = '0;
|
||||
u_user_2_apb_if.penable = '0;
|
||||
u_user_2_apb_if.pwrite = '0;
|
||||
u_user_2_apb_if.pwdata = '0;
|
||||
u_user_2_apb_if.pstrb = '0;
|
||||
unique case (sel_i)
|
||||
8'd1: begin
|
||||
// gpio
|
||||
gpio.do_o = u_user_1_gpio_if.do_o;
|
||||
gpio.oe_o = u_user_1_gpio_if.oe_o;
|
||||
// apb
|
||||
apb.pready = u_user_1_apb_if.pready;
|
||||
apb.prdata = u_user_1_apb_if.prdata;
|
||||
apb.pslverr = u_user_1_apb_if.pslverr;
|
||||
u_user_1_apb_if.paddr = apb.paddr;
|
||||
u_user_1_apb_if.pprot = apb.pprot;
|
||||
u_user_1_apb_if.psel = apb.psel;
|
||||
u_user_1_apb_if.penable = apb.penable;
|
||||
u_user_1_apb_if.pwrite = apb.pwrite;
|
||||
u_user_1_apb_if.pwdata = apb.pwdata;
|
||||
u_user_1_apb_if.pstrb = apb.pstrb;
|
||||
end
|
||||
8'd2: begin
|
||||
// gpio
|
||||
gpio.do_o = u_user_2_gpio_if.do_o;
|
||||
gpio.oe_o = u_user_2_gpio_if.oe_o;
|
||||
// apb
|
||||
apb.pready = u_user_2_apb_if.pready;
|
||||
apb.prdata = u_user_2_apb_if.prdata;
|
||||
apb.pslverr = u_user_2_apb_if.pslverr;
|
||||
u_user_2_apb_if.paddr = apb.paddr;
|
||||
u_user_2_apb_if.pprot = apb.pprot;
|
||||
u_user_2_apb_if.psel = apb.psel;
|
||||
u_user_2_apb_if.penable = apb.penable;
|
||||
u_user_2_apb_if.pwrite = apb.pwrite;
|
||||
u_user_2_apb_if.pwdata = apb.pwdata;
|
||||
u_user_2_apb_if.pstrb = apb.pstrb;
|
||||
end
|
||||
default: begin
|
||||
// gpio
|
||||
gpio.do_o = '0;
|
||||
gpio.oe_o = '0;
|
||||
// apb
|
||||
apb.pready = '0;
|
||||
apb.prdata = '0;
|
||||
apb.pslverr = '0;
|
||||
u_user_1_apb_if.paddr = '0;
|
||||
u_user_1_apb_if.pprot = '0;
|
||||
u_user_1_apb_if.psel = '0;
|
||||
u_user_1_apb_if.penable = '0;
|
||||
u_user_1_apb_if.pwrite = '0;
|
||||
u_user_1_apb_if.pwdata = '0;
|
||||
u_user_1_apb_if.pstrb = '0;
|
||||
u_user_2_apb_if.paddr = '0;
|
||||
u_user_2_apb_if.pprot = '0;
|
||||
u_user_2_apb_if.psel = '0;
|
||||
u_user_2_apb_if.penable = '0;
|
||||
u_user_2_apb_if.pwrite = '0;
|
||||
u_user_2_apb_if.pwdata = '0;
|
||||
u_user_2_apb_if.pstrb = '0;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
|
||||
assign u_user_1_gpio_if.di_i = gpio.di_i;
|
||||
assign u_user_2_gpio_if.di_i = gpio.di_i;
|
||||
|
||||
user_ip_design_username1 #(1) u_user_ip_design_username1 (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i(rst_n_i),
|
||||
.gpio (u_user_1_gpio_if),
|
||||
.apb (u_user_1_apb_if)
|
||||
);
|
||||
|
||||
user_ip_design_username2 #(2) u_user_ip_design_username2 (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i(rst_n_i),
|
||||
.gpio (u_user_2_gpio_if),
|
||||
.apb (u_user_2_apb_if)
|
||||
);
|
||||
// Generated bindings preserve scalar user-IP interfaces and isolation.
|
||||
`include "user_ip_bindings.svh"
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate the declared Mini SoC root clock/reset domains and CDC crossings."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
RESET_PRIMITIVES = {"rst_sync"}
|
||||
CDC_PRIMITIVES = {"async_fifo", "cdc_2phase", "cdc_sync"}
|
||||
IDENTIFIER_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*$")
|
||||
|
||||
|
||||
def require_object(value: Any, field: str) -> dict[str, Any]:
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError(f"{field} must be an object")
|
||||
return value
|
||||
|
||||
|
||||
def require_string(value: Any, field: str) -> str:
|
||||
if not isinstance(value, str) or not value:
|
||||
raise ValueError(f"{field} must be a non-empty string")
|
||||
return value
|
||||
|
||||
|
||||
def require_identifier(value: Any, field: str) -> str:
|
||||
identifier = require_string(value, field)
|
||||
if IDENTIFIER_RE.fullmatch(identifier) is None:
|
||||
raise ValueError(f"{field} must be a SystemVerilog identifier")
|
||||
return identifier
|
||||
|
||||
|
||||
def require_instance(path: Path, primitive: str, instance: str, field: str) -> None:
|
||||
if not path.is_file():
|
||||
raise ValueError(f"{field}.path does not exist: {path}")
|
||||
source = path.read_text(encoding="utf-8")
|
||||
expression = re.compile(rf"\b{re.escape(primitive)}\b[\s\S]*?\b{re.escape(instance)}\b")
|
||||
if expression.search(source) is None:
|
||||
raise ValueError(f"{field} does not declare {primitive} instance {instance}")
|
||||
|
||||
|
||||
def validate(document_path: Path, root: Path) -> None:
|
||||
document = require_object(json.loads(document_path.read_text(encoding="utf-8")), "domain map")
|
||||
if document.get("schema_version") != 1:
|
||||
raise ValueError("schema_version must be 1")
|
||||
|
||||
raw_domains = document.get("domains")
|
||||
if not isinstance(raw_domains, list) or not raw_domains:
|
||||
raise ValueError("domains must be a non-empty list")
|
||||
names: set[str] = set()
|
||||
for index, value in enumerate(raw_domains):
|
||||
field = f"domains[{index}]"
|
||||
domain = require_object(value, field)
|
||||
name = require_identifier(domain.get("name"), f"{field}.name")
|
||||
if name in names:
|
||||
raise ValueError(f"domain {name} is duplicated")
|
||||
names.add(name)
|
||||
require_identifier(domain.get("clock"), f"{field}.clock")
|
||||
require_identifier(domain.get("reset"), f"{field}.reset")
|
||||
require_identifier(domain.get("reset_source"), f"{field}.reset_source")
|
||||
primitive = require_string(domain.get("reset_primitive"), f"{field}.reset_primitive")
|
||||
if primitive not in RESET_PRIMITIVES:
|
||||
raise ValueError(f"{field}.reset_primitive is not supported")
|
||||
path = root / require_string(domain.get("path"), f"{field}.path")
|
||||
instance = require_identifier(domain.get("instance"), f"{field}.instance")
|
||||
require_instance(path, primitive, instance, field)
|
||||
|
||||
raw_crossings = document.get("crossings")
|
||||
if not isinstance(raw_crossings, list):
|
||||
raise ValueError("crossings must be a list")
|
||||
crossing_names: set[str] = set()
|
||||
for index, value in enumerate(raw_crossings):
|
||||
field = f"crossings[{index}]"
|
||||
crossing = require_object(value, field)
|
||||
name = require_identifier(crossing.get("name"), f"{field}.name")
|
||||
if name in crossing_names:
|
||||
raise ValueError(f"crossing {name} is duplicated")
|
||||
crossing_names.add(name)
|
||||
source = require_identifier(crossing.get("source"), f"{field}.source")
|
||||
destination = require_identifier(crossing.get("destination"), f"{field}.destination")
|
||||
if source not in names or destination not in names:
|
||||
raise ValueError(f"{field} references an unknown domain")
|
||||
if source == destination:
|
||||
raise ValueError(f"{field} must cross between distinct domains")
|
||||
primitive = require_string(crossing.get("primitive"), f"{field}.primitive")
|
||||
if primitive not in CDC_PRIMITIVES:
|
||||
raise ValueError(f"{field}.primitive is not an approved CDC primitive")
|
||||
path = root / require_string(crossing.get("path"), f"{field}.path")
|
||||
instance = require_identifier(crossing.get("instance"), f"{field}.instance")
|
||||
require_instance(path, primitive, instance, field)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--map", required=True, type=Path)
|
||||
parser.add_argument("--root", required=True, type=Path)
|
||||
arguments = parser.parse_args()
|
||||
try:
|
||||
validate(arguments.map, arguments.root)
|
||||
except (OSError, ValueError, json.JSONDecodeError) as error:
|
||||
parser.error(str(error))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+7
-4
@@ -21,13 +21,13 @@ PR_COMMANDS = (
|
||||
("configs/ci/hazard3-rv32im-ihp130-ip-mdd-shell.mk", ("firmware",)),
|
||||
(
|
||||
"configs/ci/hazard3-rv32im-ihp130-ip-mdd.mk",
|
||||
("SIMU=VERILATOR", "firmware", "sim"),
|
||||
("SIMU=VERILATOR", "HAVE_SVA=YES", "firmware", "sim"),
|
||||
),
|
||||
(
|
||||
"configs/ci/mdd-rv32im-ihp130.mk",
|
||||
("SIMU=VERILATOR", "firmware", "sim"),
|
||||
("SIMU=VERILATOR", "HAVE_SVA=YES", "firmware", "sim"),
|
||||
),
|
||||
("configs/ci/hazard3-rv32im-ihp130.mk", ("SIMU=VERILATOR", "sim")),
|
||||
("configs/ci/hazard3-rv32im-ihp130.mk", ("SIMU=VERILATOR", "HAVE_SVA=YES", "sim")),
|
||||
(
|
||||
"configs/ci/hazard3-rv32im-ihp130.mk",
|
||||
("SIMU=IVERILOG", "RTL_SIM_TIMEOUT=5200000", "sim-asm"),
|
||||
@@ -46,7 +46,10 @@ PR_COMMANDS = (
|
||||
("configs/ci/hazard3-rv32im-ihp130.mk", ("STA=OPENSTA", "sta")),
|
||||
)
|
||||
NIGHTLY_COMMANDS = PR_COMMANDS + (
|
||||
("configs/nightly/picorv32-rv32im-ihp130.mk", ("SIMU=VERILATOR", "firmware", "sim")),
|
||||
(
|
||||
"configs/nightly/picorv32-rv32im-ihp130.mk",
|
||||
("SIMU=VERILATOR", "HAVE_SVA=YES", "firmware", "sim"),
|
||||
),
|
||||
(
|
||||
"configs/nightly/picorv32-rv32im-ihp130.mk",
|
||||
("SIMU=IVERILOG", "RTL_SIM_TIMEOUT=5200000", "sim-asm"),
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Tests for the root clock/reset and CDC inventory checker."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DOMAIN_MAP = ROOT / "rtl/mini/integration/clock_reset_domains.json"
|
||||
CHECKER = ROOT / "scripts/check_clock_reset_domains.py"
|
||||
|
||||
|
||||
def check(domain_map: Path) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
[sys.executable, str(CHECKER), "--map", str(domain_map), "--root", str(ROOT)],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
|
||||
def write_invalid_map(tmp_path: Path, document: dict[str, object]) -> Path:
|
||||
path = tmp_path / "clock_reset_domains.json"
|
||||
path.write_text(json.dumps(document), encoding="utf-8")
|
||||
return path
|
||||
|
||||
|
||||
def test_clock_reset_domain_inventory_matches_the_rcu() -> None:
|
||||
result = check(DOMAIN_MAP)
|
||||
assert result.returncode == 0, result.stderr
|
||||
|
||||
|
||||
def test_clock_reset_domain_inventory_rejects_unknown_domain_and_instance(tmp_path: Path) -> None:
|
||||
document = json.loads(DOMAIN_MAP.read_text(encoding="utf-8"))
|
||||
document["crossings"][0]["destination"] = "unknown"
|
||||
result = check(write_invalid_map(tmp_path, document))
|
||||
assert result.returncode != 0
|
||||
assert "references an unknown domain" in result.stderr
|
||||
|
||||
document = json.loads(DOMAIN_MAP.read_text(encoding="utf-8"))
|
||||
document["domains"][0]["instance"] = "u_missing_rst_sync"
|
||||
result = check(write_invalid_map(tmp_path, document))
|
||||
assert result.returncode != 0
|
||||
assert "does not declare rst_sync instance" in result.stderr
|
||||
@@ -306,6 +306,7 @@ def test_verilator_simulations_use_uniform_timeout() -> None:
|
||||
for _, values in regression_commands:
|
||||
if "SIMU=VERILATOR" in values:
|
||||
assert not any(value.startswith("SOC_SIM_TIME=") for value in values)
|
||||
assert "HAVE_SVA=YES" in values
|
||||
|
||||
|
||||
def test_run_flow_writes_structured_result(tmp_path: Path) -> None:
|
||||
|
||||
@@ -16,7 +16,7 @@ MEMORY_MAP = ROOT / "rtl/mini/address_map/memory_map.json"
|
||||
MEMORY_MAP_GENERATOR = ROOT / "rtl/mini/address_map/generate_memory_map.py"
|
||||
|
||||
|
||||
def generate(output_dir: Path) -> None:
|
||||
def generate(output_dir: Path, ip: str = "NONE") -> None:
|
||||
subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
@@ -27,12 +27,14 @@ def generate(output_dir: Path) -> None:
|
||||
str(MEMORY_MAP),
|
||||
"--output-dir",
|
||||
str(output_dir),
|
||||
"--ip",
|
||||
ip,
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
def validate(topology: Path) -> subprocess.CompletedProcess[str]:
|
||||
def validate(topology: Path, ip: str = "NONE") -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
@@ -41,6 +43,8 @@ def validate(topology: Path) -> subprocess.CompletedProcess[str]:
|
||||
str(topology),
|
||||
"--memory-map",
|
||||
str(MEMORY_MAP),
|
||||
"--ip",
|
||||
ip,
|
||||
"--check",
|
||||
],
|
||||
text=True,
|
||||
@@ -54,12 +58,16 @@ def write_invalid_topology(tmp_path: Path, document: dict[str, object]) -> Path:
|
||||
return path
|
||||
|
||||
|
||||
def test_topology_generates_complete_native_and_gpio_bindings(tmp_path: Path) -> None:
|
||||
def test_topology_generates_complete_native_apb_and_gpio_bindings(tmp_path: Path) -> None:
|
||||
generate(tmp_path)
|
||||
|
||||
interfaces = (tmp_path / "rtl/soc_nmi_interfaces.svh").read_text(encoding="utf-8")
|
||||
routes = (tmp_path / "rtl/soc_nmi_routes.svh").read_text(encoding="utf-8")
|
||||
gpio = (tmp_path / "rtl/soc_gpio_alt_bindings.svh").read_text(encoding="utf-8")
|
||||
apb_interfaces = (tmp_path / "rtl/soc_apb_interfaces.svh").read_text(encoding="utf-8")
|
||||
apb_declarations = (tmp_path / "rtl/soc_apb_declarations.svh").read_text(encoding="utf-8")
|
||||
apb_routes = (tmp_path / "rtl/soc_apb_request_routes.svh").read_text(encoding="utf-8")
|
||||
apb_response = (tmp_path / "rtl/soc_apb_response_mux.svh").read_text(encoding="utf-8")
|
||||
filelist = (tmp_path / "soc_topology.fl").read_text(encoding="utf-8")
|
||||
|
||||
assert interfaces.count("nmi_if u_") == 18
|
||||
@@ -69,9 +77,31 @@ def test_topology_generates_complete_native_and_gpio_bindings(tmp_path: Path) ->
|
||||
assert gpio.count("// GPIO") == 64
|
||||
assert "assign u_uart1_if.rx_i = u_gpio_if.di_i[0];" in gpio
|
||||
assert "assign u_gpio_if.alt1_do_i[22] = u_psram_if.nss_o[0];" in gpio
|
||||
assert apb_interfaces.count("apb4_if u_") == 9
|
||||
assert "apb4_pure_if u_archinfo_apb_pure_if ();" in apb_interfaces
|
||||
assert "assign tmr.paddr = nmi.addr;" in apb_routes
|
||||
assert "({32{s_psel_q[8]}} & tmr.prdata)" in apb_response
|
||||
assert "localparam int NSLV = 9;" in apb_declarations
|
||||
assert filelist.startswith("+incdir+")
|
||||
|
||||
|
||||
def test_topology_adds_user_apb_only_for_the_mdd_ip(tmp_path: Path) -> None:
|
||||
none_output = tmp_path / "none"
|
||||
mdd_output = tmp_path / "mdd"
|
||||
generate(none_output)
|
||||
generate(mdd_output, ip="MDD")
|
||||
|
||||
none_interfaces = (none_output / "rtl/soc_apb_interfaces.svh").read_text(encoding="utf-8")
|
||||
mdd_interfaces = (mdd_output / "rtl/soc_apb_interfaces.svh").read_text(encoding="utf-8")
|
||||
mdd_declarations = (mdd_output / "rtl/soc_apb_declarations.svh").read_text(encoding="utf-8")
|
||||
mdd_response = (mdd_output / "rtl/soc_apb_response_mux.svh").read_text(encoding="utf-8")
|
||||
|
||||
assert "u_user_ip_apb_if" not in none_interfaces
|
||||
assert "apb4_if u_user_ip_apb_if (clk_i, rst_n_i);" in mdd_interfaces
|
||||
assert "localparam int NSLV = 10;" in mdd_declarations
|
||||
assert "s_psel_q[9]" in mdd_response
|
||||
|
||||
|
||||
def test_topology_rejects_unknown_or_non_native_regions(tmp_path: Path) -> None:
|
||||
document = json.loads(TOPOLOGY.read_text(encoding="utf-8"))
|
||||
document["native_targets"][0]["regions"] = ["UNKNOWN"]
|
||||
@@ -100,6 +130,26 @@ def test_topology_rejects_duplicate_region_and_disabled_owner(tmp_path: Path) ->
|
||||
assert "disabled but declares regions" in result.stderr
|
||||
|
||||
|
||||
def test_topology_rejects_invalid_apb_target_ownership(tmp_path: Path) -> None:
|
||||
document = json.loads(TOPOLOGY.read_text(encoding="utf-8"))
|
||||
document["apb_targets"][0]["region"] = "SRAM"
|
||||
result = validate(write_invalid_topology(tmp_path, document))
|
||||
assert result.returncode != 0
|
||||
assert "not an active APB region" in result.stderr
|
||||
|
||||
document = json.loads(TOPOLOGY.read_text(encoding="utf-8"))
|
||||
document["apb_targets"][8]["slot"] = 0
|
||||
result = validate(write_invalid_topology(tmp_path, document))
|
||||
assert result.returncode != 0
|
||||
assert "APB target slot 0 is duplicated" in result.stderr
|
||||
|
||||
document = json.loads(TOPOLOGY.read_text(encoding="utf-8"))
|
||||
document["apb_targets"][9]["requires_ip"] = None
|
||||
result = validate(write_invalid_topology(tmp_path, document), ip="MDD")
|
||||
assert result.returncode != 0
|
||||
assert "requires_ip does not match" in result.stderr
|
||||
|
||||
|
||||
def test_topology_rejects_invalid_gpio_coverage_and_expression(tmp_path: Path) -> None:
|
||||
document = json.loads(TOPOLOGY.read_text(encoding="utf-8"))
|
||||
document["gpio_alt_functions"][1]["pin"] = 0
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
"""Tests for the generated scalar user-extension bindings."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
EXTENSIONS = ROOT / "rtl/mini/integration/user_extensions.json"
|
||||
GENERATOR = ROOT / "rtl/mini/integration/generate_user_extensions.py"
|
||||
|
||||
|
||||
def generate(output_dir: Path) -> None:
|
||||
subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(GENERATOR),
|
||||
"--map",
|
||||
str(EXTENSIONS),
|
||||
"--output-dir",
|
||||
str(output_dir),
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
def validate(extensions: Path) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
[sys.executable, str(GENERATOR), "--map", str(extensions), "--check"],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
|
||||
def write_invalid_extensions(tmp_path: Path, document: dict[str, object]) -> Path:
|
||||
path = tmp_path / "invalid_extensions.json"
|
||||
path.write_text(json.dumps(document), encoding="utf-8")
|
||||
return path
|
||||
|
||||
|
||||
def test_extensions_generate_isolated_scalar_bindings(tmp_path: Path) -> None:
|
||||
generate(tmp_path)
|
||||
|
||||
core = (tmp_path / "rtl/user_core_bindings.svh").read_text(encoding="utf-8")
|
||||
ip = (tmp_path / "rtl/user_ip_bindings.svh").read_text(encoding="utf-8")
|
||||
filelist = (tmp_path / "user_extensions.fl").read_text(encoding="utf-8")
|
||||
|
||||
assert core.count("nmi_if u_user_") == 6
|
||||
assert "nmi.valid = '0;" in core
|
||||
assert "5'd6: begin" in core
|
||||
assert "user_core_design_username1 #(" in core
|
||||
assert ip.count("user_gpio_if #(`USER_GPIO_NUM)") == 2
|
||||
assert "gpio.do_o = '0;" in ip
|
||||
assert "8'd2: begin" in ip
|
||||
assert "u_user_2_apb_if.psel = apb.psel;" in ip
|
||||
assert filelist.startswith("+incdir+")
|
||||
|
||||
|
||||
def test_extensions_reject_noncontiguous_slots_and_invalid_modules(tmp_path: Path) -> None:
|
||||
document = json.loads(EXTENSIONS.read_text(encoding="utf-8"))
|
||||
document["core_targets"][2]["slot"] = 7
|
||||
result = validate(write_invalid_extensions(tmp_path, document))
|
||||
assert result.returncode != 0
|
||||
assert "slots must be contiguous from one" in result.stderr
|
||||
|
||||
document = json.loads(EXTENSIONS.read_text(encoding="utf-8"))
|
||||
document["ip_targets"][0]["module"] = "user-ip"
|
||||
result = validate(write_invalid_extensions(tmp_path, document))
|
||||
assert result.returncode != 0
|
||||
assert "must be a SystemVerilog identifier" in result.stderr
|
||||
Reference in new issue
Block a user