work in progress, major overhaul for design, see devlogs for details. Also added the first version of the style guide

This commit is contained in:
2025-05-29 00:18:06 -04:00
parent dac3140829
commit f61de84b4a
6 changed files with 384 additions and 90 deletions

View File

@ -1,96 +1,167 @@
module hub (
input logic rst,
input logic sys_clk,
input logic [31:0] rx_cmd, // for routing-related commands
input logic [3:0] rx_cmd_valid,
input logic [31:0] rx_byte,
input logic [3:0] rx_valid,
input logic [31:0] rx2tx_dest, // rx byte's destination
input logic [3:0] tx_ready, // if tx_byte is ready to be read
output logic [3:0] rx_ready, // if rx_byte is ready to be read
output logic [7:0] tx_src, // tell the tx where the stream is comming from
output logic [31:0] tx_byte,
output logic [3:0] tx_valid,
output logic [1:0] packet_size); // 4 states for 4 fixed packet sizes
`include <params.sv>
// IMPORTANT: interfaces are supposed to keep track of their own packet states
module hub(
input logic sys_clk,
input logic rst,
input logic [INTERFACE_CNT - 1][PACKET_ADDR_LEN - 1:0] rx_pkt_addr,
input logic [INTERFACE_CNT - 1:0][7:0] rx_byte,
input logic [INTERFACE_CNT - 1:0] rx_valid,
input logic [INTERFACE_CNT - 1:0] tx_ready,
input logic [INTERFACE_CNT - 1:0] tx_full,
input logic [INTERFACE_CNT - 1:0][PACKET_ADDR_LEN - 1:0] tx_pkt_addr,
input logic [INTERFACE_CNT - 1:0] rx_new_packet,
output logic [INTERFACE_CNT - 1:0] rx_ready,
output logic [INTERFACE_CNT - 1:0][PACKET_ADDR_LEN - 1:0] tx_queue_addr,
output logic [INTERFACE_CNT - 1:0] tx_queue_addr_valid,
output logic [INTERFACE_CNT - 1:0][7:0] tx_byte,
output logic [INTERFACE_CNT - 1:0] tx_valid);
timeunit 1ns;
timeprecision 1ps;
logic [INTERFACE_CNT - 1:0] curr_service;
logic request_new_slot;
logic [QUEUE_ADDR_LEN - 1:0] new_slot_addr;
logic free_queue_empty;
logic [QUEUE_ADDR_LEN - 1:0] empty_slot_addr;
logic [QUEUE_ADDR_LEN - 1:0] empty_slot_enqueue;
free_queue fqueue(.sys_clk(sys_clk),
.rst(rst),
.request_new_slot(request_new_slot),
.empty_slot_addr(empty_slot_addr),
.empty_slot_enqueue(empty_slot_enqueue),
.new_slot_addr(new_slot_addr),
.queue_empty(free_queue_empty));
// TBD: pre-agree on packet size
logic [INTERFACE_CNT - 1:0][MEMORY_ADDR_LEN - 1:0] rx_mem_addr;
logic [MEMORY_POOL_ADDR_LEN - 1:0] mem_read_addr;
logic [7:0] mem_read_byte;
logic [MEMORY_POOL_ADDR_LEN - 1:0] mem_write_addr;
logic mem_write_enable;
logic [7:0] mem_write_byte;
// use the round-robin strat to poll since the routing is much faster
// NOTE: To expand to more connected_devices, use a hierarchical design
logic [1:0] curr_service = 0;
logic [1:0] last_dest = 0;
// src dest byte
typedef struct {
logic [1:0] dest;
logic [7:0] payload;
} svc_buffer;
svc_buffer service_buffer [3:0];
svc_buffer curr_buffer;
assign curr_buffer = service_buffer[curr_service];
logic [3:0] in_buffer;
assign rx_ready = ~in_buffer;
always_ff @ (posedge sys_clk) begin
memory_pool mpool(.sys_clk(sys_clk),
.rst(rst),
.read_addr(mem_read_addr),
.write_addr(mem_write_addr),
.write_byte(mem_write_byte),
.write_enable(mem_write_enable),
.read_byte(mem_read_byte));
always_ff @ (posedge sys_clk or rst) begin
if (rst) begin
in_buffer <= '0;
tx_src <= '0;
tx_queue_addr <= '0;
tx_queue_addr_valid <= '0;
tx_byte <= '0;
tx_valid <= '0;
packet_size <= '0;
curr_service <= '0;
last_dest <= '0;
for (int i = 0; i < 4; i++) begin
service_buffer[i] <= '0;
end
end else begin // if (rst)
// Handle RX side logic
for (int i = 0; i < 4; i++) begin
if (rx_valid[i]) begin
if (!in_buffer[i]) begin
service_buffer[i].dest <= get_hop(rx2tx_dest, i[1:0]);
service_buffer[i].payload <= get_byte(rx_byte, i[1:0]);
in_buffer[i] <= 1;
rx_ready <= '0;
rx_mem_addr <= '0;
mem_read_addr <= '0;
mem_write_addr <= '0;
mem_write_enable <= 0;
mem_write_byte <= '0;
end else begin
// NOTE: signaled the servicing interface in the last cycle
rx_ready[curr_service] <= 0;
rx_ready[curr_service + 1] <= 1;
// IMPORTANT: interfaces should send the byte no matter what, rx_ready is to prevent sending a new byte
if (rx_valid[curr_service]) begin
// IMPORTANT: memory_write_addr is ready on the next cycle
if (rx_new_packet[curr_service]) begin
if (free_queue_empty) begin
// TODO: handle the drop logic
end else begin
request_new_slot <= 1;
rx_mem_addr[{curr_service,
MEMORY_POOL_ADDR_SHIFT'd0}
+:MEMORY_POOL_ADDR_LEN
] <= {new_slot_addr, PACKET_ADDR_LEN'd0};
mem_write_addr <= {new_slot_addr, PACKET_ADDR_LEN'd0};
end
end
end
// Handle TX side logic
if (in_buffer[curr_service] && tx_ready[curr_buffer.dest]) begin
tx_byte[{curr_buffer.dest, 3'b000} +: 8]
<= curr_buffer.payload;
tx_src[{curr_buffer.dest, 1'b0} +: 2]
<= curr_service;
in_buffer[curr_service] <= 0;
tx_valid[curr_buffer.dest] <= 1;
end
tx_valid[last_dest] <= 0;
last_dest <= service_buffer[curr_service].dest;
curr_service <= curr_service + 1;
end // else: !if(rst)
end // always_ff @ (posedge sys_clk)
end else begin // if (rx_new_packet[curr_service])
// NOTE: if memory
mem_write_addr <= mem_write_addr + 1;
request_new_slot <= 0;
end // else: !if(rx_new_packet[curr_service])
mem_write_byte <= rx_byte[{curr_service, 3'd0}+:8];
mem_write_enable <= 1;
end else // if (rx_valid[curr_service])
mem_write_enable <= 0;
end
end
endmodule // hub
function automatic logic [7:0] get_byte(input logic [31:0] byte_arr,
input logic [1:0] idx);
return byte_arr[{idx, 3'b000} +: 8];
endfunction // get_byte
// IMPORTANT: the current queue_addr is always valid unless queue_empty
// REQUIRES: hub does not request a new slot when the queue is empty
module free_queue(input logic sys_clk,
input logic rst,
input logic request_new_slot,
input logic [QUEUE_ADDR_LEN - 1:0] empty_slot_addr,
input logic empty_slot_enqueue,
output logic [QUEUE_ADDR_LEN - 1:0] new_slot_addr,
output logic queue_empty);
timeunit 1ns;
timeprecision 1ps;
// NOTE: addr 0 is alway mapped to the fabric itself and caught before this
function automatic logic [1:0] get_hop(input logic [31:0] dest_map,
input logic [1:0] idx);
case (dest_map[{idx, 3'b000} +: 8])
8'b00000001:
return 2'b00;
8'b00000010:
return 2'b01;
8'b00000011:
return 2'b10;
8'b00000100:
return 2'b11;
default:
return 0;
endcase // case (dest_map[{idx, 3'b000} +: 8])
endfunction // get_hop
logic [QUEUE_ADDR_LEN - 1:0] fqueue [QUEUE_SIZE - 1:0];
logic [QUEUE_ADDR_LEN - 1:0] head;
logic [QUEUE_ADDR_LEN - 1:0] tail;
shortint queue_size;
assign queue_empty = queue_size == 0;
initial begin
// TODO: pre-load the free queue with every slot possible
end
// IMPORTANT: rst must be held high for at least 2 sys_clk cycles
always_ff @ (posedge sys_clk or rst) begin
if (rst) begin
head <= '0;
tail <= QUEUE_ADDR_LEN'd1;
queue_size = QUEUE_SIZE;
new_slot_addr <= '0;
end else begin
if (request_new_slot) begin
head <= head + 1;
queue_size <= queue_size - 1;
end
new_slot_addr <= fqueue[head];
if (empty_slot_enqueue) begin
fqueue[tail] <= empty_slot_addr;
tail <= tail + 1;
queue_size <= queue_size + 1;
end
end
end
endmodule // free_queue
module memory_pool(input logic sys_clk,
input logic rst,
input logic [MEMORY_POOL_ADDR_LEN - 1:0] read_addr,
input logic [MEMORY_POOL_ADDR_LEN - 1:0] write_addr,
input logic [7:0] write_byte,
input logic write_enable,
output logic [7:0] read_byte);
timeunit 1ns;
timeprecision 1ps;
logic [7:0] mem_pool[MEMORY_POOL_SIZE - 1:0];
always_ff @ (posedge sys_clk or rst) begin
if (rst) begin
read_byte <= 8'hFF;
end else begin
if (write_enable)
mem_pool[write_addr] <= write_byte;
read_byte <= mem_pool[read_addr];
end
end
endmodule // memory_pool

11
fabric/src/params.sv Normal file
View File

@ -0,0 +1,11 @@
parameter int PACKET_SIZE = 64;
parameter int PACKET_ADDR_LEN = 6;
parameter int QUEUE_SIZE = 1024;
parameter int QUEUE_ADDR_LEN = 10;
parameter int MEMORY_POOL_SIZE = QUEUE_SIZE * PACKET_SIZE;
parameter int MEMORY_POOL_ADDR_LEN = QUEUE_ADDR_LEN + PACKET_ADDR_LEN;
parameter int MEMORY_POOL_ADDR_SHIFT = 4;
parameter int INTERFACE_QUEUE_SIZE = 512;
parameter int INTERFACE_QUEUE_ADDR_LEN = 9;
parameter int INTERFACE_CNT = 4;
parameter int CRC_BITS = 8;