Post

AMBA AXI随笔记

反压的slice设计请参照这篇文章

Outstanding Transfer

不需要等待前一笔传输完成就可以发送下一笔操作

outstanding outstanding_n3 outstanding_n4

Outstanding的深度

合理选择好outstanding的深度(也叫做Write issuing capability)可以达到最大的带宽(tradeoff),最佳深度的范围在:$\frac{一次transaction传输所需要的时间}{数据传输需要的时间}$

Take a SoC for example:

  • Suppose BUS@500MHz (period=2ns)
  • Suppose AXI command is burst 8 (ideal: need 8 cycle to transfer)
  • The latency is 200ns

Outstanding Num =200/ (2ns*8) =8~16

Outstanding的设计方法

你需要完整保存aw和ar通道的地址(addr)和控制信息(len,burst,id,size)

比如在D2D的设计中,将5个channel的各个信号打包成packet存储到异步fifo中,然后给LinkLayer读

Out-of-order Transfer

针对于多个从机,返回的response可以不按master访问的顺序。举个例子,master先向slave0发了读请求然后向slave1发了读请求。由于从机速度可能不一样,支持出现先返回slave1的数据然后返回slave0的数据

Out-of-order

这里的bubble就可以先返回CD

排序规则:

  • 所有的transfers in a trapsaction有相同的 ID,相同的ID的传输顺序must be ordered as issued,不同的ID没有顺序要求
  • 写数据的顺序需要和地址顺序一致

Out-of-order的设计

需要使用多个同步Queue的队列来存储支持Out-of-order,每个Queue存储相同的ID,这项工作是由发起者完成的,无需在interconnect中考虑,但是interconnect需要考虑多master的ID填充

queue

ordering model

  1. 对于相同ID而言:
    • 对于同一个外设的事务
      • 必须按照它们被发出的顺序到达外设设备,无论这些事务的地址如何。
    • 对于内存事务
      • 使用相同或重叠地址的事务,必须按照它们被发出的顺序到达内存。
  2. 如果两个具有相同 ID 的事务方向不同(如一个是读,一个是写),但需要有顺序关系:
    • 主设备必须等待第一个事务的响应,然后再发出第二个事务。

拓展一个问题

这里会存在一个switch死锁问题(Interconnect需要考虑的):返回的(B/R)由于需要按issue的顺序返回,提前返回的需要存到Queue中,当Queue满了还没有获得最开始发送的响应,就堵死了。我们需要预留空位(stall住master的发送),不让Queue占满,直到期待的响应发回,再放开stall

Interleaving Transfer

写Interleaving已经在AXI4中移除了

读Interleaving是在乱序(Out-of-order Transfer)的基础上支持不同ID间数据之间的乱序

interleaving

拓展

下面这张图解释了write interleaving出现死锁的情况 deadlock

