Skip to content

Commit 50ac086

Browse files
DSLX DMA: Draft implementation
Signed-off-by: Michal Czyz <[email protected]>
1 parent 5a89110 commit 50ac086

18 files changed

+5207
-0
lines changed

xls/modules/dma/BUILD

Lines changed: 650 additions & 0 deletions
Large diffs are not rendered by default.

xls/modules/dma/README.md

Lines changed: 411 additions & 0 deletions
Large diffs are not rendered by default.

xls/modules/dma/address_generator.x

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
// Copyright 2023-2024 The XLS Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Address Generator
16+
17+
import std;
18+
import xls.modules.dma.common;
19+
import xls.modules.dma.config;
20+
21+
type TransferDescBundle = common::TransferDescBundle;
22+
type MainCtrlBundle = common::MainCtrlBundle;
23+
24+
enum AddressGeneratorStatusEnum : u2 {
25+
IDLE = 0,
26+
WAIT = 1,
27+
BUSY = 2,
28+
DONE = 3,
29+
}
30+
31+
struct AddressGeneratorState<ADDR_W: u32> {
32+
configuration: MainCtrlBundle<ADDR_W>,
33+
status: AddressGeneratorStatusEnum,
34+
transfer: TransferDescBundle<ADDR_W>,
35+
it: u32,
36+
rsp_counter: u32,
37+
}
38+
39+
proc AddressGenerator<ADDR_W: u32, DATA_W_DIV8: u32> {
40+
configuration: chan<MainCtrlBundle<ADDR_W>> in;
41+
start_ch: chan<u1> in;
42+
busy_ch: chan<u1> out;
43+
done_ch: chan<u1> out;
44+
addr_gen_req: chan<TransferDescBundle<ADDR_W>> out;
45+
addr_gen_rsp: chan<()> in;
46+
47+
config(configuration: chan<MainCtrlBundle<ADDR_W>> in, start_ch: chan<u1> in,
48+
busy_ch: chan<u1> out, done_ch: chan<u1> out,
49+
addr_gen_req: chan<TransferDescBundle<ADDR_W>> out, addr_gen_rsp: chan<()> in) {
50+
(configuration, start_ch, busy_ch, done_ch, addr_gen_req, addr_gen_rsp)
51+
}
52+
53+
init {
54+
(AddressGeneratorState<ADDR_W> {
55+
configuration: common::zeroMainCtrlBundle<ADDR_W>(),
56+
status: AddressGeneratorStatusEnum::IDLE,
57+
transfer: common::zeroTransferDescBundle<ADDR_W>(),
58+
it: u32:0,
59+
rsp_counter: u32:0
60+
})
61+
}
62+
63+
next(tok: token, state: AddressGeneratorState<ADDR_W>) {
64+
65+
let (tok, start) = recv(tok, start_ch);
66+
let goto_wait = start && (state.status == AddressGeneratorStatusEnum::IDLE);
67+
68+
let (tok, configuration) = recv(tok, configuration);
69+
let goto_busy = state.status == AddressGeneratorStatusEnum::WAIT;
70+
let configuration = if goto_busy {
71+
trace_fmt!("[AG] New configuration = {}", configuration);
72+
configuration
73+
} else {
74+
state.configuration
75+
};
76+
77+
let send_transfer = state.status == AddressGeneratorStatusEnum::BUSY &&
78+
(state.it != (state.configuration.lineCount as u32));
79+
let tok = send_if(tok, addr_gen_req, send_transfer, state.transfer);
80+
81+
let it = if send_transfer { state.it + u32:1 } else { state.it };
82+
83+
let (tok, _, valid_addr_gen_rsp) = recv_if_non_blocking(
84+
tok, addr_gen_rsp, state.status == AddressGeneratorStatusEnum::BUSY, ());
85+
86+
let rsp_counter =
87+
if valid_addr_gen_rsp { state.rsp_counter + u32:1 } else { state.rsp_counter };
88+
89+
let goto_done = (state.status == AddressGeneratorStatusEnum::BUSY) &&
90+
(rsp_counter == (state.configuration.lineCount as u32));
91+
92+
let tok = send_if(tok, done_ch, goto_done, u1:1);
93+
94+
let goto_idle = state.status == AddressGeneratorStatusEnum::DONE;
95+
96+
// Next state logic
97+
let nextState = if state.status == AddressGeneratorStatusEnum::IDLE {
98+
if goto_wait {
99+
AddressGeneratorStatusEnum::WAIT
100+
} else {
101+
AddressGeneratorStatusEnum::IDLE
102+
}
103+
} else if state.status == AddressGeneratorStatusEnum::WAIT {
104+
if goto_busy {
105+
AddressGeneratorStatusEnum::BUSY
106+
} else {
107+
AddressGeneratorStatusEnum::WAIT
108+
}
109+
} else if state.status == AddressGeneratorStatusEnum::BUSY {
110+
if goto_done {
111+
AddressGeneratorStatusEnum::DONE
112+
} else {
113+
AddressGeneratorStatusEnum::BUSY
114+
}
115+
} else if state.status == AddressGeneratorStatusEnum::DONE {
116+
if goto_idle {
117+
AddressGeneratorStatusEnum::IDLE
118+
} else {
119+
AddressGeneratorStatusEnum::DONE
120+
}
121+
} else {
122+
AddressGeneratorStatusEnum::IDLE
123+
};
124+
// trace_fmt!("State : {} Next state : {}", state.status, nextState);
125+
126+
let nextBundle = if state.status == AddressGeneratorStatusEnum::BUSY {
127+
TransferDescBundle<ADDR_W> {
128+
address:
129+
state.transfer.address +
130+
(DATA_W_DIV8 as uN[ADDR_W]) *
131+
(state.configuration.lineLength + state.configuration.lineStride),
132+
length: state.configuration.lineLength
133+
}
134+
} else if state.status == AddressGeneratorStatusEnum::WAIT {
135+
TransferDescBundle<ADDR_W> {
136+
address: configuration.startAddress, length: configuration.lineLength
137+
}
138+
} else {
139+
state.transfer
140+
};
141+
142+
let it = if state.status == AddressGeneratorStatusEnum::DONE { u32:0 } else { it };
143+
let rsp_counter =
144+
if state.status == AddressGeneratorStatusEnum::DONE { u32:0 } else { rsp_counter };
145+
146+
let is_busy = (state.status != AddressGeneratorStatusEnum::IDLE) as u1;
147+
let tok = send(tok, busy_ch, is_busy);
148+
149+
trace!(state);
150+
AddressGeneratorState {
151+
configuration, transfer: nextBundle, status: nextState, it, rsp_counter
152+
}
153+
}
154+
}
155+
156+
pub fn AddressGeneratorReferenceFunction<C: u32, ADDR_W: u32, DATA_W_DIV8: u32>
157+
(config: MainCtrlBundle<ADDR_W>) -> TransferDescBundle<ADDR_W>[C] {
158+
159+
let a = for (i, a): (u32, TransferDescBundle[C]) in range(u32:0, C) {
160+
if i == u32:0 {
161+
update(
162+
a, i,
163+
TransferDescBundle<ADDR_W> {
164+
address: config.startAddress, length: config.lineLength
165+
})
166+
} else {
167+
update(
168+
a, i,
169+
TransferDescBundle<ADDR_W> {
170+
address:
171+
(a[i - u32:1]).address + DATA_W_DIV8 * (config.lineLength + config.lineStride),
172+
length: config.lineLength
173+
})
174+
}
175+
}(TransferDescBundle<ADDR_W>[C]:[common::zeroTransferDescBundle<ADDR_W>(), ...]);
176+
a
177+
}
178+
179+
#[test]
180+
fn TestAddressGeneratorReferenceFunction() {
181+
let ADDR_W = u32:32;
182+
let dataWidthDiv8 = u32:4;
183+
let testConfig = MainCtrlBundle<ADDR_W> {
184+
startAddress: uN[ADDR_W]:1000,
185+
lineCount: uN[ADDR_W]:4,
186+
lineLength: uN[ADDR_W]:3,
187+
lineStride: uN[ADDR_W]:2
188+
};
189+
// TODO: Is using parametric from a struct field a good practice?
190+
let C = testConfig.lineCount;
191+
let a = AddressGeneratorReferenceFunction<C, ADDR_W, dataWidthDiv8>(testConfig);
192+
assert_eq((a[0]).address, u32:1000);
193+
assert_eq((a[1]).address, u32:1020);
194+
assert_eq((a[2]).address, u32:1040);
195+
assert_eq((a[3]).address, u32:1060);
196+
197+
assert_eq((a[0]).length, u32:3);
198+
assert_eq((a[1]).length, u32:3);
199+
assert_eq((a[2]).length, u32:3);
200+
assert_eq((a[3]).length, u32:3);
201+
}
202+
203+
const TEST_DATA_W_DIV8 = u32:4;
204+
const TEST_ADDR_W = u32:32;
205+
206+
#[test_proc]
207+
proc TestAddressGenerator {
208+
configuration: chan<MainCtrlBundle<TEST_ADDR_W>> out;
209+
start_ch: chan<u1> out;
210+
busy_ch: chan<u1> in;
211+
done_ch: chan<u1> in;
212+
addr_gen_req: chan<TransferDescBundle<TEST_ADDR_W>> in;
213+
addr_gen_rsp: chan<()> out;
214+
terminator: chan<bool> out;
215+
216+
config(terminator: chan<bool> out) {
217+
let (configuration_s, configuration_r) = chan<MainCtrlBundle<TEST_ADDR_W>>;
218+
let (start_ch_s, start_ch_r) = chan<u1>;
219+
let (busy_ch_s, busy_ch_r) = chan<u1>;
220+
let (done_ch_s, done_ch_r) = chan<u1>;
221+
let (addr_gen_req_s, addr_gen_req_r) = chan<TransferDescBundle<TEST_ADDR_W>>;
222+
let (addr_gen_rsp_s, addr_gen_rsp_r) = chan<()>;
223+
spawn AddressGenerator<TEST_ADDR_W, TEST_DATA_W_DIV8>(
224+
configuration_r, start_ch_r, busy_ch_s, done_ch_s, addr_gen_req_s, addr_gen_rsp_r);
225+
(
226+
configuration_s, start_ch_s, busy_ch_r, done_ch_r, addr_gen_req_r, addr_gen_rsp_s,
227+
terminator,
228+
)
229+
}
230+
231+
init { (u32:0) }
232+
233+
next(tok: token, state: u32) {
234+
let testConfig = MainCtrlBundle<TEST_ADDR_W> {
235+
startAddress: uN[TEST_ADDR_W]:1000,
236+
lineCount: uN[TEST_ADDR_W]:5,
237+
lineLength: uN[TEST_ADDR_W]:3,
238+
lineStride: uN[TEST_ADDR_W]:0
239+
};
240+
241+
let tok = send(tok, start_ch, u1:1);
242+
let tok = send(tok, configuration, testConfig);
243+
244+
let (tok, r_data, r_data_valid) =
245+
recv_non_blocking(tok, addr_gen_req, common::zeroTransferDescBundle<TEST_ADDR_W>());
246+
247+
let state = if r_data_valid {
248+
trace_fmt!("r_data = {}", r_data);
249+
let tok = send(tok, addr_gen_rsp, ());
250+
state + u32:1
251+
} else {
252+
state
253+
};
254+
255+
let (tok, done, done_valid) = recv_non_blocking(tok, done_ch, u1:1);
256+
257+
let do_terminate = done && done_valid;
258+
if do_terminate { assert_eq(state, testConfig.lineCount); } else { };
259+
let tok = send_if(tok, terminator, do_terminate, do_terminate);
260+
261+
state
262+
}
263+
}
264+
265+
// Verilog example
266+
proc address_generator {
267+
config(configuration: chan<MainCtrlBundle<config::TOP_ADDR_W>> in, start_ch: chan<u1> in,
268+
busy_ch: chan<u1> out, done_ch: chan<u1> out,
269+
addr_gen_req: chan<TransferDescBundle<config::TOP_ADDR_W>> out, addr_gen_rsp: chan<()> in) {
270+
spawn AddressGenerator<config::TOP_ADDR_W, config::TOP_DATA_W_DIV8>(
271+
configuration, start_ch, busy_ch, done_ch, addr_gen_req, addr_gen_rsp);
272+
()
273+
}
274+
275+
init { () }
276+
277+
next(tok: token, state: ()) { }
278+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2023-2024 The XLS Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Address Generator
16+
17+
pub struct OutputBundle { address: u32, length: u32 }
18+
19+
pub struct InputBundle { startAddress: u32, lineCount: u32, lineLength: u32, lineStride: u32 }
20+
21+
pub fn FuncAddressGenerator<ARRAY_SIZE: u32, NUM_BYTES: u32>
22+
(input: InputBundle) -> OutputBundle[ARRAY_SIZE] {
23+
let arr = OutputBundle[ARRAY_SIZE]:[zero!<OutputBundle>(), ...];
24+
let arr =
25+
update(arr, u32:0, OutputBundle { address: input.startAddress, length: input.lineLength });
26+
27+
let arr = for (i, arr): (u32, OutputBundle[ARRAY_SIZE]) in u32:1..ARRAY_SIZE {
28+
update(
29+
arr, i,
30+
OutputBundle {
31+
address:
32+
(arr[i - u32:1]).address + NUM_BYTES * (input.lineLength + input.lineStride),
33+
length: input.lineLength
34+
})
35+
}(arr);
36+
arr
37+
}
38+
39+
#[test]
40+
fn TestFunction() {
41+
let TestBundle = InputBundle {
42+
startAddress: u32:1000, lineCount: u32:4, lineLength: u32:3, lineStride: u32:2
43+
};
44+
45+
let ARRAY_SIZE = TestBundle.lineCount * TestBundle.lineLength;
46+
let NUM_BYTES = u32:4;
47+
let AddressArray = FuncAddressGenerator<ARRAY_SIZE, NUM_BYTES>(TestBundle);
48+
49+
trace_fmt!("Address Array = {}", AddressArray);
50+
assert_eq((AddressArray[0]).address, u32:1000);
51+
assert_eq((AddressArray[1]).address, u32:1020);
52+
assert_eq((AddressArray[2]).address, u32:1040);
53+
54+
assert_eq((AddressArray[0]).length, u32:3);
55+
assert_eq((AddressArray[1]).length, u32:3);
56+
assert_eq((AddressArray[2]).length, u32:3);
57+
}

0 commit comments

Comments
 (0)