들어가기 앞서...
" AXI는 말 그대로 Interface이기 때문에 실체라고 할게 딱히 없습니다.
Master와 Slave를 연결하는 Bus의 규격을 ARM에서 정해놓은 것인데요,
저와 같은 학부생들은 Master와 Slave를 구하기 어렵습니다.
BFM(Bus Functional Model)을 짜서 Bus의 동작을 시뮬레이션해도 되지만
그건 조금 어려워서 아직 공부 중 입니다.
그래서 제멋대로 AXI Slave를 Verilog로 짜보겠습니다.
목적은 오로지 AMBA를 복습하고 기능을 직접 확인해보기 위함입니다! "
조금 더 공부한 후에 정확한 코드를 올려보겠습니다.
앞서 3개의 게시물을 통해 AXI Protocol에 대해 알아보았습니다.
Signal Description: https://metastable.tistory.com/44
Write Transaction: https://metastable.tistory.com/45
Read Transaction: https://metastable.tistory.com/46
이번 게시물에서는 코드를 잘 정리해서 Simple AXI Slave로 만들어보겠습니다.
다시 한 번 말씀드리지만 동작만 확인하기 위해 마음대로 짠 코드이니 복사해가는 걸 권장드리진 않습니다..!
여러분도 직접 짜본 후에,
직접 짠 코드와 git에서 받은 코드의 시뮬레이션을 비교해 보시는걸 추천드립니다.
Slave는 AXI Protocol의 동작을 시뮬레이션으로 확인해보기 위해 임의로 만든 것이기 때문에
아무 기능을 하지 않는다는 점 참고해주세요.
물론 합성 불가능합니다.
Master는 따로 만들지 않았고 Testbench에서 직접 넣어주었습니다.
나중에 Master를 만들게 되면 그때 다시 공유하겠습니다.
Verilog Code
DUT
`timescale 1ns / 1ps
module axi_slave(
input i_clk,
input i_resetn,
/************* Write Address Channel ************/
input [3:0] i_awid,
input [7:0] i_awaddr,
input [3:0] i_awlen,
input [2:0] i_awsize,
input [1:0] i_awburst,
input i_awvalid,
output reg o_awready,
/************** Write Data Channel **************/
input [3:0] i_wid,
input [15:0] i_wdata,
input i_wlast,
input i_wvalid,
output reg o_wready,
/************ Write Response Channel *************/
input i_bready,
output reg [3:0] o_bid,
output reg [1:0] o_bresp,
output reg o_bvalid,
/************* Read Address Channel **************/
input [3:0] i_arid,
input [7:0] i_araddr,
input [3:0] i_arlen,
input [2:0] i_arsize,
input [1:0] i_arburst,
input i_arvalid,
output reg o_arready,
/************** Read Data Channel ***************/
input i_rready,
output reg [3:0] o_rid,
output reg [15:0] o_rdata,
output reg o_rlast,
output reg o_rvalid,
output reg [1:0] o_rresp,
/***************** For Test *********************/
input test_awready,
input test_wready,
input [1:0] test_bresp,
input test_arready,
input test_rvalid,
input [1:0] test_rresp
);
reg [7:0] r_waddr;
reg [3:0] r_wburstn;
reg [15:0] r_wdata;
reg r_wlast;
reg [7:0] r_raddr;
reg [3:0] r_rburstn;
reg [15:0] r_rdata;
reg r_rlast;
reg r_waddr_on;
reg r_wdata_on;
reg [4:0] awlen_cnt;
reg r_raddr_on;
reg r_rdata_on;
reg [4:0] arlen_cnt;
reg [15:0] mem [0:15];
/*-------------- handshake -------------------*/
wire aw_handshake = i_awvalid && o_awready;
wire w_handshake = i_wvalid && o_wready;
wire b_handshake = i_bready && o_bvalid;
wire ar_handshake = i_arvalid && o_arready;
wire r_handshake = i_rready && o_rvalid;
always @ (posedge i_clk or negedge i_resetn) begin //for control slave
if (!i_resetn) begin
o_awready <= 1'd0;
o_wready <= 1'd0;
o_arready <= 1'd0;
end
else if (test_awready) begin
o_awready <= 1'd1;
end
else if (test_wready) begin
o_wready <= 1'd1;
end
else if (test_arready) begin
o_arready <= 1'd1;
end
else begin
o_awready <= 1'd0;
o_wready <= 1'd0;
o_arready <= 1'd0;
end
end
always @ (posedge i_clk or negedge i_resetn) begin // count write burst length
if (!i_resetn) begin
awlen_cnt <= 5'd0;
end
else if (awlen_cnt >= r_wburstn) begin
awlen_cnt <= 5'd0;
end
else if (r_wlast) begin
awlen_cnt <= 5'd0;
end
else if (r_waddr_on && r_wdata_on) begin
awlen_cnt <= awlen_cnt + 5'd1;
end
else begin
awlen_cnt <= awlen_cnt;
end
end
always @ (posedge i_clk or negedge i_resetn) begin //count read burst length
if (!i_resetn) begin
arlen_cnt <= 5'd0;
end
else if (arlen_cnt >= r_rburstn) begin
arlen_cnt <= 5'd0;
end
else if (o_rlast) begin
arlen_cnt <= 5'd0;
end
else if (r_handshake) begin
arlen_cnt <= arlen_cnt + 5'd1;
end
else begin
arlen_cnt <= arlen_cnt;
end
end
/*---------------------- Write Address --------------------------*/
always @ (posedge i_clk or negedge i_resetn) begin // write address
if (!i_resetn) begin
r_waddr <= 8'd0;
end
else if(aw_handshake) begin
r_waddr <= i_awaddr;
r_waddr_on <= 1'd1;
end
else begin
r_waddr <= r_waddr;
end
end
always @ (posedge i_clk or negedge i_resetn) begin // awlen
if (!i_resetn) begin
r_wburstn <= 8'd0;
end
else if(aw_handshake) begin
r_wburstn <= i_awlen;
end
else begin
r_wburstn <= r_wburstn;
end
end
/*----------------------- Write Data ---------------------------*/
always @ (posedge i_clk or negedge i_resetn) begin
if (!i_resetn) begin
r_wdata <= 16'd0;
end
else if (w_handshake) begin
r_wdata <= i_wdata;
r_wdata_on <= 1'd1;
end
else begin
r_wdata <= r_wdata;
end
end
always @ (posedge i_clk) begin
if (r_waddr_on && r_wdata_on) begin
mem[r_waddr + awlen_cnt] <= r_wdata;
r_wdata_on <= 1'd0;
if (i_wlast) begin
r_waddr_on <= 1'd0;
end
end
end
always @ (posedge i_clk or negedge i_resetn) begin //last data
if (!i_resetn) begin
r_wlast <= 1'd0;
end
else if (i_wlast && w_handshake) begin
r_wlast <= i_wlast;
end
else begin
r_wlast <= r_wlast;
end
end
/*----------------------- Write Response ---------------------------*/
always @ (posedge i_clk or negedge i_resetn) begin
if (!i_resetn) begin
//o_bresp <= 2'd0;
end
else if (r_wlast) begin
case (test_bresp)
2'b00: o_bresp = 2'b00; //OKAY
2'b01: o_bresp = 2'b01; //EXOKAY
2'b10: o_bresp = 2'b10; //SLVERR
2'b11: o_bresp = 2'b11; //DECERR
default: o_bresp = 2'd00;
endcase
end
else begin
o_bresp <= o_bresp;
end
end
always @ (posedge i_clk or negedge i_resetn) begin
if (!i_resetn) begin
o_bvalid <= 1'd0;
end
else if (r_wlast) begin
o_bvalid <= 1'd1;
end
else begin
o_bvalid <= 1'd0;
end
end
/*----------------------- Read Address ---------------------------*/
always @ (posedge i_clk or negedge i_resetn) begin
if (!i_resetn) begin
r_raddr <= 8'd0;
end
else if (ar_handshake) begin
r_raddr <= i_araddr;
r_raddr_on <= 1'd1;
end
else begin
r_raddr <= r_raddr;
end
end
always @ (posedge i_clk or negedge i_resetn) begin
if (!i_resetn) begin
r_rburstn <= 4'd0;
end
else if (ar_handshake) begin
r_rburstn <= i_arlen;
end
else begin
r_rburstn <= r_rburstn;
end
end
/*----------------------- Read Data ---------------------------*/
always @ (posedge i_clk or negedge i_resetn) begin
if (!i_resetn) begin
o_rvalid <= 1'd0;
end
else if (test_rvalid && r_raddr_on) begin
o_rvalid = 1'd1;
end
else begin
o_rvalid <= 1'd0;
end
end
always @ (posedge i_clk) begin
if (test_rvalid && r_raddr_on) begin
o_rdata <= mem[r_raddr + arlen_cnt];
end
end
always @ (posedge i_clk or negedge i_resetn) begin
if (!i_resetn) begin
o_rlast <= 1'd0;
end
else if (test_rvalid && r_raddr_on && (arlen_cnt == i_arlen-1)) begin
o_rlast <= 1'd1;
if (r_handshake) begin
r_raddr_on <= 1'd0;
o_rlast <= 1'd0;
end
end
end
always @ (posedge i_clk or negedge i_resetn) begin
if (!i_resetn) begin
//o_rresp <= 2'd0;
end
else if (o_rlast && r_handshake) begin
o_rresp <= test_rresp;
end
else begin
o_rresp <= o_rresp;
end
end
endmodule
Testbench
`timescale 1ns / 1ps
module tb_axi_slave();
reg i_clk;
reg i_resetn;
/************* Write Address Channel ************/
reg [3:0] i_awid;
reg [7:0] i_awaddr;
reg [3:0] i_awlen;
reg [2:0] i_awsize;
reg [1:0] i_awburst;
reg i_awvalid;
wire o_awready;
/************** Write Data Channel **************/
reg [3:0] i_wid;
reg [15:0] i_wdata;
reg i_wlast;
reg i_wvalid;
wire o_wready;
/************ Write Response Channel *************/
reg i_bready;
wire [3:0] o_bid;
wire [1:0] o_bresp;
wire o_bvalid;
/************* Read Address Channel **************/
reg [3:0] i_arid;
reg [7:0] i_araddr;
reg [3:0] i_arlen;
reg [2:0] i_arsize;
reg [1:0] i_arburst;
reg i_arvalid;
wire o_arready;
/************** Read Data Channel ***************/
reg i_rready;
wire [3:0] o_rid;
wire [15:0] o_rdata;
wire o_rlast;
wire o_rvalid;
wire [1:0] o_rresp;
/***************** For Test *********************/
reg test_awready;
reg test_wready;
reg [1:0] test_bresp;
reg test_arready;
reg test_rvalid;
reg [1:0] test_rresp;
axi_slave axi_slave ( .i_clk (i_clk),
.i_resetn (i_resetn),
.i_awid (i_awid),
.i_awaddr (i_awaddr),
.i_awlen (i_awlen),
.i_awsize (i_awsize),
.i_awburst (i_awburst),
.i_awvalid (i_awvalid),
.o_awready (o_awready),
.i_wid (i_wid),
.i_wdata (i_wdata),
.i_wlast (i_wlast),
.i_wvalid (i_wvalid),
.o_wready (o_wready),
.i_bready (i_bready),
.o_bid (o_bid),
.o_bresp (o_bresp),
.o_bvalid (o_bvalid),
.i_arid (i_arid),
.i_araddr (i_araddr),
.i_arlen (i_arlen),
.i_arsize (i_arsize),
.i_arburst (i_arburst),
.i_arvalid (i_arvalid),
.o_arready (o_arready),
.i_rready (i_rready),
.o_rid (o_rid),
.o_rdata (o_rdata),
.o_rlast (o_rlast),
.o_rvalid (o_rvalid),
.o_rresp (o_rresp),
.test_awready (test_awready),
.test_wready (test_wready),
.test_bresp (test_bresp),
.test_arready (test_arready),
.test_rvalid (test_rvalid),
.test_rresp (test_rresp)
);
always #5 i_clk = ~i_clk;
initial begin
i_clk = 1'd0; i_resetn = 1'd1;
test_rresp = 2'd0;
test_bresp = 2'd0;
#1
i_resetn = 1'd0;
#1
i_resetn = 1'd1;
#10
i_awaddr = 8'd8;
i_awlen = 4'd4;
i_awvalid = 1'd1;
#10
test_awready = 1'd1;
#20
test_awready = 1'd0;
i_awvalid = 1'd0;
#20
i_wdata = 16'd37;
i_wvalid = 1'd1;
test_wready = 1'd1;
#20
test_wready = 1'd0;
i_wvalid = 1'd0;
#10
i_wdata = 16'd38;
i_wvalid = 1'd1;
test_wready = 1'd1;
#20
test_wready = 1'd0;
i_wvalid = 1'd0;
#10
i_wdata = 16'd39;
i_wvalid = 1'd1;
test_wready = 1'd1;
#20
test_wready = 1'd0;
i_wvalid = 1'd0;
#10
i_wdata = 16'd40;
i_wvalid = 1'd1;
i_wlast = 1'd1;
test_wready = 1'd1;
#20
test_wready = 1'd0;
i_wvalid = 1'd0;
i_wlast = 1'd0;
#10
i_bready = 1'd1;
#30
i_araddr = 8'd8;
i_arlen = 4'd4;
i_arvalid = 1'd1;
#10
test_arready = 1'd1;
#20
test_arready = 1'd0;
i_arvalid = 1'd0;
#20
test_rvalid = 1'd1;
#20
i_rready = 1'd1;
#10
i_rready = 1'd0;
#10
i_rready = 1'd1;
#10
i_rready = 1'd0;
#10
i_rready = 1'd1;
#10
i_rready = 1'd0;
#10
i_rready = 1'd1;
#10
i_rready = 1'd0;
#30
$finish;
end
endmodule
Simulation
Signal이 많아서 한눈에 보기 어려운 편 입니다.
이걸 timing diagram으로 간단하게 나타내면
- Write Transaction
- Read Transaction
결과가 이런 식으로 나왔다고 보시면 됩니다.
이번에 설계한 AXI Protocol은 burst mode만 지원합니다.
추후에 multiple transaction과 out-of-order 기능을 지원하는 AXI를 설계해서 공유하도록 하겠습니다.
'Digital Design > SoC' 카테고리의 다른 글
[SoC] Timing Violation (Setup/Hold/Skew/Jitter/해결법) (2) | 2023.04.14 |
---|---|
[SoC] DMA Controller (2) | 2023.03.06 |
[AMBA] AXI Protocol 설계(Verilog) - ③ Read Transaction (1) | 2023.02.26 |
[AMBA] AXI Protocol 설계(Verilog) - ② Write Transaction (0) | 2023.02.24 |
[AMBA] AXI Protocol 설계(Verilog) - ① Signal Descriptions (0) | 2023.02.24 |
댓글