WrapAddr地址变化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
assign OffsetAddr = {12{ASIZE[1:0] == 2'b00}} & AddrIn |
                    {12{ASIZE[1:0] == 2'b01}} & {1'b0 ,AddrIn[11:1]} |
                    {12{ASIZE[1:0] == 2'b10}} & {2'b00 ,AddrIn[11:2]} |
                    {12{ASIZE[1:0] == 2'b11}} & {3'b000, AddrIn[11:3]} ;

assign IncrAddr = OffsetAddr + 1'b1;
assign WrapAddr[11:4] = OffsetAddr[11:4];
assign WrapAddr[3:0] = (ALEN & IncrAddr[3:0]) | (~ALEN & OffsetAddr[3:0]);
//wrap的burst_len只能为2 4 8 16,所以只改变低四位的地址
//~ALEN & OffsetAddr[3:0]保证在边界内,ALEN & IncrAddr[3:0]保证地址正常incr
assign MuxAddr = (ABURST == 2'b01) ? IncrAddr: WrapAddr;

assign CalcAddr = {12{ASIZE[1:0] ==2'b00}} & MuxAddr |
				  {12{ASIZE[1:0]==2'b01}} & {MuxAddr[10:0], 1'b0} |
				  {12{ASIZE[1:0] =2'b10}} & {MuxAddr[9:0], 2'b00} |
				  {12{ASIZE[1:0] == 2'b11}} & {MuxAddr[8:0], 3'b000};

assign AddrOut = CalcAddr;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//也可以这么写:
val CurAddr     = IO(Input(UInt(AddrWidth.W)))
val Len         = IO(Input(UInt(8.W)))
val Size        = IO(Input(UInt(3.W)))
val Burst       = IO(Input(UInt(2.W)))
val NextAddr    = IO(Output(UInt(AddrWidth.W)))

val iCurAddr    = CurAddr(11, 0)
val iNextAddr   = Wire(UInt(12.W))

// Size Numeric Value
val iSize       = MuxLookup(Size, 0.U, (0 to 7).map(x => x.U -> x.U))
// Word Address
val wordAddress = iCurAddr >> iSize
// INCR Next Addr
val incrNextAddr = (wordAddress + 1.U) << iSize
// WRAP Next Addr
val wrapBound   = Cat(wordAddress(11, 4), (wordAddress(3, 0) & ~Len(3,0))) << iSize
val totSize     = (Len(3,0) +& 1.U) << iSize
val wrapNextAddr = Mux(incrNextAddr === wrapBound + totSize, wrapBound, incrNextAddr)

iNextAddr := MuxLookup(Burst, CurAddr, Seq(0.U -> CurAddr, 1.U -> incrNextAddr, 2.U -> wrapNextAddr))
NextAddr  := Cat(CurAddr(AddrWidth-1, 12), iNextAddr)

在AXI中,当地址增加到边界的时候,地址要回到起始值,公式为:

wrapaddr=(int( start_addr / (size * burst))) * (size * burst)

当地址等于wrapaddr+(size*burst)的时候, 地址回到wrapaddr

比如:size为4,burst_len为4(burst_len必须为2 4 8 16之一)。起始地址为 24(这个必须整除size)

则地址为:24 28 16 20 这个地方,16是关键点,当地址等于32的时候,地址回到起始int(24/16)*16=16

如何用代码实现这个功能呢?如下是个参考:

  1. 24/4=6
  2. 取低4bit做为起始地址a
  3. 将地址加1作为b
  4. len为(1 3 7 15)的二进制(AXI中规定长度为len+1)
则nextaddr = (len & b)(~len & a);

讲解一个AXI2SRAM的设计例子

下面两张图展示了读写的时序,下面讲解一下各个信号之间的关系

axi2sram_write axi2sram_read axi2sram_arbiter

感觉arbiter的时序图有点问题,后面用到再说

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//-------------------- AXItoSRAM --------------------//
SRAM.ADDR  := Mux(WriteEngine.Ready, WriteEngine.Addr, ReadEngine.Addr)
SRAM.CEn   := ~((wrIE.Ready & wrIE.Valid) | (rdIE.Ready & rdIE.Valid))
SRAM.WDATA := wrIE.Data
SRAM.WEn   := ~(wrIE.Ready & wrIE.Valid)
SRAM.WBEn  := ~wrIE.Strb

//-------------------- WriteEngine --------------------//
val Ready       = IO(Input(Bool())) // for arbiter
val Valid       = IO(Output(Bool()))// for arbiter, Valid := iValid
val Addr        = IO(Output(UInt(AddrWidth.W)))// for SRAM
val Data        = IO(Output(UInt(DataWidth.W)))// for SRAM
val Strb        = IO(Output(UInt((DataWidth/8).W)))// for SRAM


val iAddrValidNext= Mux(S_AXI.AW.fire,
                    true.B,
                    Mux(S_AXI.B.fire,
                    false.B, iAddrValid))
iAddrValid := iAddrValidNext


S_AXI.AW.ready  := ~iAddrValid
S_AXI.W.ready   := iAddrValid & Ready //这个Ready是Arbiter给的arbiter.WrReady

iValid := iAddrValid & S_AXI.W.valid
Valid  := iValid
//iLast := S_AXI.W.bits.LAST
//Data  := S_AXI.W.bits.DATA
val iBValidNext = Mux(iValid & iLast & Ready,
                  true.B,
                  Mux(iBValid & S_AXI.B.ready,
                  false.B, iBValid))
iBValid         := iBValidNext
S_AXI.B.valid   := iBValid

//在aw.fire~b.fire期间,保持采样到的iLen(aw.len)、iSize、iBurst
NextAddr := SramAddrGen(AddrWidth, iAddr, iLen, iSize, iBurst)
val iAddrNext = Mux(iAwReady & S_AXI.AW.valid,
                S_AXI.AW.bits.ADDR,
                Mux(iValid & Ready,
                NextAddr,iAddr))
iAddr := iAddrNext
Addr  := iAddr

//-------------------- ReadEngine --------------------//
val Ready       = IO(Input(Bool()))
val Valid       = IO(Output(Bool()))
val Addr        = IO(Output(UInt(AddrWidth.W)))//for SRAM
val Data        = IO(Input(UInt(DataWidth.W)))//from SRAM to axi

val iRValidNext=Mux(iValid & Ready,
                true.B,
                Mux(iRValid & ~S_AXI.R.ready,
                true.B,
                Mux(iRValid & S_AXI.R.ready,
                false.B,iRValid)))
iRValid         := iRValidNext
S_AXI.R.valid   := iRValid

val iRLastNext= Mux(iValid & iLast & Ready,
                true.B,
                Mux(iRValid & S_AXI.R.ready,
                false.B, iRLast))
iRLast              := iRLastNext
S_AXI.R.bits.LAST   := iRLast

val iValidNext= Mux(S_AXI.AR.fire,
                true.B,
                Mux(iLast & Ready,
                false.B, iValid))
iValid  := iValidNext
Valid   := iValid

val iCounterNext=Mux(S_AXI.AR.fire,
                 S_AXI.AR.bits.LEN,
                 Mux(iValid & Ready,
                 iCounter - 1.U, iCounter))
iCounter    := iCounterNext
iLast       := iCounter === 0.U

//-------------------- SramArbiter --------------------//
//这里的仲裁感觉不是很好,并不需要时时刻刻都在仲裁
//只需要在读完和写完的时候change
// Write Channel
val WrValid     = IO(Input(Bool()))
val WrReady     = IO(Output(Bool()))
// Read Channel
val RdValid     = IO(Input(Bool()))
val RdReady     = IO(Output(Bool()))

switch (Cat(WrValid, RdValid)) {
    is (0.U) {
        choiceNext  := choice
    }
    is (1.U) {
        choiceNext  := false.B
    }
    is (2.U) {
        choiceNext  := true.B
    }
    is (3.U) {
        choiceNext  := ~choice
    }
}
WrReady     := choice
RdReady     := ~choice

AXI VIP

JTAG to AXI Master IP可以驱动AXI Slave,这样就不需要硬核的参与,烧录比特流之后在终端使用Tcl控制

该IP不是用来仿真的,只有在使用Vivado逻辑分析仪Debug的时候,才能利用该IP进行动态实时交互

jtag2axi

AXI 事务创建范例

hw

事务创建范例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#--------------------------- AXI4 事务创建范例 -------------------------#
#-------- 1. 创建8个32bit数据的AXI突发写事务(32位地址) -------- 
create_hw_axi_txn wr_txn [get_hw_axis hw_axi_1] -address 00000000 -data\
{11111111_22222222_33333333_44444444_55555555_66666666_77777777_88888888} -len 8\
-size 32 -type write

//-------- 2. 创建8个32bit数据的AXI突发读事务(32位地址) -------- 
create_hw_axi_txn rd_txn [get_hw_axis hw_axi_1] -address 00000000 -len 8 -size 32\
-type read

#--------------------------- AXI4-Lite 事务创建范例 -------------------------#
create_hw_axi_txn wr_txn_lite [get_hw_axis hw_axi_1] -address 00000000 -data
12345678 -type write

create_hw_axi_txn rd_txn_lite [get_hw_axis hw_axi_1] -address 00000000 -type read

运行事务及删除事务

注:不能出现重名事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#完整写法(我也不知道为什么要这么写。。。,如果有人知道望指教)
run_hw_axi [get_hw_axi_txns wr_txn ]
run_hw_axi [get_hw_axi_txns rd_txn]
run_hw_axi [get_hw_axi_txns wr_txn64 ]
run_hw_axi [get_hw_axi_txns rd_txn64 ]
run_hw_axi [get_hw_axi_txns wr_txn_lite ]
run_hw_axi [get_hw_axi_txns rd_txn_lite ]
#简略写法(我这样写也行)
run_hw_axi  wr_txn 
run_hw_axi  rd_txn 
run_hw_axi  wr_txn64 
run_hw_axi  rd_txn64 
run_hw_axi  wr_txn_lite 
run_hw_axi  rd_txn_lite 


#完整写法(我也不知道为什么要这么写。。。,如果有人知道望指教)
delete_hw_axi_txn [get_hw_axi_txns wr_txn ]
delete_hw_axi_txn [get_hw_axi_txns rd_txn]
delete_hw_axi_txn [get_hw_axi_txns wr_txn64 ]
delete_hw_axi_txn [get_hw_axi_txns rd_txn64 ]
delete_hw_axi_txn [get_hw_axi_txns wr_txn_lite ]
delete_hw_axi_txn [get_hw_axi_txns rd_txn_lite ]
#简略写法(我这样写也行)
delete_hw_axi_txn wr_txn 
delete_hw_axi_txn rd_txn 
delete_hw_axi_txn wr_txn64 
delete_hw_axi_txn rd_txn64 
delete_hw_axi_txn wr_txn_lite 
delete_hw_axi_txn rd_txn_lite 

一个调试的Tcl脚本示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#注:这里使用的是AXI-Lite接口
#**************W/R CONTROL**************
set data_list ""
set num 1

proc ReadReg { address } {
global data_list
global num
create_hw_axi_txn read_txn [get_hw_axis hw_axi_1] -address $address -type read
run_hw_axi  read_txn
set read_value [lindex [report_hw_axi_txn  read_txn] 1];
append data_list [format %3i $num] 
append data_list [format %3s r]
append data_list [format %10s $address]
append data_list [format "%10s\n" $read_value]
delete_hw_axi_txn read_txn
incr num
set tmp 0x
append tmp $read_value
return $tmp
}

proc WriteReg { address data } {
global data_list
global num
create_hw_axi_txn write_txn [get_hw_axis hw_axi_1] -address $address -data $data -type write
run_hw_axi  write_txn
set write_value [lindex [report_hw_axi_txn  write_txn] 1];
append data_list [format %3i $num] 
append data_list [format %3s w]
append data_list [format %10s $address]
append data_list [format "%10s\n" $write_value]
delete_hw_axi_txn write_txn
incr num
}

#**************USER**************
WriteReg 00000000 12345678
if {[ReadReg 00000000] == 0x00005678} {
  puts "**************\n\
  write success!\n\
  **************\n"
} else {
  puts "**************\n\
  write fail!\n\
  **************\n"
}

#**************IO CONTROL**************
set currentTime [clock seconds]
set ctime "The time is: \
[clock format $currentTime -format %D] \
[clock format $currentTime -format  %H:%M:%S] \n"
#需编辑自定义文件路径
set file_name "D:/data.txt" 
set fp [open $file_name a+] 
puts $fp $ctime
puts $fp $data_list
close $fp
set fp [open $file_name r]
set file_data [read $fp]
puts $file_data
close $fp

这是一个用什么VS来用c的api调试替换tcl的教程,但是我觉得没必要


附录-信号释意

  • AxPROT[2:0] :给CPU用的,AxPROT[0]特权模式;AxPROT[1]Non-secure;AxPROT[2] Instruction
  • AxCACHE[3:0]: [WA:RA:C/M:B] write/read allocate;Cacheable(AXI3)/Modifiable(AXI4);Bufferable (Modifiable:The burst and transfer characteristics can change between source and destination)
  • AxLOCK:normal和exclusive
    • locked access:Blocks(阻止) access from all other masters to the slave.(AXI4取消了
    • exclusive access:阻止其他对memory region in the slave的访问
    • The exclusive access mechanism enables the implementation of semaphore type operations without requiring the bus to remain locked to a particular master for the duration of the operation.
    • semaphore:Requires slave hardware support.Exclusive Access Monitor.对share的region进行lock
    • exclusive:先进行读某个地址空间的地址,获取lock之后可以写,写成功会清除lock
  • AxQOS:[3:0],Encoding of 0xF is highest priority 可以用作arbiters和slave对访问的顺序进行调整
  • AxREGION:[3:0],Usage Models:
    • 可以借助master的信号来区分访问memory的哪一块region,简化slave对地址的译码操作
    • slave可以限制不同区域的访问等行为
  • AXI3升级到AXI4
    • 从之前的16长度的burst support (up to 256 beats. i.e.AxLEN is 0-255)
    • 去除了write data interleaving和LOCK
  • AXI-Lite:和AXI4的区别
    • All accesses are Non-modifiable, Non-bufferable,不支持Exclusive
    • data bus必须为32 or 64 的full width,且burst length为1
  • AXIStream 没看
  • Slaves建议设计的ID width可配置,因为master可能的宽度是不确定的
This post is licensed under CC BY 4.0 by the author.