현재 ARM의 DMAC Reference Manual을 보고 DMAC Arbiter를 설계하는 프로젝트를 진행하고 있습니다.
이게 ARM에서 추천한 DMAC 동작 메뉴얼을 보고 만든거라 DMAC Arbiter라고 하는건데,
사실 조금씩 수정해서 아무데나 범용성 있게 사용해도 될 것 같습니다.
이제 마지막 관문입니다!
앞서 Fixed Priority와 LRG 기반 Round-Robin Arbiter를 설계했었습니다.
오늘은 이 두 아비터를 merge한 top block을 업로드하려고 합니다.
참고로 지난 시간에 설계한 LRG Round-Robin Arbiter는 시뮬레이션을 좀 더 편하게 보려고
한 Master에게 1clk씩만 사용권을 부여했었습니다.
하지만 실제로는 여러 클락동안 사용해야겠죠?
따라서 REQ가 내려가면 Master의 bus 사용이 끝났다고 받아들이고 GNT를 중단하도록 수정했습니다.
※ 티스토리 코드블럭을 이용하면 띄어쓰기가 좀 바뀌어서 가독성이 떨어질 수 있다는 점 양해 부탁드려요!
Block Diagram
먼저 ARM DMAC Technical Reference Manual에 나와있는 상태도를 보겠습니다.
- State Diagram
위의 상태도를 기반으로 Fixed Priority Arbiter와 Round-Robin Arbiter를 순차적으로 배치하겠습니다.
- Block Diagram
Block Diagram으로는 이렇게 나타낼 수 있습니다.
우선 4개의 Master가 있다고 가정하였습니다.
이 Master에서 REQ가 들어오면 먼저 Fixed Priority Arbiter로 들어갑니다.
여기서 우선순위를 참고하여 GNT할 Master를 선택해줍니다.
만약 우선순위가 같다면 두 번째인 Round-Robin Arbiter로 들어갑니다.
여기서는 History QUEUE를 참고하여 GNT할 Master를 결정합니다.
최종적으로 GNT하게 되면 shift_case와 shift_trigger 신호를 이용해 History QUEUE를 업데이트합니다.
여기까지가 arbiter_top의 개요이고 이제 코드를 보겠습니다.
※ 2023.05.16 : Block Diagram 수정
좀 더 예쁘게 바꿔보았습니다.
Verilog Code
DUT
- dmac_arbiter_top.v
`timescale 1ns / 1ps
module dmac_arbiter_top(
input i_clk,
input i_reset,
input i_req0,
input i_req1,
input i_req2,
input i_req3,
output o_gnt0,
output o_gnt1,
output o_gnt2,
output o_gnt3
);
wire w_m0;
wire w_m1;
wire w_m2;
wire w_m3;
arbiter_fixed_priority arbiter_fp (.i_clk (i_clk),
.i_reset (i_reset),
.i_req0 (i_req0),
.i_req1 (i_req1),
.i_req2 (i_req2),
.i_req3 (i_req3),
.o_gnt0 (w_m0),
.o_gnt1 (w_m1),
.o_gnt2 (w_m2),
.o_gnt3 (w_m3) );
rrarbiter_lrg arbiter_lrg (.i_clk (i_clk),
.i_reset (i_reset),
.i_req0 (w_m0),
.i_req1 (w_m1),
.i_req2 (w_m2),
.i_req3 (w_m3),
.o_gnt0 (o_gnt0),
.o_gnt1 (o_gnt1),
.o_gnt2 (o_gnt2),
.o_gnt3 (o_gnt3) );
endmodule
- arbiter_fixed_priority.v
`timescale 1ns / 1ps
module arbiter_fixed_priority(
input i_clk,
input i_reset,
input i_req0,
input i_req1,
input i_req2,
input i_req3,
output reg o_gnt0,
output reg o_gnt1,
output reg o_gnt2,
output reg o_gnt3
);
//PRIORITY: M2 > M1 = M2 = M3
always @ (posedge i_clk or negedge i_reset) begin
if (!i_reset) begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b0000;
end
else if (i_req2) begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b0100;
end
else if (i_req0 || i_req1 || i_req3) begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= {i_req3, i_req2, i_req1, i_req0};
end
else begin //IF NO REQUEST
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b0000;
end
end
endmodule
Master의 우선순위는 M2 > M0 = M1 = M3로 하였습니다.
- rrarbiter_lrg.v
`timescale 1ns / 1ps
module rrarbiter_lrg (
input i_clk,
input i_reset,
input i_req0,
input i_req1,
input i_req2,
input i_req3,
output reg o_gnt0,
output reg o_gnt1,
output reg o_gnt2,
output reg o_gnt3
);
parameter [3:0] m_0 = 4'b0001;
parameter [3:0] m_1 = 4'b0010;
parameter [3:0] m_2 = 4'b0100;
parameter [3:0] m_3 = 4'b1000;
reg [3:0] queue [0:3];
reg [1:0] shift_case;
reg shift_trigger;
reg req_delay0;
reg req_delay1;
reg req_delay2;
reg req_delay3;
wire [3:0] req = {i_req3, i_req2, i_req1, i_req0};
wire [3:0] req_delay = {req_delay3, req_delay2, req_delay1, req_delay0};
assign req0_fall = ~i_req0 & req_delay0;
assign req1_fall = ~i_req1 & req_delay1;
assign req2_fall = ~i_req2 & req_delay2;
assign req3_fall = ~i_req3 & req_delay3;
always @ (posedge i_clk or negedge i_reset) begin
if (!i_reset) begin
req_delay0 <= 1'b0;
end
else begin
req_delay0 <= i_req0;
end
end
always @ (posedge i_clk or negedge i_reset) begin
if (!i_reset) begin
req_delay1 <= 1'b0;
end
else begin
req_delay1 <= i_req1;
end
end
always @ (posedge i_clk or negedge i_reset) begin
if (!i_reset) begin
req_delay2 <= 1'b0;
end
else begin
req_delay2 <= i_req2;
end
end
always @ (posedge i_clk or negedge i_reset) begin
if (!i_reset) begin
req_delay3 <= 1'b0;
end
else begin
req_delay3 <= i_req3;
end
end
/*
wire [3:0] queue0 = queue[0];
wire [3:0] queue1 = queue[1];
wire [3:0] queue2 = queue[2];
wire [3:0] queue3 = queue[3];
*/
/* history queue
* _____3________2_______1_______0________
| recently | ... ..| .. .. | granted |
| granted | ..->..| ..->. | long time |
|__________|_______|_______|___ago_____|
1. REQn input
2. Is REQn in queue[0]? (!(queue[0] & req == 0))
Yes: GNT, update(shift) history queue
No: go to queue[1]
3. Is REQn in queue[1]?
Yes: GNT, update(shift) history queue
No: go to queue[2]
.
.
**Initial Value: queue <= {M3, M2, M1, M0}
*/
always @ (posedge i_clk or negedge i_reset) begin
if (!i_reset) begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b0000;
shift_case <= 2'b00;
shift_trigger <= 1'b0;
end
else if (!((queue[0] & req_delay) == 4'b0000)) begin
case (queue[0])
m_0: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b0001;
if (req0_fall) begin
shift_case <= 2'b11;
shift_trigger <= ~shift_trigger;
end
else begin
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
end
m_1: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b0010;
if (req1_fall) begin
shift_case <= 2'b11;
shift_trigger <= ~shift_trigger;
end
else begin
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
end
m_2: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b0100;
if (req2_fall) begin
shift_case <= 2'b11;
shift_trigger <= ~shift_trigger;
end
else begin
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
end
m_3: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b1000;
if (req3_fall) begin
shift_case <= 2'b11;
shift_trigger <= ~shift_trigger;
end
else begin
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
end
default: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= {o_gnt3, o_gnt2, o_gnt1, o_gnt0};
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
endcase
end
else if (!((queue[1] & req_delay) == 4'b0000)) begin
case (queue[1])
m_0: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b0001;
if (req0_fall) begin
shift_case <= 2'b10;
shift_trigger <= ~shift_trigger;
end
else begin
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
end
m_1: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b0010;
if (req1_fall) begin
shift_case <= 2'b10;
shift_trigger <= ~shift_trigger;
end
else begin
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
end
m_2: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b0100;
if (req2_fall) begin
shift_case <= 2'b10;
shift_trigger <= ~shift_trigger;
end
else begin
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
end
m_3: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b1000;
if (req3_fall) begin
shift_case <= 2'b10;
shift_trigger <= ~shift_trigger;
end
else begin
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
end
default: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= {o_gnt3, o_gnt2, o_gnt1, o_gnt0};
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
endcase
end
else if (!((queue[2] & req_delay) == 4'b0000)) begin
case (queue[2])
m_0: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b0001;
if (req0_fall) begin
shift_case <= 2'b01;
shift_trigger <= ~shift_trigger;
end
else begin
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
end
m_1: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b0010;
if (req1_fall) begin
shift_case <= 2'b01;
shift_trigger <= ~shift_trigger;
end
else begin
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
end
m_2: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b0100;
if (req2_fall) begin
shift_case <= 2'b01;
shift_trigger <= ~shift_trigger;
end
else begin
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
end
m_3: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b1000;
if (req3_fall) begin
shift_case <= 2'b01;
shift_trigger <= ~shift_trigger;
end
else begin
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
end
default: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= {o_gnt3, o_gnt2, o_gnt1, o_gnt0};
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
endcase
end
else if (!((queue[3] & req_delay) == 4'b0000)) begin
case (queue[3])
m_0: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b0001;
if (req0_fall) begin
shift_case <= 2'b00;
shift_trigger <= ~shift_trigger;
end
else begin
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
end
m_1: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b0010;
if (req1_fall) begin
shift_case <= 2'b00;
shift_trigger <= ~shift_trigger;
end
else begin
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
end
m_2: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b0100;
if (req2_fall) begin
shift_case <= 2'b00;
shift_trigger <= ~shift_trigger;
end
else begin
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
end
m_3: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= 4'b1000;
if (req3_fall) begin
shift_case <= 2'b00;
shift_trigger <= ~shift_trigger;
end
else begin
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
end
default: begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= {o_gnt3, o_gnt2, o_gnt1, o_gnt0};
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
endcase
end
else begin
{o_gnt3, o_gnt2, o_gnt1, o_gnt0} <= {o_gnt3, o_gnt2, o_gnt1, o_gnt0};
shift_case <= shift_case;
shift_trigger <= shift_trigger;
end
end
//shift_case
always @ (shift_trigger) begin
case (shift_case)
2'b00: begin //when queue[3] POP
//No-op
end
2'b01: begin //when queue[2] POP
queue[3] <= queue[2];
queue[2] <= queue[3];
//queue[1] <= queue[1];
//queue[0] <= queue[0];
end
2'b10: begin //when queue[1] POP
queue[3] <= queue[1];
queue[2] <= queue[3];
queue[1] <= queue[2];
//queue[0] <= queue[0];
end
2'b11: begin //when queue[0] POP
queue[3] <= queue[0];
queue[2] <= queue[3];
queue[1] <= queue[2];
queue[0] <= queue[1];
end
default: begin
//No-op
end
endcase
end
endmodule
FF처리 (req_delay <= i_req)하여 req_delay를 만들어줍니다.
이후 ~i_req & req_delay를 이용해서 REQ의 falling edge를 캡쳐하는 req_fall을 만들어줍니다.
이 req_fall이 뜨면 버스 사용이 끝난 것(REQ이 내려갔다)이므로 history queue를 shift 해줍니다.
queue가 바뀌었다면 case문에서 걸려서 bus 사용권이 바뀌게 됩니다.
이런 방식으로 LRG 알고리즘을 짜보았습니다.
Testbench
`timescale 1ns / 1ps
module tb_dmac_arbiter_top();
reg i_clk;
reg i_reset;
reg i_req0;
reg i_req1;
reg i_req2;
reg i_req3;
wire o_gnt0;
wire o_gnt1;
wire o_gnt2;
wire o_gnt3;
dmac_arbiter_top arbiter_top (.i_clk (i_clk),
.i_reset (i_reset),
.i_req0 (i_req0),
.i_req1 (i_req1),
.i_req2 (i_req2),
.i_req3 (i_req3),
.o_gnt0 (o_gnt0),
.o_gnt1 (o_gnt1),
.o_gnt2 (o_gnt2),
.o_gnt3 (o_gnt3) );
always #5 i_clk = ~i_clk;
initial begin
i_clk = 1'b0; i_reset = 1'b1;
rrarbiter_lrg.queue[0] = 4'b0001;
rrarbiter_lrg.queue[1] = 4'b0010;
rrarbiter_lrg.queue[2] = 4'b0100;
rrarbiter_lrg.queue[3] = 4'b1000;
{i_req3, i_req2, i_req1, i_req0} = 4'b0000; #1
i_reset = 1'b0; #1
i_reset = 1'b1; #8
{i_req3, i_req2, i_req1, i_req0} = 4'b0100; #20
{i_req3, i_req2, i_req1, i_req0} = 4'b0110; #20
{i_req3, i_req2, i_req1, i_req0} = 4'b0011; #20
{i_req3, i_req2, i_req1, i_req0} = 4'b1001; #20
{i_req3, i_req2, i_req1, i_req0} = 4'b1101; #20
{i_req3, i_req2, i_req1, i_req0} = 4'b0011; #20
{i_req3, i_req2, i_req1, i_req0} = 4'b0101; #20
{i_req3, i_req2, i_req1, i_req0} = 4'b1111; #20
{i_req3, i_req2, i_req1, i_req0} = 4'b1011; #20
{i_req3, i_req2, i_req1, i_req0} = 4'b1011; #20
{i_req3, i_req2, i_req1, i_req0} = 4'b1011; #20
{i_req3, i_req2, i_req1, i_req0} = 4'b0101; #20
{i_req3, i_req2, i_req1, i_req0} = 4'b0011; #20
{i_req3, i_req2, i_req1, i_req0} = 4'b1010; #20
$finish;
end
endmodule
Simulation
(Waveform은 컴퓨터에서 클릭해서 보면 더 잘보입니다.)
REQ2가 포함되어 있으면 무조건 M2 먼저 GNT해줍니다.
그렇지 않다면 history queue를 참고하여 우선권을 결정합니다.
'Digital Design > SoC' 카테고리의 다른 글
[SoC] Arbiter_Fixed Priority (Design with Verilog) (0) | 2023.04.30 |
---|---|
[SoC] Round-Robin Arbiter_LRG (Design with Verilog) (0) | 2023.04.26 |
[SoC] Round-Robin Arbiter_Fixed History (Design with Verilog) (0) | 2023.04.25 |
[SoC] DMAC LRG Arbitration (ARM DMAC Reference Manual) (0) | 2023.04.24 |
[SoC] Timing Violation (Setup/Hold/Skew/Jitter/해결법) (2) | 2023.04.14 |
댓글