diff --git a/tests/verilog/test_buffer_doublebuff.py b/tests/verilog/test_buffer_doublebuff.py new file mode 100644 index 000000000000..e0439d9c984c --- /dev/null +++ b/tests/verilog/test_buffer_doublebuff.py @@ -0,0 +1,73 @@ +import tvm +import numpy as np +from tvm.addon import verilog + +def test_buffer_doublebuff(): + # Test the tvm_buffer.v module as a double buffer + # Window size is 16, buffer size is 32 + window_width = 16 + set_size = 8 + + # Find file will search root/verilog and root/tests/verilog + sess = verilog.session([ + verilog.find_file("test_buffer_doublebuff.v"), + verilog.find_file("tvm_buffer.v") + ]) + + # Get the handles by their names + rst = sess.main.rst + write_advance = sess.main.write_advance + write_addr = sess.main.write_addr + write_valid = sess.main.write_valid + write_ready = sess.main.write_ready + write_data = sess.main.write_data + read_data = sess.main.read_data + read_data_valid = sess.main.read_data_valid + + # Simulation input data + test_data = np.arange(window_width*set_size).astype('int8') + + # Initial state + rst.put_int(1) + write_advance.put_int(0) + write_addr.put_int(0) + write_valid.put_int(0) + write_data.put_int(0) + + # De-assert reset + sess.yield_until_posedge() + rst.put_int(0) + + # Leave the following signals set to true + sess.yield_until_posedge() + write_valid.put_int(1) + + # Main simulation loop + write_idx = 0 + read_idx = 0 + while read_idx < len(test_data): + # write logic + if (write_idx < len(test_data)): + write_advance.put_int(0) + if (write_ready.get_int()): + write_data.put_int(test_data[write_idx]) + write_addr.put_int(write_idx%window_width) + if (write_idx%window_width==window_width-1): + write_advance.put_int(1) + write_idx += 1 + else: + write_advance.put_int(0) + write_valid.put_int(0) + + # correctness checks + if (read_data_valid.get_int()): + assert(read_data.get_int()==test_data[read_idx]) + # print "{} {}".format(read_data.get_int(), test_data[read_idx]) + read_idx += 1 + + # step + sess.yield_until_posedge() + + +if __name__ == "__main__": + test_buffer_doublebuff() diff --git a/tests/verilog/test_buffer_doublebuff.v b/tests/verilog/test_buffer_doublebuff.v new file mode 100644 index 000000000000..f9a7a92d5a5c --- /dev/null +++ b/tests/verilog/test_buffer_doublebuff.v @@ -0,0 +1,107 @@ +module main(); + + // Parameters + parameter PER=10; + + // Double buffer parameters + parameter DATA_WIDTH = 8; + parameter DEPTH = 32; + parameter CNTR_WIDTH = 6; // floor(log(32)) + 1 + parameter RD_WINDOW = 16; + parameter RD_ADVANCE = 16; + parameter RD_ADDR_WIDTH = 5; // floor(log(16)) + 1 + parameter WR_WINDOW = 16; + parameter WR_ADVANCE = 16; + parameter WR_ADDR_WIDTH = 5; // floor(log(16)) + 1 + + // Clock & reset + reg clk; + reg rst; + + // Read port inputs + reg read_advance; + reg [RD_ADDR_WIDTH-1:0] read_addr; + reg read_ready; + // Write port outputs + reg write_advance; + reg [DATA_WIDTH-1:0] write_data; + reg [WR_ADDR_WIDTH-1:0] write_addr; + reg write_valid; + + // Outputs + wire [DATA_WIDTH-1:0] read_data; + wire read_valid; + wire write_ready; + wire [CNTR_WIDTH-1:0] status_counter; + + // Module instantiation + tvm_buffer #( + .DATA_WIDTH(DATA_WIDTH), + .DEPTH(DEPTH), + .CNTR_WIDTH(CNTR_WIDTH), + .RD_WINDOW(RD_WINDOW), + .RD_ADVANCE(RD_ADVANCE), + .RD_ADDR_WIDTH(RD_ADDR_WIDTH), + .WR_WINDOW(WR_WINDOW), + .WR_ADVANCE(WR_ADVANCE), + .WR_ADDR_WIDTH(WR_ADDR_WIDTH) + ) uut ( + .clk(clk), + .rst(rst), + .read_advance(read_advance), + .read_data(read_data), + .read_addr(read_addr), + .read_ready(read_ready), + .read_valid(read_valid), + .write_advance(write_advance), + .write_data(write_data), + .write_addr(write_addr), + .write_ready(write_ready), + .write_valid(write_valid), + .status_counter(status_counter) + ); + + // clock generation + always begin + #(PER/2) clk =~ clk; + end + + // read logic + always @(posedge clk) begin + if (rst) begin + read_advance <= 0; + read_addr <= 0; + read_ready <= 0; + end else begin + if (read_valid) begin + read_ready <= 1; + end else begin + read_ready <= 0; + end + if (read_addr%RD_WINDOW==RD_WINDOW-2) begin + read_advance <= 1; + end else begin + read_advance <= 0; + end + if (read_ready) begin + read_addr <= (read_addr+1) % WR_WINDOW; + end else begin + read_addr <= read_addr % WR_WINDOW; + end + end + end + + // read_data_valid logic + reg read_data_valid; + always @(posedge clk) begin + if (rst) + read_data_valid <= 0; + else + read_data_valid <= read_ready; + end + + initial begin + // This will allow tvm session to be called every cycle. + $tvm_session(clk); + end +endmodule diff --git a/tests/verilog/test_buffer_fifo.py b/tests/verilog/test_buffer_fifo.py new file mode 100644 index 000000000000..3255ceafb0de --- /dev/null +++ b/tests/verilog/test_buffer_fifo.py @@ -0,0 +1,53 @@ +import tvm +import numpy as np +from tvm.addon import verilog + +def test_buffer_fifo(): + # Test the tvm_buffer.v module as a fifo + + # Find file will search root/verilog and root/tests/verilog + sess = verilog.session([ + verilog.find_file("test_buffer_fifo.v"), + verilog.find_file("tvm_buffer.v") + ]) + + # Get the handles by their names + rst = sess.main.rst + enq = sess.main.enq + write_data = sess.main.write_data + read_data = sess.main.read_data + read_data_valid = sess.main.read_data_valid + + # Simulation input data + test_data = np.arange(16).astype('int8') + + # Initial state + rst.put_int(1) + enq.put_int(0) + write_data.put_int(0) + + # De-assert reset + sess.yield_until_posedge() + rst.put_int(0) + + # Main simulation loop + read_idx = 0 + write_idx = 0 + while read_idx < len(test_data): + # write logic + if (write_idx < len(test_data)): + enq.put_int(1) + write_data.put_int(write_idx) + write_idx += 1 + else: + enq.put_int(0) + # read logic + if (read_data_valid.get_int()): + assert(read_data.get_int()==test_data[read_idx]) + read_idx += 1 + # step + sess.yield_until_posedge() + + +if __name__ == "__main__": + test_buffer_fifo() diff --git a/tests/verilog/test_buffer_fifo.v b/tests/verilog/test_buffer_fifo.v new file mode 100644 index 000000000000..a20f77d4b042 --- /dev/null +++ b/tests/verilog/test_buffer_fifo.v @@ -0,0 +1,89 @@ +module main(); + + // Parameters + parameter PER=10; + + // FIFO parameters + parameter DATA_WIDTH = 8; + parameter DEPTH = 32; + parameter CNTR_WIDTH = 6; // floor(log(32)) + 1 + parameter RD_WINDOW = 1; + parameter RD_ADVANCE = 1; + parameter RD_ADDR_WIDTH = 1; + parameter WR_WINDOW = 1; + parameter WR_ADVANCE = 1; + parameter WR_ADDR_WIDTH = 1; + + // Clock & reset + reg clk; + reg rst; + + // Module inputs + reg [DATA_WIDTH-1:0] write_data; + // FIFO interface abstraction: + // Connect deq to read_advance and read_ready + // Connect enq to write_advance and write_valid + // Set read_addr and write_addr to 0 + reg deq; + reg enq; + + // Module outputs + wire [DATA_WIDTH-1:0] read_data; + wire read_valid; + wire write_ready; + wire [CNTR_WIDTH-1:0] status_counter; + + // Module instantiation + tvm_buffer #( + .DATA_WIDTH(DATA_WIDTH), + .DEPTH(DEPTH), + .CNTR_WIDTH(CNTR_WIDTH), + .RD_WINDOW(RD_WINDOW), + .RD_ADVANCE(RD_ADVANCE), + .RD_ADDR_WIDTH(RD_ADDR_WIDTH), + .WR_WINDOW(WR_WINDOW), + .WR_ADVANCE(WR_ADVANCE), + .WR_ADDR_WIDTH(WR_ADDR_WIDTH) + ) uut ( + .clk(clk), + .rst(rst), + .read_advance(deq), + .read_addr({RD_ADDR_WIDTH{1'b0}}), + .read_ready(deq), + .read_valid(read_valid), + .read_data(read_data), + .write_advance(enq), + .write_addr({WR_ADDR_WIDTH{1'b0}}), + .write_ready(write_ready), + .write_valid(enq), + .write_data(write_data), + .status_counter(status_counter) + ); + + // clock generation + always begin + #(PER/2) clk =~ clk; + end + + // fifo read logic + always @(posedge clk) begin + if (rst) + deq <= 0; + else + deq <= read_valid; + end + + // read_data_valid logic + reg read_data_valid; + always @(posedge clk) begin + if (rst) + read_data_valid <= 0; + else + read_data_valid <= deq; + end + + initial begin + // This will allow tvm session to be called every cycle. + $tvm_session(clk); + end +endmodule diff --git a/tests/verilog/test_buffer_linebuff.py b/tests/verilog/test_buffer_linebuff.py new file mode 100644 index 000000000000..da01f3fc0cc1 --- /dev/null +++ b/tests/verilog/test_buffer_linebuff.py @@ -0,0 +1,74 @@ +import tvm +import numpy as np +from tvm.addon import verilog + +def test_buffer_linebuff(): + # Test the tvm_buffer.v module as a line buffer + # Window is 8x8, kernel is 3x3 + window_width = 8 + kernel_width = 3 + + # Find file will search root/verilog and root/tests/verilog + sess = verilog.session([ + verilog.find_file("test_buffer_linebuff.v"), + verilog.find_file("tvm_buffer.v") + ]) + + # Get the handles by their names + rst = sess.main.rst + write_advance = sess.main.write_advance + write_valid = sess.main.write_valid + write_ready = sess.main.write_ready + write_data = sess.main.write_data + read_data = sess.main.read_data + read_data_valid = sess.main.read_data_valid + + # Simulation input data + test_data = np.arange(window_width*window_width).astype('int8') + + # Initial state + rst.put_int(1) + write_advance.put_int(0) + write_valid.put_int(0) + write_data.put_int(0) + + # De-assert reset + sess.yield_until_posedge() + rst.put_int(0) + + # Leave the following signals set to true + sess.yield_until_posedge() + write_advance.put_int(1) + write_valid.put_int(1) + + # Main simulation loop + write_idx = 0 + read_idx = 0 + while read_idx < (window_width-kernel_width+1)*(window_width-kernel_width+1)*kernel_width*kernel_width: + # write logic + if (write_idx < len(test_data)): + if (write_ready.get_int()): + write_data.put_int(test_data[write_idx]) + write_idx += 1 + else: + write_advance.put_int(0) + write_valid.put_int(0) + + # correctness checks + if (read_data_valid.get_int()): + # Derive convolution window indices + baseIdx = read_idx/(kernel_width*kernel_width) + offsetIdx = read_idx%(kernel_width*kernel_width) + yOffset = offsetIdx/kernel_width + xOffset = offsetIdx%kernel_width + pixIndex = baseIdx + yOffset * window_width + xOffset + assert(read_data.get_int()==test_data[pixIndex]) + # print "{} {}".format(read_data.get_int(), test_data[pixIndex]) + read_idx += 1 + + # step + sess.yield_until_posedge() + + +if __name__ == "__main__": + test_buffer_linebuff() diff --git a/tests/verilog/test_buffer_linebuff.v b/tests/verilog/test_buffer_linebuff.v new file mode 100644 index 000000000000..641947a5ed24 --- /dev/null +++ b/tests/verilog/test_buffer_linebuff.v @@ -0,0 +1,121 @@ +module main(); + + // Parameters + parameter PER=10; + + // In this example we perform a 3x3 convolution of an 8x8 input image + // Therefore the window size here is (3-1)*8+3 = 19 + parameter IMAGE_WIDTH = 8; + parameter KERNEL_WIDTH = 3; + // Line buffer parameters + parameter DATA_WIDTH = 8; + parameter DEPTH = 20; // (3-1)*8+3+1 + parameter CNTR_WIDTH = 5; // floor(log(20)) + 1 + parameter RD_WINDOW = 19; // (3-1)*8+3 + parameter RD_ADVANCE = 1; + parameter RD_ADDR_WIDTH = 5; // floor(log(19)) + 1 + parameter WR_WINDOW = 1; + parameter WR_ADVANCE = 1; + parameter WR_ADDR_WIDTH = 1; + + // Clock & reset + reg clk; + reg rst; + + // Read port inputs + reg read_advance; + reg [RD_ADDR_WIDTH-1:0] read_addr; + reg read_ready; + // Write port outputs + reg write_advance; + reg [DATA_WIDTH-1:0] write_data; + reg write_valid; + + // Outputs + wire [DATA_WIDTH-1:0] read_data; + wire read_valid; + wire write_ready; + wire [CNTR_WIDTH-1:0] status_counter; + + // Module instantiation + tvm_buffer #( + .DATA_WIDTH(DATA_WIDTH), + .DEPTH(DEPTH), + .CNTR_WIDTH(CNTR_WIDTH), + .RD_WINDOW(RD_WINDOW), + .RD_ADVANCE(RD_ADVANCE), + .RD_ADDR_WIDTH(RD_ADDR_WIDTH), + .WR_WINDOW(WR_WINDOW), + .WR_ADVANCE(WR_ADVANCE), + .WR_ADDR_WIDTH(WR_ADDR_WIDTH) + ) uut ( + .clk(clk), + .rst(rst), + .read_advance(read_advance), + .read_data(read_data), + .read_addr(read_addr), + .read_ready(read_ready), + .read_valid(read_valid), + .write_advance(write_advance), + .write_data(write_data), + .write_addr({WR_ADDR_WIDTH{1'b0}}), + .write_ready(write_ready), + .write_valid(write_valid), + .status_counter(status_counter) + ); + + // clock generation + always begin + #(PER/2) clk =~ clk; + end + + // read logic + localparam KERNEL_SIZE = KERNEL_WIDTH*KERNEL_WIDTH; + reg [3:0] read_counter; + always @(posedge clk) begin + if (rst) begin + read_counter <= KERNEL_SIZE-1; + read_advance <= 0; + read_addr <= -1; + read_ready <= 0; + end else begin + if (read_valid) begin + read_counter <= (read_counter+1)%KERNEL_SIZE; + read_ready <= 1; + // Only advance at the last inner loop iteration + if (read_counter==KERNEL_SIZE-2) begin + read_advance <= 1; + end else begin + read_advance <= 0; + end + // Read address should describe a loop + if (read_counter==KERNEL_SIZE-1) begin + read_addr <= 0; + end else if (read_counter%KERNEL_WIDTH==KERNEL_WIDTH-1) begin + read_addr <= read_addr+IMAGE_WIDTH-KERNEL_WIDTH+1; + end else begin + read_addr <= read_addr+1; + end + end else begin + read_counter <= read_counter; + read_advance <= 0; + read_addr <= read_addr; + read_ready <= 0; + end + end + end + + // read_data_valid logic + reg read_data_valid; + always @(posedge clk) begin + if (rst) + read_data_valid <= 0; + else + read_data_valid <= read_ready; + end + + initial begin + // This will allow tvm session to be called every cycle. + $tvm_session(clk); + end +endmodule diff --git a/verilog/tvm_buffer.v b/verilog/tvm_buffer.v new file mode 100644 index 000000000000..402d09b619a7 --- /dev/null +++ b/verilog/tvm_buffer.v @@ -0,0 +1,118 @@ +// Buffer used to add intermediate data buffering in channels +// +// Data within the read/write window is directly accessible via rd_addr/wr_addr. +// The read_advance/write_advance signals update the read/write data pointers by adding RD_WINDOW/WR_WINDOW. +// The status_counter indicate how many items are currently in the buffer (only registered after an advance signal is asserted). +// The ready/valid signals are used to implement a handshake protocol. +// +// Usage: create and pass instance to additional arguments of $tvm_session. + + +module tvm_buffer #( + parameter DATA_WIDTH = 256, + parameter DEPTH = 1024, + parameter CNTR_WIDTH = 10, // log base 2 of BUFF_DEPTH + parameter RD_WINDOW = 8, // set to 1 for FIFO behavior, or DEPTH for SRAM behavior + parameter RD_ADVANCE = 2, // window advance (set to 1 for FIFO behavior) + parameter RD_ADDR_WIDTH = 3, // log base 2 of RD_WINDOW + parameter WR_WINDOW = 8, // set to 1 for FIFO behavior, or DEPTH for SRAM behavior + parameter WR_ADVANCE = 2, // window advance (set to 1 for FIFO behavior) + parameter WR_ADDR_WIDTH = 3 // log base 2 of WR_WINDOW +) ( + input clk, + input rst, + // Read ports + input read_advance, // Window advance (read pointer) + input [RD_ADDR_WIDTH-1:0] read_addr, // Read address offset + input read_ready, // Read ready (dequeue) + output read_valid, // Read valid (not empty) + output [DATA_WIDTH-1:0] read_data, // Read data port + // Write ports + input write_advance, // Window advance (write pointer) + input [WR_ADDR_WIDTH-1:0] write_addr, // Write address offset + output write_ready, // Write ready (not full) + input write_valid, // Write valid (enqueue) + input [DATA_WIDTH-1:0] write_data, // Write data port + // Other outputs + output [CNTR_WIDTH-1:0] status_counter // Number of elements currently in FIFO +); + + // Outputs that need to be latched + reg read_data; + reg status_counter; + + // Internal registers (read pointer, write pointer) + reg[CNTR_WIDTH-1:0] read_ptr; + reg[CNTR_WIDTH-1:0] write_ptr; + + // RAM instance + reg [DATA_WIDTH-1:0] ram[DEPTH-1:0]; + + // Empty and full logic + assign read_valid = (status_counter>=RD_WINDOW) ? 1'b1 : 1'b0; + assign write_ready = (status_counter<(DEPTH-WR_WINDOW)) ? 1'b1 : 1'b0; + + // Counter logic (only affected by enq and deq) + always @(posedge clk) begin + // Case 1: system reset + if (rst==1'b1) begin + status_counter <= 0; + // Case 2: simultaneous write advance and read advance and deq + end else if ((write_advance && write_ready) && (read_advance && read_valid)) begin + status_counter <= status_counter + (WR_ADVANCE - RD_ADVANCE); + // Case 3: write advance + end else if (write_advance && write_ready) begin + status_counter <= status_counter + WR_ADVANCE; + // Case 4: deq + end else if (read_advance && read_valid) begin + status_counter <= status_counter - RD_ADVANCE; + // Default + end else begin + status_counter <= status_counter; + end + end + + // Output logic + always @(posedge clk) begin + if (rst==1'b1) begin + read_data <= 0; + end else begin + if(read_ready) begin + read_data <= ram[(read_ptr+read_addr)%DEPTH]; + end else begin + read_data <= read_data; + end + end + end + + // RAM writing logic + always @(posedge clk) begin + if(write_valid) begin + ram[((write_ptr+write_addr)%DEPTH)] <= write_data; + end + end + + // Read and write pointer logic + always@(posedge clk) begin + if (rst==1'b1) begin + write_ptr <= 0; + read_ptr <= 0; + end else begin + // Increment write pointer by WR_ADVANCE when asserting write_advance + // When performing a write, no need to update the write pointer + if (write_advance && write_ready) begin + write_ptr <= (write_ptr + WR_ADVANCE) % DEPTH; + end else begin + write_ptr <= write_ptr; + end + // Increment read pointer by RD_ADVANCE when asserting read_advance + // When performing a read, no need to update the read pointer + if(read_advance && read_valid) begin + read_ptr <= (read_ptr + RD_ADVANCE) % DEPTH; + end else begin + read_ptr <= read_ptr; + end + end + end + +endmodule // tvm_buffer