Hi everyone. I’m testing a simple copy function as following in MLIR:
module {
func.func @copy(%arg0: memref<20xi32>, %arg1: memref<20xi32>) {
affine.for %arg2 = 0 to 20 {
%0 = affine.load %arg0[%arg2] : memref<20xi32>
affine.store %0, %arg1[%arg2] : memref<20xi32>
}
return
}
}
For Lowering I used this command:
circt-opt copy.mlir -convert-affine-to-pipeline -lower-static-logic-to-calyx | circt-translate -export-calyx -o copy.futil
The intermediate calyx dialect code in MLIR is:
module attributes {calyx.entrypoint = "copy"} {
calyx.component @copy(%ext_mem0_read_data: i32 {mem = {id = 0 : i32, tag = "read_data"}}, %ext_mem0_done: i1 {mem = {id = 0 : i32, tag = "done"}}, %ext_mem1_read_data: i32 {mem = {id = 1 : i32, tag = "read_data"}}, %ext_mem1_done: i1 {mem = {id = 1 : i32, tag = "done"}}, %clk: i1 {clk}, %reset: i1 {reset}, %go: i1 {go}) -> (%ext_mem0_write_data: i32 {mem = {id = 0 : i32, tag = "write_data"}}, %ext_mem0_addr0: i5 {mem = {addr_idx = 0 : i32, id = 0 : i32, tag = "addr"}}, %ext_mem0_write_en: i1 {mem = {id = 0 : i32, tag = "write_en"}}, %ext_mem1_write_data: i32 {mem = {id = 1 : i32, tag = "write_data"}}, %ext_mem1_addr0: i5 {mem = {addr_idx = 0 : i32, id = 1 : i32, tag = "addr"}}, %ext_mem1_write_en: i1 {mem = {id = 1 : i32, tag = "write_en"}}, %done: i1 {done}) {
%c1_i32 = hw.constant 1 : i32
%c20_i32 = hw.constant 20 : i32
%c0_i32 = hw.constant 0 : i32
%true = hw.constant true
%std_slice_1.in, %std_slice_1.out = calyx.std_slice @std_slice_1 : i32, i5
%std_slice_0.in, %std_slice_0.out = calyx.std_slice @std_slice_0 : i32, i5
%std_add_0.left, %std_add_0.right, %std_add_0.out = calyx.std_add @std_add_0 : i32, i32, i32
%std_lt_0.left, %std_lt_0.right, %std_lt_0.out = calyx.std_lt @std_lt_0 : i32, i32, i1
%stage_0_register_0_reg.in, %stage_0_register_0_reg.write_en, %stage_0_register_0_reg.clk, %stage_0_register_0_reg.reset, %stage_0_register_0_reg.out, %stage_0_register_0_reg.done = calyx.register @stage_0_register_0_reg : i32, i1, i1, i1, i32, i1
%while_0_arg0_reg.in, %while_0_arg0_reg.write_en, %while_0_arg0_reg.clk, %while_0_arg0_reg.reset, %while_0_arg0_reg.out, %while_0_arg0_reg.done = calyx.register @while_0_arg0_reg : i32, i1, i1, i1, i32, i1
calyx.wires {
calyx.group @assign_while_0_init_0 {
calyx.assign %while_0_arg0_reg.in = %c0_i32 : i32
calyx.assign %while_0_arg0_reg.write_en = %true : i1
calyx.group_done %while_0_arg0_reg.done : i1
}
calyx.comb_group @bb0_0 {
calyx.assign %std_lt_0.left = %while_0_arg0_reg.out : i32
calyx.assign %std_lt_0.right = %c20_i32 : i32
}
calyx.group @bb0_1 {
calyx.assign %std_slice_1.in = %while_0_arg0_reg.out : i32
calyx.assign %ext_mem0_addr0 = %std_slice_1.out : i5
calyx.assign %stage_0_register_0_reg.in = %ext_mem0_read_data : i32
calyx.assign %stage_0_register_0_reg.write_en = %true : i1
calyx.group_done %stage_0_register_0_reg.done : i1
}
calyx.group @bb0_2 {
calyx.assign %std_add_0.left = %while_0_arg0_reg.out : i32
calyx.assign %std_add_0.right = %c1_i32 : i32
calyx.assign %while_0_arg0_reg.in = %std_add_0.out : i32
calyx.assign %while_0_arg0_reg.write_en = %true : i1
calyx.group_done %while_0_arg0_reg.done : i1
}
calyx.group @bb0_3 {
calyx.assign %std_slice_0.in = %while_0_arg0_reg.out : i32
calyx.assign %ext_mem1_addr0 = %std_slice_0.out : i5
calyx.assign %ext_mem1_write_data = %stage_0_register_0_reg.out : i32
calyx.assign %ext_mem1_write_en = %true : i1
calyx.group_done %ext_mem1_done : i1
}
}
calyx.control {
calyx.seq {
calyx.par {
calyx.enable @assign_while_0_init_0
}
calyx.par {
calyx.enable @bb0_1
calyx.enable @bb0_2
}
calyx.while %std_lt_0.out with @bb0_0 {
calyx.par {
calyx.enable @bb0_1
calyx.enable @bb0_2
calyx.enable @bb0_3
}
} {bound = 19 : i64}
calyx.par {
calyx.enable @bb0_3
}
}
}
} {toplevel}
}
And the exported calyx code is:
import "primitives/core.futil";
component copy<"toplevel"=1>(ext_mem0_read_data: 32, ext_mem0_done: 1, ext_mem1_read_data: 32, ext_mem1_done: 1, @clk clk: 1, @reset reset: 1, @go go: 1) -> (ext_mem0_write_data: 32, ext_mem0_addr0: 5, ext_mem0_write_en: 1, ext_mem1_write_data: 32, ext_mem1_addr0: 5, ext_mem1_write_en: 1, @done done: 1) {
cells {
std_slice_1 = std_slice(32, 5);
std_slice_0 = std_slice(32, 5);
std_add_0 = std_add(32);
std_lt_0 = std_lt(32);
stage_0_register_0_reg = std_reg(32);
while_0_arg0_reg = std_reg(32);
}
wires {
group assign_while_0_init_0 {
while_0_arg0_reg.in = 32'd0;
while_0_arg0_reg.write_en = 1'd1;
assign_while_0_init_0[done] = while_0_arg0_reg.done;
}
comb group bb0_0 {
std_lt_0.left = while_0_arg0_reg.out;
std_lt_0.right = 32'd20;
}
group bb0_1 {
std_slice_1.in = while_0_arg0_reg.out;
ext_mem0_addr0 = std_slice_1.out;
stage_0_register_0_reg.in = ext_mem0_read_data;
stage_0_register_0_reg.write_en = 1'd1;
bb0_1[done] = stage_0_register_0_reg.done;
}
group bb0_2 {
std_add_0.left = while_0_arg0_reg.out;
std_add_0.right = 32'd1;
while_0_arg0_reg.in = std_add_0.out;
while_0_arg0_reg.write_en = 1'd1;
bb0_2[done] = while_0_arg0_reg.done;
}
group bb0_3 {
std_slice_0.in = while_0_arg0_reg.out;
ext_mem1_addr0 = std_slice_0.out;
ext_mem1_write_data = stage_0_register_0_reg.out;
ext_mem1_write_en = 1'd1;
bb0_3[done] = ext_mem1_done;
}
}
control {
seq {
par {
assign_while_0_init_0;
}
par {
bb0_1;
bb0_2;
}
@bound(19) while std_lt_0.out with bb0_0 {
par {
bb0_1;
bb0_2;
bb0_3;
}
}
par {
bb0_3;
}
}
}
}
Then I changed the component name to main
and tried to use native calyx compiler to run this program with Xilinx tools as these steps:
For me the first and third steps works fine. There are sv and xml files generated. But with the second step, I always get this error saying:
Error: Program has no memories marked with attribute @external. Please make sure that at least one memory is marked as @external.
Then I tried to add the @external
attribute to the memory within cells
block, but there is none!
I’m wondering if this is something wrong with the pass or translate or I should add memory usage myself, which either is problematic.