Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions hardware/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ find_package(CoyoteHW REQUIRED)
set(N_REGIONS 1)
set(EN_STRM 1)
set(N_STRM_AXI 4)
set(EN_MEM 1)
set(N_CARD_AXI 1)
set(FDEV_NAME u55c)

validation_checks_hw()
Expand Down
89 changes: 89 additions & 0 deletions hardware/src/hdl/buffer/stream_buffer_interfaces.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
`timescale 1ns / 1ps

import libstf::*;

`include "libstf_macros.svh"

/*
* This interface links the StreamBufferWriter and StreamBufferWriter, acting as
* a stream where tokens are shared. Each tokens represents the amount of
* bytes written by the latest card write. This is used to pause reads until
* data for a region has been fully written, so that partial data is never
* read.
*/
interface stream_buffer_link_i (
input logic clk,
input logic rst_n
);
vaddress_t vaddr;
vaddress_t size;
logic last;
logic valid;
logic ready;

task tie_off_m();
valid = 1'b0;
endtask

task tie_off_s();
ready = 1'b1;
endtask

modport m (
import tie_off_m,
output vaddr, size, last, valid,
input ready
);

modport s (
import tie_off_s,
output ready,
input vaddr, size, last, valid
);

`ifndef SYNTHESIS
`STF_ASSERT_SIGNAL_STABLE(vaddr);
`STF_ASSERT_SIGNAL_STABLE(size);
`STF_ASSERT_SIGNAL_STABLE(last);

`STF_ASSERT_NOT_UNDEFINED(valid);
`STF_ASSERT_NOT_UNDEFINED(ready);
`endif
endinterface

interface mem_read_config_i (
input logic clk,
input logic rst_n
);
vaddress_t vaddr;
data32_t size;
logic valid;
logic ready;

task tie_off_m();
valid = 1'b0;
endtask

task tie_off_s();
ready = 1'b1;
endtask

modport m (
import tie_off_m,
output vaddr, size, valid,
input ready
);

modport s (
import tie_off_s,
output ready,
input vaddr, size, valid
);

`ifndef SYNTHESIS
`STF_ASSERT_STABLE(vaddr, valid, ready);
`STF_ASSERT_STABLE(size, valid, ready);
`STF_ASSERT_NOT_UNDEFINED(valid);
`STF_ASSERT_NOT_UNDEFINED(ready);
`endif
endinterface
86 changes: 86 additions & 0 deletions hardware/src/hdl/buffer/stream_buffer_reader.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
`timescale 1ns / 1ps

import lynxTypes::*;
import libstf::*;

`include "axi_macros.svh"
`include "lynx_macros.svh"
`include "libstf_macros.svh"

/*
* NOTE: the input_data should be wired to the AXI stream where incmonig data
* will be streamed after a request has been sent via sq_rd.
* For example, in the case of card memory, it should be
* axis_card_recv[AXI_STRM_ID].
* NOTE: the TRANSFER_LENGTH_BYTES must be the same as configured in the
* writer.
*/
module StreamBufferReader #(
parameter AXI_STRM_ID = 0,
parameter TRANSFER_SIZE = TRANSFER_SIZE_BYTES
) (
input logic clk,
input logic rst_n,

metaIntf.m sq_rd,
metaIntf.s cq_rd,

stream_buffer_link_i.s link,

AXI4SR.s in,
AXI4S.m out
);

`RESET_RESYNC // Reset pipelining

mem_read_config_i mem_config (.clk(clk), .rst_n(reset_synced));

assign mem_config.vaddr = link.vaddr;
assign mem_config.size = link.size;
assign mem_config.valid = link.valid;
assign link.ready = mem_config.ready;

AXI4S inner_out (.aclk(clk), .aresetn(reset_synced));

StreamReader #(
.STRM(STRM_CARD),
.AXI_STRM_ID(AXI_STRM_ID),
.TRANSFER_LENGTH_BYTES(TRANSFER_SIZE)
) inst_stream_reader (
.clk(clk),
.rst_n(reset_synced),

.sq_rd(sq_rd),
.cq_rd(cq_rd),

.mem_config(mem_config),

.input_data(in),
.output_data(inner_out)
);

logic last_received, n_last_received;

