Skip to content

Instantly share code, notes, and snippets.

@zaun
Created December 23, 2024 19:25
Show Gist options
  • Save zaun/80a4f47645e35f75b8d6a75efea5c56f to your computer and use it in GitHub Desktop.
Save zaun/80a4f47645e35f75b8d6a75efea5c56f to your computer and use it in GitHub Desktop.
CPU <--> TileLink-UL interface
`timescale 1ns / 1ps
`default_nettype none
module cpu_mem_interface #(
parameter XLEN = 32, // Bus data width
parameter SLEN = 2, // Source ID length
parameter MAX_RETRIES = 3 // Maximum number of retry attempts
) (
input wire clk,
input wire reset,
// CPU Interface
input wire cpu_valid,
input wire [XLEN-1:0] cpu_address,
input wire [XLEN-1:0] cpu_wdata,
input wire [XLEN/8-1:0] cpu_wstrb,
input wire [2:0] cpu_size, // 0:byte, 1:halfword, 2:word, 3:doubleword
input wire cpu_read,
output reg [XLEN-1:0] cpu_rdata,
output reg cpu_ready, // Indicates cpu_rdata valid
output reg mem_ready, // Indicates memory operation completed when data is stable
// TileLink A Channel
output reg tl_a_valid,
input wire tl_a_ready,
output reg [2:0] tl_a_opcode, // Operation type
output reg [2:0] tl_a_param, // Parameters for operation
output reg [2:0] tl_a_size, // Log2(Bytes per beat)
output wire [SLEN-1:0] tl_a_source, // Source ID
output reg [XLEN-1:0] tl_a_address, // Address
output reg [XLEN/8-1:0] tl_a_mask, // Write byte mask
output reg [XLEN-1:0] tl_a_data, // Data for write ops
// TileLink D Channel
input wire tl_d_valid,
output reg tl_d_ready,
input wire [2:0] tl_d_opcode, // Response type
input wire [1:0] tl_d_param, // Response params
input wire [2:0] tl_d_size,
input wire [SLEN-1:0] tl_d_source,
input wire [XLEN-1:0] tl_d_data,
input wire tl_d_corrupt,
input wire tl_d_denied,
// Non-Maskable IRQs (Edge-Triggered)
output reg nmi_denied,
output reg nmi_corrupt
);
// Local parameters for TileLink opcodes
localparam GET_OPCODE = 3'b100; // Get (Read)
localparam PUT_FULL_DATA_OPCODE = 3'b000; // PutFullData (Write)
localparam DEFAULT_PARAM = 3'b000; // Default TL param
// FSM States
typedef enum logic [2:0] {
IDLE,
SEND_REQ,
WAIT_RESP,
WRITE_RDATA,
COMPLETE
} state_t;
state_t current_state, next_state;
// Registers to hold CPU request data
reg [XLEN-1:0] req_address;
reg [XLEN-1:0] req_wdata;
reg [7:0] req_wstrb;
reg [2:0] req_size;
reg req_read;
// Retry mechanism
reg [1:0] retry_count;
// NMI states
reg do_nmi_retry_max;
reg do_nmi_denied;
reg do_nmi_corrupt;
// Read data hold
reg [XLEN-1:0] read_data_hold;
// Assign fixed source ID since only one request is handled at a time
assign tl_a_source = {SLEN{1'b0}};
// Initialize all registers on reset
always_ff @(posedge clk or posedge reset) begin
if (reset) begin
current_state <= IDLE;
tl_a_valid <= 1'b0;
tl_a_opcode <= 3'b000;
tl_a_param <= 3'b000;
tl_a_size <= 3'b000;
tl_a_address <= {XLEN{1'b0}};
tl_a_mask <= 8'h00;
tl_a_data <= {XLEN{1'b0}};
tl_d_ready <= 1'b0;
cpu_rdata <= {XLEN{1'b0}};
cpu_ready <= 1'b0;
mem_ready <= 1'b0;
retry_count <= 2'd0;
do_nmi_retry_max <= 1'b0;
do_nmi_denied <= 1'b0;
do_nmi_corrupt <= 1'b0;
nmi_denied <= 1'b0;
nmi_corrupt <= 1'b0;
read_data_hold <= {XLEN{1'b0}};
end else begin
// Default assignments
tl_a_valid <= 1'b0;
tl_d_ready <= 1'b0;
cpu_ready <= 1'b0;
mem_ready <= 1'b0;
do_nmi_retry_max <= 1'b0;
do_nmi_denied <= 1'b0;
do_nmi_corrupt <= 1'b0;
case (current_state)
IDLE: begin
if (cpu_valid) begin
// Capture CPU request
req_address <= cpu_address;
req_wdata <= cpu_wdata;
req_wstrb <= cpu_wstrb;
req_size <= cpu_size;
req_read <= cpu_read;
// Prepare TileLink A Channel signals
tl_a_opcode <= cpu_read ? GET_OPCODE : PUT_FULL_DATA_OPCODE;
tl_a_param <= DEFAULT_PARAM;
tl_a_size <= cpu_size;
tl_a_address <= cpu_address;
tl_a_mask <= cpu_wstrb;
tl_a_data <= cpu_wdata;
tl_a_valid <= 1'b1;
`ifdef LOG $display("[cpu_mem_interface] Time %0t: Captured CPU request - Read: %0d, Address: 0x%h, Size: %0d",
$time, cpu_read, cpu_address, cpu_size); `endif
if (tl_a_ready) begin
`ifdef LOG $display("[cpu_mem_interface] Time %0t: TileLink A Channel ready, transitioning to WAIT_RESP", $time); `endif
next_state <= WAIT_RESP;
end else begin
`ifdef LOG $display("[cpu_mem_interface] Time %0t: TileLink A Channel not ready, transitioning to SEND_REQ", $time); `endif
next_state <= SEND_REQ;
end
end else begin
next_state <= IDLE;
end
end
SEND_REQ: begin
// Resend TileLink A Channel request until tl_a_ready is asserted
tl_a_valid <= 1'b1;
tl_a_opcode <= req_read ? GET_OPCODE : PUT_FULL_DATA_OPCODE;
tl_a_param <= DEFAULT_PARAM;
tl_a_size <= req_size;
tl_a_address <= req_address;
tl_a_mask <= req_wstrb;
tl_a_data <= req_wdata;
`ifdef LOG
$display("[cpu_mem_interface] Time %0t: Sending TileLink A Channel PUT_FULL_DATA request - Address: 0x%h, Data: 0x%h, Mask: 0x%h",
$time, req_read, req_address, req_wstrb);
`endif
if (tl_a_ready) begin
`ifdef LOG $display("[cpu_mem_interface] Time %0t: TileLink A Channel accepted, transitioning to WAIT_RESP", $time); `endif
next_state <= WAIT_RESP;
end else if (retry_count < MAX_RETRIES) begin
retry_count <= retry_count + 1;
`ifdef LOG $display("[cpu_mem_interface] Time %0t: TileLink A Channel not ready, retrying (%0d/%0d)",
$time, retry_count, MAX_RETRIES); `endif
next_state <= SEND_REQ;
end else begin
`ifdef LOG $display("[cpu_mem_interface] Time %0t: TileLink A Channel failed after max retries, transitioning to WRITE_RDATA", $time); `endif
do_nmi_retry_max <= 1'b1;
// mem_ready <= 1'b1;
// cpu_ready <= 1'b1;
read_data_hold <= {XLEN{1'b0}};
next_state <= WRITE_RDATA;
end
end
WAIT_RESP: begin
if (tl_d_valid) begin
// Acknowledge the response
tl_d_ready <= 1'b1;
`ifdef LOG $display("[cpu_mem_interface] Time %0t: Received TileLink D Channel response - Opcode: %0b, Data: 0x%h",
$time, tl_d_opcode, tl_d_data); `endif
if (tl_d_denied || tl_d_corrupt) begin
if (retry_count < MAX_RETRIES) begin
retry_count <= retry_count + 1;
`ifdef LOG $display("[cpu_mem_interface] Time %0t: Response denied/corrupt, retrying (%0d/%0d)",
$time, retry_count, MAX_RETRIES); `endif
next_state <= SEND_REQ;
end else begin
`ifdef LOG $display("[cpu_mem_interface] Time %0t: Response denied/corrupt after max retries, transitioning to WRITE_RDATA", $time); `endif
read_data_hold <= {XLEN{1'b0}};
if (tl_d_denied) do_nmi_denied <= 1'b1;
if (tl_d_corrupt) do_nmi_corrupt <= 1'b1;
next_state <= WRITE_RDATA;
end
end else begin
// Successful response
if (req_read) begin
case (req_size)
3'b000: read_data_hold <= { {(XLEN-8){1'b0}}, tl_d_data[7:0] };
3'b001: read_data_hold <= { {(XLEN-16){1'b0}}, tl_d_data[7:0], tl_d_data[15:8] };
3'b010: read_data_hold <= { {(XLEN-32){1'b0}}, tl_d_data[7:0], tl_d_data[15:8], tl_d_data[23:16], tl_d_data[31:24] };
3'b011: read_data_hold <= { tl_d_data[7:0], tl_d_data[15:8], tl_d_data[23:16], tl_d_data[31:24],
tl_d_data[39:32], tl_d_data[47:40], tl_d_data[55:48], tl_d_data[63:56] };
default: read_data_hold <= {XLEN{1'b0}}; // For simplicity, handle only up to word
endcase
`ifdef LOG $display("[cpu_mem_interface] Time %0t: Read data received - 0x%h", $time, tl_d_data); `endif
end else begin
`ifdef LOG $display("[cpu_mem_interface] Time %0t: Write operation acknowledged", $time); `endif
end
retry_count <= 2'd0;
next_state <= WRITE_RDATA;
end
end else begin
// Waiting for D Channel response
next_state <= WAIT_RESP;
end
end
WRITE_RDATA: begin
`ifdef LOG $display("[cpu_mem_interface] Time %0t: Assigning read data to CPU - 0x%h nmi_denied=%0d nmi_corrupt=%0d", $time, read_data_hold, do_nmi_denied || do_nmi_retry_max, do_nmi_corrupt); `endif
if (req_read) begin
cpu_rdata <= read_data_hold;
end else begin
cpu_rdata <= {XLEN{1'b0}};
end
if (do_nmi_denied || do_nmi_retry_max) nmi_denied <= 1'b1;
if (do_nmi_corrupt) nmi_corrupt <= 1'b1;
next_state <= COMPLETE;
end
COMPLETE: begin
`ifdef LOG $display("[cpu_mem_interface] Time %0t: Memory operation complete, ready signals asserted", $time); `endif
cpu_ready <= 1'b1;
mem_ready <= 1'b1;
nmi_denied <= 1'b0;
nmi_corrupt <= 1'b0;
do_nmi_retry_max <= 1'b0;
do_nmi_denied <= 1'b0;
do_nmi_corrupt <= 1'b0;
next_state <= IDLE;
end
default: begin
next_state <= IDLE;
end
endcase
end
end
// FSM State Transition
always_ff @(posedge clk or posedge reset) begin
if (reset) begin
current_state <= IDLE;
end else begin
current_state <= next_state;
end
end
endmodule
`timescale 1ns / 1ps
`default_nettype none
module mock_memory #(
parameter XLEN = 32,
parameter SLEN = 2,
parameter MEM_SIZE = 2048
) (
input wire clk,
input wire reset,
// TileLink A Channel
input wire tl_a_valid,
output reg tl_a_ready,
input wire [2:0] tl_a_opcode,
input wire [2:0] tl_a_param,
input wire [2:0] tl_a_size,
input wire [SLEN-1:0] tl_a_source,
input wire [XLEN-1:0] tl_a_address,
input wire [XLEN/8-1:0] tl_a_mask,
input wire [XLEN-1:0] tl_a_data,
// TileLink D Channel
output reg tl_d_valid,
input wire tl_d_ready,
output reg [2:0] tl_d_opcode,
output reg [1:0] tl_d_param,
output reg [2:0] tl_d_size,
output reg [SLEN-1:0] tl_d_source,
output reg [XLEN-1:0] tl_d_data,
output reg tl_d_corrupt,
output reg tl_d_denied
`ifdef DEBUG
// Debug inputs
,input wire dbg_wait
,input wire [XLEN-1:0] dbg_corrupt_read_address
,input wire [XLEN-1:0] dbg_denied_read_address
,input wire [XLEN-1:0] dbg_corrupt_write_address
,input wire [XLEN-1:0] dbg_denied_write_address
`endif
);
// Memory array
reg [7:0] memory [0:MEM_SIZE-1];
initial begin
integer i;
for (i=0; i<MEM_SIZE; i=i+1) begin
memory[i] = 8'h00;
end
@(posedge clk);
end
// Local parameters
localparam [2:0] TL_ACCESS_ACK = 3'b000;
localparam [2:0] TL_ACCESS_ACK_DATA = 3'b010;
localparam [2:0] PUT_FULL_DATA_OPCODE = 3'b000;
localparam [2:0] GET_OPCODE = 3'b100;
localparam [2:0] TL_ACCESS_ACK_DATA_CORRUPT = 3'b101;
localparam [2:0] TL_ACCESS_ACK_ERROR = 3'b111;
// States
typedef enum logic [1:0] {
IDLE,
WAIT_CYCLE,
RESPOND
} mem_state_t;
mem_state_t state;
// Registers to hold request info
reg [XLEN-1:0] req_address;
reg [2:0] req_size;
reg req_read;
reg [SLEN-1:0] req_source;
reg [XLEN/8-1:0] req_wstrb;
reg [XLEN-1:0] req_wdata;
// Registers to hold computed response data before asserting tl_d_valid
reg [XLEN-1:0] resp_data;
reg [2:0] resp_opcode;
reg [1:0] resp_param;
reg [2:0] resp_size;
reg [SLEN-1:0] resp_source;
reg resp_denied;
reg resp_corrupt;
// Capture A-Channel request
always @(posedge clk or posedge reset) begin
if (reset) begin
state <= IDLE;
tl_a_ready <= 1'b0;
tl_d_valid <= 1'b0;
tl_d_opcode <= 3'b000;
tl_d_param <= 2'b00;
tl_d_size <= 3'b000;
tl_d_source <= {SLEN{1'b0}};
tl_d_data <= {XLEN{1'b0}};
tl_d_corrupt <= 1'b0;
tl_d_denied <= 1'b0;
`ifdef DEBUG
end else if (dbg_wait == 1) begin
// Do nothing
`endif
end else begin
// Defaults
tl_a_ready <= (state == IDLE);
tl_d_valid <= (state == RESPOND);
case (state)
IDLE: begin
if (tl_a_valid && tl_a_ready) begin
// Capture request
req_address <= tl_a_address;
req_size <= tl_a_size;
req_read <= (tl_a_opcode == GET_OPCODE);
req_source <= tl_a_source;
req_wstrb <= tl_a_mask;
req_wdata <= tl_a_data;
`ifdef LOG $display("[mock_memory] time %0t: IDLE tl_a_address=%0h", $time, tl_a_address); `endif
state <= WAIT_CYCLE;
end
end
WAIT_CYCLE: begin
// Initialize response flags
resp_denied = 1'b0;
resp_corrupt = 1'b0;
resp_param = 2'b00;
resp_source = req_source;
// Check for debug conditions
`ifdef DEBUG
if (req_read) begin
// For read requests
if (req_address == dbg_corrupt_read_address) begin
resp_corrupt = 1'b1;
`ifdef LOG $display("[mock_memory] Corrupt read at address %h", req_address); `endif
end
if (req_address == dbg_denied_read_address) begin
resp_denied = 1'b1;
`ifdef LOG $display("[mock_memory] Denied read at address %h", req_address); `endif
end
end else begin
// For write requests
if (req_address == dbg_corrupt_write_address) begin
resp_corrupt = 1'b1;
`ifdef LOG $display("[mock_memory] Corrupt write at address %h", req_address); `endif
end
if (req_address == dbg_denied_write_address) begin
resp_denied = 1'b1;
`ifdef LOG $display("[mock_memory] Denied write at address %h", req_address); `endif
end
end
`endif
// If denied or corrupted, set response accordingly
if (resp_denied) begin
resp_opcode = TL_ACCESS_ACK_ERROR;
resp_data = {XLEN{1'b0}};
end else if (resp_corrupt) begin
resp_opcode = TL_ACCESS_ACK_DATA_CORRUPT;
// Optionally, set resp_data to a corrupted value
// For demonstration, flipping the LSB
resp_data = req_read ? resp_data ^ 32'h00000001 : {XLEN{1'b1}};
end else begin
// Handle normal read or write
if (req_read) begin
// Handle read
case (req_size)
3'b000: begin
// Byte
resp_data = {{(XLEN-8){1'b0}}, memory[req_address]};
resp_opcode = TL_ACCESS_ACK_DATA;
end
3'b001: begin
// Halfword
resp_data = {{(XLEN-16){1'b0}}, memory[req_address], memory[req_address + 1]};
resp_opcode = TL_ACCESS_ACK_DATA;
end
3'b010: begin
// Word
resp_data = {{(XLEN-32){1'b0}}, memory[req_address], memory[req_address + 1], memory[req_address + 2], memory[req_address + 3]};
resp_opcode = TL_ACCESS_ACK_DATA;
end
3'b011: begin
// Double-word
if (XLEN >= 64) begin
resp_data = {memory[req_address], memory[req_address + 1],
memory[req_address + 2], memory[req_address + 3],
memory[req_address + 4], memory[req_address + 5],
memory[req_address + 6], memory[req_address + 7]};
resp_opcode = TL_ACCESS_ACK_DATA;
end else begin
resp_data = {(XLEN){1'b0}};
resp_opcode = TL_ACCESS_ACK_DATA;
end
end
3'b100: if (XLEN >= 128) begin
resp_data = {memory[req_address], memory[req_address + 1],
memory[req_address + 2], memory[req_address + 3],
memory[req_address + 4], memory[req_address + 5],
memory[req_address + 6], memory[req_address + 7],
memory[req_address + 8], memory[req_address + 9],
memory[req_address + 10], memory[req_address + 11],
memory[req_address + 12], memory[req_address + 13],
memory[req_address + 14], memory[req_address + 15]};
resp_opcode = TL_ACCESS_ACK_DATA;
end
default: begin
resp_data = {(XLEN){1'b0}};
resp_opcode = TL_ACCESS_ACK_DATA;
end
endcase
`ifdef LOG $display("[mock_memory] Time %0t: /WAIT_CYCLE/ READ req_address=%0h, resp_data=%0h", $time, req_address, resp_data); `endif
end else begin
// Handle write operations based on store size
case (req_size)
3'b000: begin // Store Byte (SB)
if (req_wstrb[0] || req_wstrb[1] || req_wstrb[2] || req_wstrb[3]) begin
memory[req_address] <= req_wdata[7:0];
end else if ((req_wstrb[0] + req_wstrb[1] + req_wstrb[2] + req_wstrb[3]) != 0) begin
resp_denied = 1'b1;
end
if (XLEN >=64 ) begin
if (req_wstrb[4] || req_wstrb[5] || req_wstrb[6] || req_wstrb[7]) begin
memory[req_address] <= req_wdata[7:0];
end else if ((req_wstrb[4] + req_wstrb[5] + req_wstrb[6] + req_wstrb[7]) != 0) begin
resp_denied = 1'b1;
end
end
end
3'b001: begin // Store Half-Word (SH)
if ((req_wstrb[0] && req_wstrb[1]) || (req_wstrb[2] && req_wstrb[3])) begin
memory[req_address + 0] <= req_wdata[7:0];
memory[req_address + 1] <= req_wdata[15:8];
end else if (req_wstrb[0] || req_wstrb[1] || req_wstrb[2] || req_wstrb[3]) begin
resp_denied = 1'b1;
end
if (XLEN >=64 ) begin
if ((req_wstrb[4] && req_wstrb[5]) || (req_wstrb[6] && req_wstrb[7])) begin
memory[req_address + 0] <= req_wdata[7:0];
memory[req_address + 1] <= req_wdata[15:8];
end else if (req_wstrb[4] || req_wstrb[5] || req_wstrb[6] || req_wstrb[7]) begin
resp_denied = 1'b1;
end
end
end
3'b010: begin // Store Word (SW)
// Check each bit of mem_wstrb and write the word data accordingly
if (req_wstrb[0]) begin
memory[req_address + 0] <= req_wdata[7:0];
end
if (req_wstrb[1]) begin
memory[req_address + 1] <= req_wdata[15:8];
end
if (req_wstrb[2]) begin
memory[req_address + 2] <= req_wdata[23:16];
end
if (req_wstrb[3]) begin
memory[req_address + 3] <= req_wdata[31:24];
end
// if (req_wstrb[4]) begin
// memory[req_address + 0] <= req_wdata[7:0];
// end
// if (req_wstrb[5]) begin
// memory[req_address + 1] <= req_wdata[15:8];
// end
// if (req_wstrb[6]) begin
// memory[req_address + 2] <= req_wdata[23:16];
// end
// if (req_wstrb[7]) begin
// memory[req_address + 3] <= req_wdata[31:24];
// end
end
3'b011: begin // Store Double-Word (SD) - 8 bytes
// Check each bit of mem_wstrb and write the word data accordingly
// $display("%00d-bit: SD mask=%00b address=%00h data=%00h", XLEN, req_wstrb, req_address, req_wdata);
if (req_wstrb[0]) begin
memory[req_address + 0] <= req_wdata[7:0];
end
if (req_wstrb[1]) begin
memory[req_address + 1] <= req_wdata[15:8];
end
if (req_wstrb[2]) begin
memory[req_address + 2] <= req_wdata[23:16];
end
if (req_wstrb[3]) begin
memory[req_address + 3] <= req_wdata[31:24];
end
if (req_wstrb[4]) begin
memory[req_address + 4] <= req_wdata[39:32];
end
if (req_wstrb[5]) begin
memory[req_address + 5] <= req_wdata[47:40];
end
if (req_wstrb[6]) begin
memory[req_address + 6] <= req_wdata[55:48];
end
if (req_wstrb[7]) begin
memory[req_address + 7] <= req_wdata[63:56];
end
end
default: begin
end
endcase
`ifdef LOG $display("[mock_memory] Time %0t: /WAIT_CYCLE/ WRITE req_address=%0h, req_wdata=%0h", $time, req_address, req_wdata); `endif
resp_opcode = TL_ACCESS_ACK;
resp_data = {XLEN{1'b0}};
end
end
// Assign response signals
tl_d_opcode <= resp_opcode;
tl_d_param <= resp_param;
tl_d_size <= req_size;
tl_d_source <= resp_source;
tl_d_data <= resp_data;
tl_d_corrupt <= resp_corrupt;
tl_d_denied <= resp_denied;
`ifdef LOG $display("[mock_memory] Time %0t: /WAIT_CYCLE/ Computed resp_data=0x%08h resp_opcode=%0b",
$time, resp_data, resp_opcode); `endif
state <= RESPOND;
end
RESPOND: begin
// tl_d_valid is 1 here, wait for tl_d_ready handshake
if (tl_d_valid && tl_d_ready) begin
// Handshake done, go back to IDLE
tl_d_opcode <= 3'b000;
tl_d_param <= 2'b00;
tl_d_size <= 3'b000;
tl_d_source <= {SLEN{1'b0}};
tl_d_data <= {XLEN{1'b0}};
tl_d_corrupt <= 1'b0;
tl_d_denied <= 1'b0;
state <= IDLE;
end
end
default: state <= IDLE;
endcase
end
end
endmodule
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment