Вот к примеру шина
wire riscv_mem_valid;
wire riscv_mem_ready;
wire [31:0] riscv_mem_addr;
wire [31:0] riscv_mem_wdata;
wire [3:0] riscv_mem_wstrb;
wire [31:0] riscv_mem_rdata;
.mem_valid (riscv_mem_valid), // out
.mem_ready (riscv_mem_ready), // in
.mem_addr (riscv_mem_addr), // out
.mem_wdata (riscv_mem_wdata), // out
.mem_wstrb (riscv_mem_wstrb), // out
.mem_rdata (riscv_mem_rdata), // in
wdata, wstrb (write strobe) идут на все блоки одновременно. valid декодируется в зависимости от адреса, и на каждый блок приходит свой valid. У блока может быть несколько входов valid, если там несколько независимых регистров, как у вас. Например cfg_valid, cnt_valid, err_valid, и т.д.
ready и rdata мультиплексируются в зависимости от того же адреса\
И у меня есть блок io_map,
где происходит это декодирование / мультиплексирование адреса
типа такого
function void io_port(input [31:0] addr, output reg valid, input ready, input [31:0] rdata);
begin
casez({mem_valid, mem_addr})
{1'b1, addr}:
begin
valid = 1'b1;
mem_ready = ready;
mem_rdata = rdata;
end
default:
valid = 1'b0;
endcase
end
endfunction
......
always_comb begin
// USB
io_port(32'b0000_0000__0000_0000__0000_0001__????_????, // 0x0000_0100..0x0000_01FF
usb_io_valid, usb_io_ready, usb_io_rdata);
// EPCQ flash memory cache
io_port(32'b0000_0000__1???_????????_????????_??00, // 0x0080_0000..0x00FF_FFFC
epcq_cache_valid, epcq_cache_ready, epcq_cache_rdata);
end