always_ff @(posedge clk) begin
if (reset_synced == 1'b0) begin
last_received <= 1'b0;
end else begin
last_received <= n_last_received;
end
end

always_comb begin
n_last_received = last_received;

if (link.ready && link.valid) begin
n_last_received = link.last;
end
end

assign out.tdata = inner_out.tdata;
assign out.tkeep = inner_out.tkeep;
assign out.tlast = inner_out.tlast && last_received;
assign out.tvalid = inner_out.tvalid;
assign inner_out.tready = out.tready;

endmodule
141 changes: 141 additions & 0 deletions hardware/src/hdl/buffer/stream_buffer_writer.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
`timescale 1ns / 1ps

import libstf::*;
import lynxTypes::*;

`include "axi_macros.svh"
`include "libstf_macros.svh"

/*
* NOTE: out must be wired to axis_card_send[AXI_STRM_ID].
*/
module StreamBufferWriter #(
parameter AXI_STRM_ID = 0,
parameter TRANSFER_SIZE = TRANSFER_SIZE_BYTES,
// NOTE: this is the number of tranfers that will be allocated at a time
// when more memory is provided to the underlying StreamWriter.
parameter TRANFERS_PER_ALLOCATION = MAXIMUM_HOST_ALLOCATION_SIZE_BYTES / TRANSFER_SIZE
) (
input logic clk,
input logic rst_n,

metaIntf.m sq_wr,
metaIntf.s cq_wr,

AXI4S.s in,

stream_buffer_link_i.m link,
AXI4SR.m out
);

`RESET_RESYNC // Reset pipelining

localparam int ALLOCATION_BYTES = TRANFERS_PER_ALLOCATION * TRANSFER_SIZE;

// This stream is used on the host to allocate more data for the StreamWriter.
// On card memory, there's no need for allocations, new mem_config_i regions
// are provided when requested from the code below. Thus, we can just tie off
// this signal.
stream_writer_notify_i notify (.clk(clk), .rst_n(reset_synced));
mem_config_i mem_config (.clk(clk), .rst_n(reset_synced));
vaddress_t next_vaddr, next_buffer_vaddr, last_allocation_end_vaddr;

buffer_t next_buffer;
assign next_buffer.vaddr = next_vaddr;
assign next_buffer.size = TRANFERS_PER_ALLOCATION;
assign mem_config.flush_buffers = 1'b0;
assign mem_config.buffer_data = next_buffer;
assign mem_config.buffer_valid = 1'b1;

// This state machine ensures that the notification of a compled write is
// received by the consumer on the other end of the link. It also ensures that
// when the current memory allocation is exhausted, a new memory allocation is
// provided on the mem_config interface and acknowledged.
typedef enum logic {
ST_NOT_FULL,
ST_NEEDS_ALLOCATION
} state_t;
state_t state;

vaddress_t n_next_vaddr;
assign n_next_vaddr = next_vaddr + notify.size;

always_ff @(posedge clk) begin
if (reset_synced == 1'b0) begin
next_vaddr <= '0;
next_buffer_vaddr <= '0;
last_allocation_end_vaddr <= '0;

state <= ST_NEEDS_ALLOCATION;
end else begin
case (state)
ST_NOT_FULL: begin
if (notify.ready && notify.valid) begin
next_vaddr <= n_next_vaddr;

// When we receive a last, the writer is going to assume
// that we want a new allocation for the next stream. This
// is the case when sending data to the host, but not when
// sending data to the card (we don't want to waste memory
// for a new allocation, leaving the current one half-used).
if (notify.last || n_next_vaddr >= last_allocation_end_vaddr) begin
state <= ST_NEEDS_ALLOCATION;
end
end
end

ST_NEEDS_ALLOCATION: begin
if (mem_config.buffer_ready) begin
next_buffer_vaddr <= next_buffer_vaddr + ALLOCATION_BYTES;
last_allocation_end_vaddr <= next_vaddr + ALLOCATION_BYTES;

state <= ST_NOT_FULL;
end
end
endcase
end
end

always_comb begin
if (reset_synced == 1'b0) begin
notify.ready = 1'b0;
mem_config.buffer_valid = 1'b0;
end else begin
case (state)
ST_NOT_FULL: begin
notify.ready = link.ready;
mem_config.buffer_valid = 1'b0;
end

ST_NEEDS_ALLOCATION: begin
notify.ready = 1'b0;
mem_config.buffer_valid = 1'b1;
end
endcase
end

link.vaddr = next_vaddr;
link.size = notify.size;
link.last = notify.last;
link.valid = state == ST_NOT_FULL && notify.valid;
end

StreamWriter #(
.STRM(STRM_CARD),
.AXI_STRM_ID(AXI_STRM_ID),
.TRANSFER_LENGTH_BYTES(TRANSFER_SIZE)
) inst_stream_writer (
.clk(clk),
.rst_n(reset_synced),

.sq_wr(sq_wr),
.cq_wr(cq_wr),

.notify(notify),
.mem_config(mem_config),

.input_data(in),
.output_data(out)
);

endmodule
Loading