Skip to content

Commit

Permalink
Add an AxiSlaveToReg variant AxiSlaveToCSReg
Browse files Browse the repository at this point in the history
This new class supports both control and status registers.
  • Loading branch information
ben-k committed Aug 12, 2024
1 parent 3cb550c commit 8f7db63
Show file tree
Hide file tree
Showing 5 changed files with 284 additions and 14 deletions.
235 changes: 229 additions & 6 deletions cmod/include/axi/AxiSlaveToReg.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ template <typename axiCfg, int numReg, int numAddrBitsToInspect = axiCfg::addrWi
class AxiSlaveToReg : public sc_module {
public:
static const int kDebugLevel = 5;

typedef typename axi::axi4<axiCfg> axi4_;

sc_in<bool> clk;
sc_in<bool> reset_bar;

Expand Down Expand Up @@ -89,7 +89,7 @@ class AxiSlaveToReg : public sc_module {
void run() {
if_axi_rd.reset();
if_axi_wr.reset();

NVUINTW(axi4_::DATA_WIDTH) reg[numReg];
NVUINTW(numAddrBitsToInspect) maxValidAddr = baseAddr.read() + bytesPerReg*numReg - 1;

Expand Down Expand Up @@ -130,7 +130,7 @@ class AxiSlaveToReg : public sc_module {
}

if (!read_arb_req) {
if (if_axi_rd.nb_aread(axi_rd_req)) {
if (if_axi_rd.nb_aread(axi_rd_req)) {
read_arb_req = 1;
NVUINTW(numAddrBitsToInspect) addr_temp(static_cast<sc_uint<numAddrBitsToInspect> >(axi_rd_req.addr));
NVUINTW(axi4_::ALEN_WIDTH) len_temp(static_cast< sc_uint<axi4_::ALEN_WIDTH> >(axi_rd_req.len));
Expand All @@ -139,7 +139,7 @@ class AxiSlaveToReg : public sc_module {
}
}

if (!write_arb_req) {
if (!write_arb_req) {
if (if_axi_wr.aw.PopNB(axi_wr_req_addr)) {
write_arb_req = 1;
NVUINTW(numAddrBitsToInspect) addr_temp(static_cast< sc_uint<numAddrBitsToInspect> >(axi_wr_req_addr.addr));
Expand All @@ -156,7 +156,7 @@ class AxiSlaveToReg : public sc_module {
axi_rd_resp.resp = axi4_::Enc::XRESP::OKAY;
NVUINTW(axi4_::DATA_WIDTH) read_data;
axi_rd_resp.data = reg[regAddr];
}
}
else {
axi_rd_resp.resp = axi4_::Enc::XRESP::SLVERR;
}
Expand Down Expand Up @@ -229,4 +229,227 @@ class AxiSlaveToReg : public sc_module {
}
};

/**
* \brief An AXI slave containing memory-mapped registers.
* \ingroup AXI
*
* \tparam axiCfg A valid AXI config.
* \tparam numControlReg The number of control (local writable state) registers in the slave. Each register has a width equivalent to the AXI data width.
* \tparam numStatusReg The number of status (external read-only state) registers in the slave. Each register has a width equivalent to the AXI data width.
* \tparam numAddrBitsToInspect The number of address bits to inspect when determining which slave to direct traffic to. If this is less than the full address width, the routing determination will be made based on the number of address LSBs specified. (Default: axiCfg::addrWidth)
*
* \par Overview
* AxiSlaveToCSnReg is an AXI slave that saves its state in a bank of registers. The register state is accessible as an array of sc_out.
* Access to read-only status registers is provided as an array of sc_in. The status register address range immediately follows the control (read/write) register address range
*
* \par Usage Guidelines
*
* This module sets the stall mode to flush by default to mitigate possible RTL
* bugs that can occur in the default stall mode. If you are confident that
* this class of bugs will not occur in your use case, you can change the stall
* mode via TCL directive:
*
* \code
* directive set /path/to/AxiSlaveToCSReg/run/while -PIPELINE_STALL_MODE stall
* \endcode
*
* This may reduce area/power.
* \par
*
*/
template <typename axiCfg, int numControlReg, int numStatusReg, int numAddrBitsToInspect = axiCfg::addrWidth>
class AxiSlaveToCSReg : public sc_module {
public:
static const int kDebugLevel = 5;

typedef typename axi::axi4<axiCfg> axi4_;

sc_in<bool> clk;
sc_in<bool> reset_bar;

typename axi4_::read::template slave<> if_axi_rd;
typename axi4_::write::template slave<> if_axi_wr;

static const int numReg = numControlReg + numStatusReg;
static const int controlRegAddrWidth = nvhls::log2_ceil<numControlReg>::val;
static const int statusRegAddrWidth = nvhls::log2_ceil<numStatusReg>::val;
static const int bytesPerReg = axi4_::DATA_WIDTH >> 3;
static const int axiAddrBitsPerReg = nvhls::log2_ceil<bytesPerReg>::val;

sc_in<NVUINTW(numAddrBitsToInspect)> baseAddr;

// Each reg is one AXI data word
sc_out<NVUINTW(axi4_::DATA_WIDTH)> regOut[numControlReg];
sc_in<NVUINTW(axi4_::DATA_WIDTH)> regIn[numStatusReg];

public:
SC_CTOR(AxiSlaveToCSReg)
: clk("clk"),
reset_bar("reset_bar"),
if_axi_rd("if_axi_rd"),
if_axi_wr("if_axi_wr")
{
SC_THREAD(run);
sensitive << clk.pos();
async_reset_signal_is(reset_bar, false);
}

protected:
void run() {
if_axi_rd.reset();
if_axi_wr.reset();

NVUINTW(axi4_::DATA_WIDTH) reg[numControlReg];
NVUINTW(numAddrBitsToInspect) maxValidRdAddr = baseAddr.read() + bytesPerReg*numReg - 1;
NVUINTW(numAddrBitsToInspect) maxValidWrAddr = baseAddr.read() + bytesPerReg*numControlReg - 1;

#pragma hls_unroll yes
for (int i=0; i<numControlReg; i++) {
reg[i] = 0;
regOut[i].write(reg[i]);
}

typename axi4_::AddrPayload axi_rd_req;
typename axi4_::ReadPayload axi_rd_resp;
typename axi4_::AddrPayload axi_wr_req_addr;
typename axi4_::WritePayload axi_wr_req_data;
typename axi4_::WRespPayload axi_wr_resp;

NVUINTW(numAddrBitsToInspect) axiRdAddr;
NVUINTW(axi4_::ALEN_WIDTH) axiRdLen;
bool valid_rd_addr;
NVUINTW(numAddrBitsToInspect) axiWrAddr;
bool valid_wr_addr;

bool read_arb_req = 0;
bool write_arb_req = 0;
bool arb_needs_update = 1;
NVUINTW(2) valid_mask = 0;
NVUINTW(2) select_mask = 0;
Arbiter<2> arb;

#pragma hls_pipeline_init_interval 1
#pragma pipeline_stall_mode flush
while (1) {
wait();

valid_mask = write_arb_req << 1 | read_arb_req;
if (arb_needs_update) {
select_mask = arb.pick(valid_mask);
if (select_mask != 0) arb_needs_update = 0;
}

if (!read_arb_req) {
if (if_axi_rd.nb_aread(axi_rd_req)) {
read_arb_req = 1;
NVUINTW(numAddrBitsToInspect) addr_temp(static_cast<sc_uint<numAddrBitsToInspect> >(axi_rd_req.addr));
NVUINTW(axi4_::ALEN_WIDTH) len_temp(static_cast< sc_uint<axi4_::ALEN_WIDTH> >(axi_rd_req.len));
axiRdAddr = addr_temp;
axiRdLen = len_temp;
}
}

if (!write_arb_req) {
if (if_axi_wr.aw.PopNB(axi_wr_req_addr)) {
write_arb_req = 1;
NVUINTW(numAddrBitsToInspect) addr_temp(static_cast< sc_uint<numAddrBitsToInspect> >(axi_wr_req_addr.addr));
axiWrAddr = addr_temp;
}
}

if (select_mask == 1) {
valid_rd_addr = (axiRdAddr >= baseAddr.read() && axiRdAddr <= maxValidRdAddr);
NVHLS_ASSERT_MSG(valid_rd_addr, "Read address is out of bounds");
NVUINTW(controlRegAddrWidth) controlRegAddr = (axiRdAddr - baseAddr.read()) >> axiAddrBitsPerReg;
NVUINTW(statusRegAddrWidth) statusRegAddr = ((axiRdAddr - baseAddr.read()) >> axiAddrBitsPerReg) - numControlReg;
axi_rd_resp.id = axi_rd_req.id;
if (valid_rd_addr) {
axi_rd_resp.resp = axi4_::Enc::XRESP::OKAY;
NVUINTW(axi4_::DATA_WIDTH) read_data;
if (axiRdAddr <= maxValidWrAddr) { // Read internal control register
axi_rd_resp.data = reg[controlRegAddr];
} else { // Read external status register
axi_rd_resp.data = regIn[statusRegAddr].read();
}
}
else {
axi_rd_resp.resp = axi4_::Enc::XRESP::SLVERR;
}
if (axiRdLen == 0) {
axi_rd_resp.last = 1;
read_arb_req = 0;
arb_needs_update = 1;
} else {
axi_rd_resp.last = 0;
axiRdLen--;
axiRdAddr += bytesPerReg;
}
if_axi_rd.rwrite(axi_rd_resp);
if (axiRdAddr <= maxValidWrAddr) {
CDCOUT(sc_time_stamp() << " " << name() << " Read from control reg:"
<< " axi_addr=" << hex << axiRdAddr.to_int64()
<< " reg_addr=" << controlRegAddr.to_int64()
<< " data=" << hex << axi_rd_resp.data
<< endl, kDebugLevel);
} else {
CDCOUT(sc_time_stamp() << " " << name() << " Read from status reg:"
<< " axi_addr=" << hex << axiRdAddr.to_int64()
<< " reg_addr=" << statusRegAddr.to_int64()
<< " data=" << hex << axi_rd_resp.data
<< endl, kDebugLevel);
}
} else if (select_mask == 2) {
if (if_axi_wr.w.PopNB(axi_wr_req_data)) {
valid_wr_addr = (axiWrAddr >= baseAddr.read() && axiWrAddr <= maxValidWrAddr);
NVHLS_ASSERT_MSG(valid_wr_addr, "Write address is out of bounds");
NVUINTW(axi4_::DATA_WIDTH) axiData(static_cast<typename axi4_::Data>(axi_wr_req_data.data));
NVUINTW(controlRegAddrWidth) regAddr = (axiWrAddr - baseAddr.read()) >> axiAddrBitsPerReg;
if (axi4_::WSTRB_WIDTH > 0) {
if (!axi_wr_req_data.wstrb.and_reduce()) { // Non-uniform write strobe - need to do read-modify-write
NVUINTW(axi4_::DATA_WIDTH) old_data = reg[regAddr];
#pragma hls_unroll yes
for (int i=0; i<axi4_::WSTRB_WIDTH; i++) {
if (axi_wr_req_data.wstrb[i] == 0) {
axiData = nvhls::set_slc(axiData, nvhls::get_slc<8>(old_data,8*i), 8*i);
}
}
}
}
#pragma hls_unroll yes
for (int i=0; i<numControlReg; i++) { // More verbose, but this is the preferred coding style for HLS
if (i == regAddr) {
reg[i] = axiData;
}
}
CDCOUT(sc_time_stamp() << " " << name() << " Wrote to local reg:"
<< " axi_addr=" << hex << axiWrAddr.to_int64()
<< " reg_addr=" << regAddr.to_int64()
<< " data=" << hex << axi_wr_req_data.data
<< " wstrb=" << hex << axi_wr_req_data.wstrb.to_uint64()
<< endl, kDebugLevel);
if (axi_wr_req_data.last == 1) {
write_arb_req = 0;
arb_needs_update = 1;
if (axiCfg::useWriteResponses) {
axi_wr_resp.id = axi_wr_req_addr.id;
if (valid_wr_addr) {
axi_wr_resp.resp = axi4_::Enc::XRESP::OKAY;
} else {
axi_wr_resp.resp = axi4_::Enc::XRESP::SLVERR;
}
if_axi_wr.bwrite(axi_wr_resp);
}
} else {
axiWrAddr += bytesPerReg;
}
}
}
#pragma hls_unroll yes
for (int i=0; i<numControlReg; i++) {
regOut[i].write(reg[i]);
}
}
}
};

#endif
24 changes: 20 additions & 4 deletions cmod/unittests/axi/AxiSlaveToRegTop/AxiSlaveToRegTop.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,25 @@ class AxiSlaveToRegTop : public sc_module {
static const int kDebugLevel = 4;

typedef axi::axi4<axi::cfg::standard> axi_;
enum { numReg = 128, baseAddress = 0x100, numAddrBitsToInspect = 16 };
enum { numControlReg = 128, numStatusReg = 16, baseAddress = 0x100, numAddrBitsToInspect = 16 };

sc_in<bool> clk;
sc_in<bool> reset_bar;

typename axi_::read::template slave<> axi_read;
typename axi_::write::template slave<> axi_write;

AxiSlaveToReg<axi::cfg::standard, numReg, numAddrBitsToInspect> slave;
#ifdef STATUS_REG
AxiSlaveToCSReg<axi::cfg::standard, numControlReg, numStatusReg, numAddrBitsToInspect> slave;
#else
AxiSlaveToReg<axi::cfg::standard, numControlReg, numAddrBitsToInspect> slave;
#endif

sc_signal<NVUINTW(numAddrBitsToInspect)> baseAddr;
sc_out<NVUINTW(axi_::DATA_WIDTH)> regOut[numReg];
sc_out<NVUINTW(axi_::DATA_WIDTH)> regOut[numControlReg];
#ifdef STATUS_REG
sc_in<NVUINTW(axi_::DATA_WIDTH)> regIn[numStatusReg];
#endif

SC_HAS_PROCESS(AxiSlaveToRegTop);

Expand All @@ -59,9 +66,18 @@ class AxiSlaveToRegTop : public sc_module {
slave.baseAddr(baseAddr);
baseAddr.write(baseAddress);

for (int i = 0; i < numReg; i++) {
#pragma hls_unroll yes
for (int i = 0; i < numControlReg; i++) {
slave.regOut[i](regOut[i]);
}

#ifdef STATUS_REG
#pragma hls_unroll yes
for (int i = 0; i < numStatusReg; i++) {
slave.regIn[i](regIn[i]);
}
#endif

}
};

Expand Down
16 changes: 15 additions & 1 deletion cmod/unittests/axi/AxiSlaveToRegTop/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,19 @@
# limitations under the License.
#

include ../../../cmod_Makefile

include ../../unittests_Makefile
all: sim_reg sim_csreg

sim_reg: $(wildcard *.h) $(wildcard *.cpp) $(wildcard $(CWD)/../../../include/axi/*.h)
$(CC) -o $@ $(CFLAGS) $(USER_FLAGS) -I../../../include $(wildcard *.cpp) $(BOOSTLIBS) $(LIBS)

sim_csreg: $(wildcard *.h) $(wildcard *.cpp) $(wildcard $(CWD)/../../../include/axi/*.h)
$(CC) -o $@ $(CFLAGS) $(USER_FLAGS) -DSTATUS_REG -I../../../include $(wildcard *.cpp) $(BOOSTLIBS) $(LIBS)

run:
./sim_reg
./sim_csreg

sim_clean:
rm -rf *.o sim_*
16 changes: 13 additions & 3 deletions cmod/unittests/axi/AxiSlaveToRegTop/testbench.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ SC_MODULE(testbench) {

typedef AxiSlaveToRegTop::axi_ axi_;
enum {
numReg = AxiSlaveToRegTop::numReg,
numControlReg = AxiSlaveToRegTop::numControlReg,
numStatusReg = AxiSlaveToRegTop::numStatusReg,
numAddrBitsToInspect = AxiSlaveToRegTop::numAddrBitsToInspect
};

Expand All @@ -53,7 +54,10 @@ SC_MODULE(testbench) {
typename axi_::read::template chan<> axi_read;
typename axi_::write::template chan<> axi_write;

sc_signal<NVUINTW(axi::axi4<axi::cfg::standard>::DATA_WIDTH)> regOut[numReg];
sc_signal<NVUINTW(axi::axi4<axi::cfg::standard>::DATA_WIDTH)> regOut[numControlReg];
#ifdef STATUS_REG
sc_signal<NVUINTW(axi::axi4<axi::cfg::standard>::DATA_WIDTH)> regIn[numStatusReg];
#endif

SC_CTOR(testbench)
: master("master"),
Expand All @@ -77,10 +81,16 @@ SC_MODULE(testbench) {
slave.axi_read(axi_read);
slave.axi_write(axi_write);

for (int i = 0; i < numReg; i++) {
for (int i = 0; i < numControlReg; i++) {
slave.regOut[i](regOut[i]);
}

#ifdef STATUS_REG
for (int i = 0; i < numStatusReg; i++) {
slave.regIn[i](regIn[i]);
}
#endif

master.done(done);
SC_THREAD(run);
}
Expand Down
7 changes: 7 additions & 0 deletions hls/unittests/axi/AxiSlaveToRegTop/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,12 @@ SRC_PATH := $(ROOT)/cmod/unittests/axi

CLK_PERIOD := 3

all: reg csreg

include $(ROOT)/hls/hls_Makefile

reg:
catapult -shell -product ultra -file go_hls.tcl

csreg:
env COMPILER_FLAGS="$(COMPILER_FLAGS) STATUS_REG" catapult -shell -product ultra -file go_hls.tcl

0 comments on commit 8f7db63

Please sign in to comment.