Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
ycai
simbricks
Commits
b22870d8
Commit
b22870d8
authored
Apr 15, 2024
by
Jonas Kaufmann
Committed by
Antoine Kaufmann
May 08, 2024
Browse files
lib/simbricks/axi: add AXI Lite Manager (Master) adapter
parent
105688c8
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
439 additions
and
0 deletions
+439
-0
lib/simbricks/axi/axil_manager.hh
lib/simbricks/axi/axil_manager.hh
+439
-0
No files found.
lib/simbricks/axi/axil_manager.hh
0 → 100644
View file @
b22870d8
/*
* Copyright 2024 Max Planck Institute for Software Systems, and
* National University of Singapore
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef SIMBRICKS_AXI_AXIL_MANAGER_HH_
#define SIMBRICKS_AXI_AXIL_MANAGER_HH_
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <functional>
#include <iostream>
#include <memory>
#include <utility>
#include <variant>
// #define AXIL_R_DEBUG 0
// #define AXIL_W_DEBUG 0
namespace
simbricks
{
struct
AXILOperationR
{
AXILOperationR
(
uint64_t
addr
,
uint64_t
req_id
)
:
addr
(
addr
),
req_id
(
req_id
)
{
}
uint64_t
addr
;
uint64_t
req_id
;
uint64_t
data
=
0
;
};
struct
AXILOperationW
{
AXILOperationW
(
uint64_t
addr
,
uint64_t
req_id
,
uint64_t
data
,
bool
posted
)
:
addr
(
addr
),
req_id
(
req_id
),
data
(
data
),
posted
(
posted
)
{
}
uint64_t
addr
;
uint64_t
req_id
;
uint64_t
data
;
bool
posted
;
};
template
<
size_t
BytesAddr
,
size_t
BytesData
>
class
AXILManagerReadPort
{
/* address channel */
uint8_t
*
const
ar_addr_
;
const
uint8_t
&
ar_ready_
;
uint8_t
&
ar_valid_
;
/* data channel */
const
uint8_t
*
const
r_data_
;
uint8_t
&
r_ready_
;
const
uint8_t
&
r_valid_
;
uint8_t
&
r_resp_
;
/* Temp values for outputs. We can't update the outputs directly in `step()`
since that violates the semantics of non-blocking assignments. */
uint8_t
ar_valid_tmp_
=
0
;
uint64_t
ar_addr_tmp_
=
0
;
/* other stuff */
uint64_t
main_time_
=
0
;
AXILOperationR
*
cur_op_
=
nullptr
;
std
::
function
<
void
()
>
op_done_cb_
;
bool
handling_op_
=
false
;
public:
AXILManagerReadPort
(
uint8_t
*
ar_addr
,
const
uint8_t
&
ar_ready
,
uint8_t
&
ar_valid
,
const
uint8_t
*
r_data
,
uint8_t
&
r_ready
,
const
uint8_t
&
r_valid
,
uint8_t
&
r_resp
,
std
::
function
<
void
()
>
op_done_cb
)
:
ar_addr_
(
ar_addr
),
ar_ready_
(
ar_ready
),
ar_valid_
(
ar_valid
),
r_data_
(
r_data
),
r_ready_
(
r_ready
),
r_valid_
(
r_valid
),
r_resp_
(
r_resp
),
op_done_cb_
(
std
::
move
(
op_done_cb
))
{
}
void
step
(
uint64_t
cur_ts
);
void
step_apply
();
void
set_op
(
AXILOperationR
&
axi_op
)
{
if
(
cur_op_
!=
nullptr
)
{
throw
"AXILManagerReadPort::set_op() cur_op_ must be completed i.e. NULL "
"before setting new one"
;
}
cur_op_
=
&
axi_op
;
}
};
template
<
size_t
BytesAddr
,
size_t
BytesData
>
class
AXILManagerWritePort
{
/* address channel */
uint8_t
*
const
aw_addr_
;
const
uint8_t
&
aw_ready_
;
uint8_t
&
aw_valid_
;
/* data channel */
uint8_t
*
const
w_data_
;
const
uint8_t
&
w_ready_
;
uint8_t
&
w_valid_
;
uint8_t
&
w_strb_
;
/* response channel */
uint8_t
&
b_ready_
;
const
uint8_t
&
b_valid_
;
const
uint8_t
&
b_resp_
;
/* Temp values for outputs. We can't update the outputs directly in `step()`
since that violates the semantics of non-blocking assignments. */
uint8_t
aw_valid_tmp_
=
0
;
uint64_t
aw_addr_tmp_
=
0
;
uint8_t
w_valid_tmp_
=
0
;
uint64_t
w_data_tmp_
=
0
;
/* other stuff */
uint64_t
main_time_
=
0
;
AXILOperationW
*
cur_op_
=
nullptr
;
std
::
function
<
void
()
>
op_done_cb_
;
bool
handling_op_
=
false
;
public:
AXILManagerWritePort
(
uint8_t
*
aw_addr
,
const
uint8_t
&
aw_ready
,
uint8_t
&
aw_valid
,
uint8_t
*
w_data
,
const
uint8_t
&
w_ready
,
uint8_t
&
w_valid
,
uint8_t
&
w_strb
,
uint8_t
&
b_ready
,
const
uint8_t
&
b_valid
,
const
uint8_t
&
b_resp
,
std
::
function
<
void
()
>
op_done_cb
)
:
aw_addr_
(
aw_addr
),
aw_ready_
(
aw_ready
),
aw_valid_
(
aw_valid
),
w_data_
(
w_data
),
w_ready_
(
w_ready
),
w_valid_
(
w_valid
),
w_strb_
(
w_strb
),
b_ready_
(
b_ready
),
b_valid_
(
b_valid
),
b_resp_
(
b_resp
),
op_done_cb_
(
std
::
move
(
op_done_cb
))
{
}
void
step
(
uint64_t
cur_ts
);
void
step_apply
();
void
set_op
(
AXILOperationW
&
axi_op
)
{
if
(
cur_op_
!=
nullptr
)
{
throw
"AXILManagerReadPort::set_op() cur_op_ must be completed i.e. NULL "
"before setting new one"
;
}
cur_op_
=
&
axi_op
;
}
};
template
<
size_t
BytesAddr
,
size_t
BytesData
>
class
AXILManager
{
static_assert
(
BytesData
==
4
||
BytesData
==
8
,
"AXI 4 Lite standard dictates 32 or 64 bits data width"
);
static_assert
(
BytesAddr
<=
8
);
using
ReadPortT
=
AXILManagerReadPort
<
BytesAddr
,
BytesData
>
;
using
WritePortT
=
AXILManagerWritePort
<
BytesAddr
,
BytesData
>
;
private:
ReadPortT
read_port_
;
WritePortT
write_port_
;
std
::
deque
<
std
::
variant
<
AXILOperationR
,
AXILOperationW
>>
pending_
{};
public:
AXILManager
(
uint8_t
*
ar_addr
,
const
uint8_t
&
ar_ready
,
uint8_t
&
ar_valid
,
const
uint8_t
*
r_data
,
uint8_t
&
r_ready
,
const
uint8_t
&
r_valid
,
uint8_t
&
r_resp
,
uint8_t
*
aw_addr
,
const
uint8_t
&
aw_ready
,
uint8_t
&
aw_valid
,
uint8_t
*
w_data
,
const
uint8_t
&
w_ready
,
uint8_t
&
w_valid
,
uint8_t
&
w_strb
,
uint8_t
&
b_ready
,
const
uint8_t
&
b_valid
,
const
uint8_t
&
b_resp
)
:
read_port_
(
ReadPortT
(
ar_addr
,
ar_ready
,
ar_valid
,
r_data
,
r_ready
,
r_valid
,
r_resp
,
std
::
bind
(
&
AXILManager
<
BytesAddr
,
BytesData
>::
op_done
,
this
))),
write_port_
(
WritePortT
(
aw_addr
,
aw_ready
,
aw_valid
,
w_data
,
w_ready
,
w_valid
,
w_strb
,
b_ready
,
b_valid
,
b_resp
,
std
::
bind
(
&
AXILManager
<
BytesAddr
,
BytesData
>::
op_done
,
this
)))
{
}
/*
Updates the output signals based on the inputs.
This function doesn't apply the output changes yet. This is necessary to
properly model the semantics of non-blocking writes, i.e. the changes only
become visible to the connected Subordinate in the next clock cycle. In
Verilator, call this function before `eval()`. To apply the output changes,
call `step_apply()`.
*/
void
step
(
uint64_t
cur_ts
);
/*
Applies the output changes. In Verilator, call this function after `eval()`.
*/
void
step_apply
();
/* issue a new request */
void
issue_read
(
uint64_t
req_id
,
uint64_t
addr
);
void
issue_write
(
uint64_t
req_id
,
uint64_t
addr
,
uint64_t
data
,
bool
posted
);
protected:
virtual
void
read_done
(
AXILOperationR
&
axi_op
)
=
0
;
virtual
void
write_done
(
AXILOperationW
&
axi_op
)
=
0
;
private:
void
op_done
();
void
ports_set_op
();
};
/******************************************************************************/
/* Start of implementation. We need to put this into the header since code for
templated classes is only emitted at instantiation. */
/* AXILManagerReadPort */
/******************************************************************************/
template
<
size_t
BytesAddr
,
size_t
BytesData
>
void
AXILManagerReadPort
<
BytesAddr
,
BytesData
>::
step
(
uint64_t
cur_ts
)
{
main_time_
=
cur_ts
;
/* drive these signals to constants */
r_ready_
=
1
;
r_resp_
=
0
;
/* addr handshake complete */
if
(
ar_valid_
&&
ar_ready_
)
{
assert
(
cur_op_
!=
nullptr
);
ar_valid_tmp_
=
0
;
ar_addr_tmp_
=
0
;
#if AXIL_R_DEBUG
std
::
cout
<<
main_time_
<<
" AXIL R addr handshake done id="
<<
cur_op_
->
req_id
<<
"
\n
"
;
#endif
}
/* data handshake complete */
if
(
r_ready_
&&
r_valid_
)
{
assert
(
cur_op_
!=
nullptr
);
#if AXIL_R_DEBUG
std
::
cout
<<
main_time_
<<
" AXIL R read data segment id="
<<
cur_op_
->
req_id
<<
"
\n
"
;
#endif
std
::
memcpy
(
&
cur_op_
->
data
,
r_data_
,
BytesData
);
cur_op_
=
nullptr
;
handling_op_
=
false
;
op_done_cb_
();
}
/* issue new read request */
if
(
!
handling_op_
&&
cur_op_
!=
nullptr
)
{
handling_op_
=
true
;
ar_addr_tmp_
=
cur_op_
->
addr
;
ar_valid_tmp_
=
1
;
#if AXIL_R_DEBUG
std
::
cout
<<
main_time_
<<
" AXIL R issuing new op id="
<<
cur_op_
->
req_id
<<
" addr="
<<
cur_op_
->
addr
<<
"
\n
"
;
#endif
}
}
template
<
size_t
BytesAddr
,
size_t
BytesData
>
void
AXILManagerReadPort
<
BytesAddr
,
BytesData
>::
step_apply
()
{
ar_valid_
=
ar_valid_tmp_
;
std
::
memcpy
(
ar_addr_
,
&
ar_addr_tmp_
,
BytesAddr
);
}
/******************************************************************************/
/* AXILManagerWritePort */
/******************************************************************************/
template
<
size_t
BytesAddr
,
size_t
BytesData
>
void
AXILManagerWritePort
<
BytesAddr
,
BytesData
>::
step
(
uint64_t
cur_ts
)
{
main_time_
=
cur_ts
;
/* drive these signals to constants */
w_strb_
=
0xff
;
b_ready_
=
1
;
/* addr handshake complete */
if
(
aw_valid_
&&
aw_ready_
)
{
aw_valid_tmp_
=
0
;
aw_addr_tmp_
=
0
;
#if AXIL_W_DEBUG
std
::
cout
<<
main_time_
<<
" AXIL W addr handshake done id="
<<
cur_op_
->
req_id
<<
"
\n
"
;
#endif
}
/* handshake for data complete */
if
(
w_ready_
&&
w_valid_
)
{
#if AXIL_W_DEBUG
std
::
cout
<<
main_time_
<<
" AXIL W data handshake done id="
<<
cur_op_
->
req_id
<<
"
\n
"
;
#endif
w_valid_tmp_
=
0
;
w_data_tmp_
=
0
;
}
/* response handshake complete */
if
(
b_ready_
&&
b_valid_
)
{
#if AXIL_W_DEBUG
std
::
cout
<<
main_time_
<<
" AXIL W completed id="
<<
cur_op_
->
req_id
<<
"
\n
"
;
#endif
cur_op_
=
nullptr
;
handling_op_
=
false
;
op_done_cb_
();
}
/* issue new request */
if
(
!
handling_op_
&&
cur_op_
!=
nullptr
)
{
#if AXIL_W_DEBUG
std
::
cout
<<
main_time_
<<
" AXIL W issuing new op id="
<<
cur_op_
->
req_id
<<
" addr="
<<
cur_op_
->
addr
<<
"
\n
"
;
#endif
handling_op_
=
true
;
aw_addr_tmp_
=
cur_op_
->
addr
;
aw_valid_tmp_
=
1
;
w_data_tmp_
=
cur_op_
->
data
;
w_valid_tmp_
=
1
;
}
}
template
<
size_t
BytesAddr
,
size_t
BytesData
>
void
AXILManagerWritePort
<
BytesAddr
,
BytesData
>::
step_apply
()
{
aw_valid_
=
aw_valid_tmp_
;
std
::
memcpy
(
aw_addr_
,
&
aw_addr_tmp_
,
BytesAddr
);
w_valid_
=
w_valid_tmp_
;
std
::
memcpy
(
w_data_
,
&
w_data_tmp_
,
BytesData
);
}
/******************************************************************************/
/* AXILManager */
/******************************************************************************/
template
<
size_t
BytesAddr
,
size_t
BytesData
>
void
AXILManager
<
BytesAddr
,
BytesData
>::
step
(
uint64_t
cur_ts
)
{
if
(
pending_
.
empty
())
{
return
;
}
auto
&
axi_op
=
pending_
.
front
();
if
(
std
::
holds_alternative
<
AXILOperationR
>
(
axi_op
))
{
read_port_
.
step
(
cur_ts
);
}
else
if
(
std
::
holds_alternative
<
AXILOperationW
>
(
axi_op
))
{
write_port_
.
step
(
cur_ts
);
}
}
template
<
size_t
BytesAddr
,
size_t
BytesData
>
void
AXILManager
<
BytesAddr
,
BytesData
>::
step_apply
()
{
if
(
pending_
.
empty
())
{
return
;
}
auto
&
axi_op
=
pending_
.
front
();
if
(
std
::
holds_alternative
<
AXILOperationR
>
(
axi_op
))
{
read_port_
.
step_apply
();
}
else
if
(
std
::
holds_alternative
<
AXILOperationW
>
(
axi_op
))
{
write_port_
.
step_apply
();
}
}
template
<
size_t
BytesAddr
,
size_t
BytesData
>
void
AXILManager
<
BytesAddr
,
BytesData
>::
issue_read
(
uint64_t
req_id
,
uint64_t
addr
)
{
if
(
addr
%
BytesData
!=
0
)
{
throw
"AXILManager::issue_read() addr has to be aligned to BytesData"
;
}
bool
was_empty
=
pending_
.
empty
();
pending_
.
emplace_back
(
AXILOperationR
{
addr
,
req_id
});
if
(
was_empty
)
{
ports_set_op
();
}
}
template
<
size_t
BytesAddr
,
size_t
BytesData
>
void
AXILManager
<
BytesAddr
,
BytesData
>::
issue_write
(
uint64_t
req_id
,
uint64_t
addr
,
uint64_t
data
,
bool
posted
)
{
if
(
addr
%
BytesData
!=
0
)
{
throw
"AXILManager::issue_write() addr has to be aligned to BytesData"
;
}
bool
was_empty
=
pending_
.
empty
();
pending_
.
emplace_back
(
AXILOperationW
{
addr
,
req_id
,
data
,
posted
});
if
(
was_empty
)
{
ports_set_op
();
}
}
template
<
size_t
BytesAddr
,
size_t
BytesData
>
void
AXILManager
<
BytesAddr
,
BytesData
>::
op_done
()
{
auto
&
axi_op
=
pending_
.
front
();
if
(
std
::
holds_alternative
<
AXILOperationR
>
(
axi_op
))
{
read_done
(
std
::
get
<
AXILOperationR
>
(
axi_op
));
}
else
if
(
std
::
holds_alternative
<
AXILOperationW
>
(
axi_op
))
{
write_done
(
std
::
get
<
AXILOperationW
>
(
axi_op
));
}
pending_
.
pop_front
();
ports_set_op
();
}
template
<
size_t
BytesAddr
,
size_t
BytesData
>
void
AXILManager
<
BytesAddr
,
BytesData
>::
ports_set_op
()
{
if
(
pending_
.
empty
())
{
return
;
}
auto
&
axi_op
=
pending_
.
front
();
if
(
std
::
holds_alternative
<
AXILOperationR
>
(
axi_op
))
{
read_port_
.
set_op
(
std
::
get
<
AXILOperationR
>
(
axi_op
));
}
else
if
(
std
::
holds_alternative
<
AXILOperationW
>
(
axi_op
))
{
write_port_
.
set_op
(
std
::
get
<
AXILOperationW
>
(
axi_op
));
}
}
}
// namespace simbricks
#endif // SIMBRICKS_AXI_AXIL_MANAGER_HH_
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment