Commit 7cd6b35d by Thierry Moreau Committed by Tianqi Chen

[VERILOG] Generalize Buffer and Tests (#76)

* adding tvm_buffer and fifo testbench

* minor edits

* line buffer test bench

* adding double buffer tests for the tvm_buffer

* making variable consistent with  python style
parent 0f54865e
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()
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
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()
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
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()
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
// 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment