[Presentation]
오전에는 어제 만들었던 Ultrasonic module 미션에 대해 발표하는 시간을 가졌다.
첫 발표인만큼, 떨리기도 많이 떨렸고 부족한 점도 많았다.
이번 PPT를 발판 삼아, 부족했던 부분들을 보완하는 기회로 삼아야 겠다.
[Review]
📌 Code(교수님 버전)
사실 Ultrasonic sensor는 input과 output이 각각 나뉘어 있기 때문에
굳이 FSM을 쓰지 않고, 각각 따로 동작하도록 설계해줘도 된다.
근대 FSM에 익숙해지면 좋으니까 그냥 쓰자.
module UltraSonic_Profsr(
input clk, rst,
input echo,
output reg trig,
output reg [15:0] distance_cm,
output reg [7:0] LED_bar
);
// 얘는 input output이 따로 있기 때문에
// 사실 FSM을 안 써도 된다.
// 각각 따로 놀게끔 만들어줘도 됨.
// 2'b00, 2'b01, 2'b10, 2'b11 이렇게 해줘도 되는데, 베릴로그는 아래처럼 만들어버림.
// 상태머신 쓸 떄는 원환코드로 만들어주면 된다.
parameter S_IDLE = 4'b0001;
parameter S_TRIG = 4'b0010;
parameter S_WAIT_PEDGE = 4'b0100;
parameter S_WAIT_NEDGE = 4'b1000;
reg [21:0] count_usec; // 넉넉히 주면 좋다. 메뉴얼에는 60ms의 여유를 주라 했는데, 실제로는 70ms 넘어서도 걸린다.
wire clk_usec;
reg count_usec_e;
clock_usec usec_clk(.clk(clk), .reset_p(rst), .clk_usec(clk_usec));
// usec Counter
always @(negedge clk, posedge rst) begin
if(rst) count_usec = 0; // 리셋 누르면 출력이 0되도록 설계
else begin
// data가 들어올 때 동안만 count
if(clk_usec && count_usec_e) count_usec = count_usec + 1; // enable이 1이고, clk_usec가 들어올 때만 count++
else if(!count_usec_e) count_usec = 0;
end
end
//Edge를 보내서 쓰는 걸로 clk 동기화
wire echo_pedge, echo_nedge;
edge_detector_n edg_echo(.clk(clk), .cp_in(echo), .rst(rst), .p_edge(echo_pedge), .n_edge(echo_nedge)); // Edge Detector
reg [5:0] state, next_state;
// State Machine
always @(negedge clk, posedge rst) begin
if(rst) state = S_IDLE;
else state = next_state; // 매 클락마다 state를 next_state로 바꿔준다.
end
reg [21:0] echo_pw; // count_usec 저장하는 echo pulse width 레지스터
always@(posedge clk, posedge rst) begin
if(rst) begin
echo_pw <= 0;
// state <= S_IDLE;
LED_bar <= 8'b0000_0000;
end
else begin
case(state)
S_IDLE : begin
if(count_usec < 22'd100_000) begin
count_usec_e <= 1;
LED_bar[0] <= 1;
end
else begin
count_usec_e <= 0;
next_state <= S_TRIG;
LED_bar <= 8'b0000_0000;
end
end
S_TRIG : begin
if(count_usec < 22'd12) begin
count_usec_e <= 1;
trig <= 1;
LED_bar[1] <= 1;
end
else begin
next_state <= S_WAIT_PEDGE;
count_usec_e <= 0;
trig <= 0;
end
end
S_WAIT_PEDGE : begin
LED_bar[2] <= 1;
if(echo_pedge) begin
count_usec_e <= 1;
next_state <= S_WAIT_NEDGE;
end
else begin // 따지고 보면, pedge 발생 안 하면 아무 것도 안 하는 상태이다. 사실 아무 것도 안 아면, else 생략 가능
count_usec_e <= 0;
next_state <= S_WAIT_PEDGE;
end
end
S_WAIT_NEDGE : begin
LED_bar[3] <= 1;
if(echo_nedge) begin
echo_pw <= count_usec;
count_usec_e <= 0;
next_state <= S_IDLE;
end
else begin // 안 써줘도 되는 건데, 가독성을 위해 작성
count_usec_e <= 1;
next_state <= S_WAIT_NEDGE;
end
end
default : begin
count_usec_e <= 0;
next_state <= S_IDLE;
end
endcase
end
end
// 동작적 모델링으로 만들어준다. assign써가지고 구조적으로 만들면 pdt 발생 확률 증가
always @(posedge clk, posedge rst) begin
if(rst) begin
distance_cm <= 0;
end
else begin
distance_cm <= echo_pw / 58;
end
end
endmodule
module UltraSonic_Top_Profsr(
input clk, rst,
input echo,
output trig,
output [7:0] LED_bar,
output [3:0] com,
output [7:0] seg_7
);
wire [15:0] distance_cm;
UltraSonic_Profsr SR04(clk, rst, echo, trig, distance_cm, LED_bar);
wire [15:0] distance_dec;
bin_to_dec humi_b2d(.bin(distance_cm[11:0]), .bcd(distance_dec)); // 400cm 충분히 받으므로 12bit로 짤라버려도 된다.
FND_4digit_cntr fnd_cntr(.clk(clk), .rst(rst), .value(distance_dec), .com(com), .seg_7(seg_7));
endmodule
📌 놓친 부분
우리가 사용하는 Cora board의 정격 전압은 3.3V이고 초음파 센서는 5V를 받아서 쓰기 때문에
단기적으로는 큰 문제가 없지만, 장기적으로 회로에 무리가 갈 수 있다.
따라서 echo line에 1㏀ 저항을 달아주고 사용해야 한다.
[Timing Management]
📌 WNS, TNS
Bitstream을 생성하다 보면, 위와 같이 빨간색 숫자가 뜨는 것을 종종 볼 수 있다.
이제까지는 별 문제없이 생성되었어서 그다지 신경을 안 썼던 부분이었는데,
실무에서는 밥먹듯이 수정하는 부분이라고 한다. 사실 엄청 중요한 것이었음.
Worst Negative Slack
WNS는 디자인의 가장 최악의 타이밍 지연이다.
타이밍 지연은 회로의 입력에서 출력까지의 시간을 측정하는데 사용되며,
이는 회로가 올바르게 동작하기 위해 필요한 최소 시간을 나타내고
WNS는 회로가 원하는 동작을 제대로 수행하지 못하고 최악의 지연 시간에 도달했을 때 발생한다.
즉, 이 것은 회로의 타이밍 제약을 얼마나 위반하고 있는 지 를 나타낸다.
Total Negative Slack
TNS는 디자인 내의 모든 경로에 대한 NS의 합계이다
즉, TNS는 디자인 전체의 타이밍 제약 위반 정도를 나타내며,
TNS가 양수인 경우에는 모든 경로가 타이밍 제약을 만족하고
TNS가 음수인 경우에는 하나 이상의 경로가 타이밍 제약을 위반하고 있다는 것을 의미한다.
쉽게 말해서 여유가 얼마나 있냐? 라는 뜻이다.
빨간색(-)이 떴으면 여유가 없다는 뜻.
위 그림에서는 3.272ns의 딜레이가 더 주어져야(더 지나야) 제대로 결과값을 받을 수 있다는 의미이고
여러 개의 neg slack 중에서 가장 안 좋은 것이 WNS, neg slack들의 값들을 다 더한 것이 TNS 이다.
만약 WNS만 크면(-이면) 그 것만 수정하면 된다.
그런데 TNS가 크게 나온다? 그러면 처음부터 다 수정해봐야 됨.
우리의 목적은 WNS를 줄어들도록 수정하는 것이다.
📌 Timing Tab의 해석
위 그림의 빨간 색으로 표기된 부분을 더블클릭하면 다음과 같은 창이 나온다.
✅ Requirment(요구 사항)
- 디자인에서 특정 타이밍 경로에 대한 타이밍 제약을 정의
- 이 것은 어떤 시간까지 특정 신호가 도달해야하는 지를 나타내고
예를 들어, 클럭 신호가 특정 레지스터에 도달해야 하는 시간을 정의할 수 있다.
- Req는 주로 디자인 제약 파일(Constraints)에 설정되며,
디자이너가 디자인의 타이밍 요구 사항을 명시적으로 지정할 수 있다.
즉, 이 시간 이내에는 신호가 도달해야 정상적인 동작을 할 수 있는 한계점
✅ Slack(슬랙)
- 슬랙은 특정 타이밍 경로에서 Req와 실제 지연 시간의 차이를 나타내고
Slack = Requirement - Actual Delay로 표현할 수 있다.
- Slack이 양수인 경우, 해당 경로는 타이밍 제약을 충족하고 있으며
Slack이 음수인 경우, 해당 경로는 타이밍 제약을 위반하고 있다.
- 이 값은 경로가 얼마나 여유가 있는 지 또는 얼마나 타이밍 제약을
위반하는 지를 나타내는 중요한 지표이다.
✅ Total Delay(전체 지연)
- Total Delay는 특정 타이밍 경로 중 입력에서 출력까지의 누적된 총 지연을 의미한다.
- 이 것은 해당 경로의 논리적인 포인트 사이를 통과하는데 필요한 모든 시간의 합
- Total Delay를 이용하면 어떤 경로의 디자인 내에서 어느 정도의 시간이 소요되는지 알 수 있다.
요약하면,
Requirement와 Slack는 Timing Path가 Req를 얼마나 충족시키는지 평가하기 위한 지표
Total Delay는 해당 경로의 시간 지연을 측정하기 위한 지표를 의미한다.
Timing 해석 시퀀스
1. Design Run
: 타이밍 탭을 사용하려면 Design Run에서 적절한 디자인 실행을 선택해야 한다.
보통 Run Synthesis 및 Tun Implementation 단계를 마쳐야 타이밍 분석 결과를 얻을 수 있다.
2. Timing Tab 열기
: 위에서는 WNS를 더블클릭하는 것으로 열었는데,
실제로는 상단 메뉴->Window -> Timing을 선택하여 열 수 있다.
3. Timing Summary 보기
: Timing tab을 열면 기본적으로 타이밍 서머리가 표시된다.
이 섹션은 디자인의 주요 타이밍 정보를 제공하며 다음과 같은 정보가 표시된다.
○ Slack
: 각 경로의 Slack 값이 표시된다.
Slack은 해당 경로에 얼마나 타이밍 제약을 위반하는지 나타내며
음수 Slack은 제약 위반을 나타낸다.
○ WNS : 경로 중 가장 나쁜 Slack
○ TNS : 모든 경로의 Slack 값을 합산
4. Path 탭 확인
: Timing 탭에서 Path 탭을 선택하면, 디자인 내의 개별 타이밍 경로에 대한 정보를 얻을 수 있으며,
이 경로에 대한 세부 정보와 Slack 값을 확인할 수 있다.
5. 타이밍 제약 확인
: Timing Tab에서 Report 버튼을 클릭하면, 자세한 타이밍 리포트를 볼 수 있고
이 리포트는 디자인의 타이밍 분석 결과와 제약 정보를 포함하고 있다.
6. 결과 해석 및 수정
: 타이밍 정보를 분석하여 어떤 경로에서 제약을 위반하는지 식별하고,
필요한 경우, 디자인을 최적화하여 타이밍을 개선한다.
📌 Requirement 제약 위반 해결법
어떻게 해결하느냐??
require time을 늘려주면 된다.
위에서 필요한 total delay는 11.247ns이니까, 2분주해서 16ns짜리 clk 만들어서 사용하면 된다.
(System clock는 125MHz이므로 8ns 주기의 clock이다.)
reg clk_div_2;
always@(posedge clk) clk_div_2 = ~clk_div_2;
always @(posedge clk_div_2, posedge rst) begin
if(rst) begin
distance_cm <= 0;
end
else begin
distance_cm <= echo_pw / 58;
end
end
// 어짜피 클락에 맞춰서 동작하는 task이라 아주 약간의 pdt가 발생할 수는 있겠지만,
// 그래도 clk에 동기가 맞춰져있기 때문에 크게 문제가 되지 않는다.
// 하지만, 버튼 동작같은 완벽한 비동기 입력의 경우에는 always문의 sensitivity list에 넣어서 사용하면 안 된다.
아니면 아래와 같이 clk_usec을 사용한다.
16ns보다 계산을 좀 늦게하기 때문에 FND에 출력은 살짝 늦어지겠지만,
usec 단위이므로 사람의 눈으로 볼 떄 불편하지는 않다.
// clk를 clk_usec을 사용한다.
always @(posedge clk_usec, posedge rst) begin
if(rst) begin
distance_cm <= 0;
end
else begin
distance_cm <= echo_pw / 58;
end
end
[Running Average]
📌 Running Average(이동 평균)
이동 평균은 시계열 데이터에서 노이즈를 제거하고
추세를 부드럽게 만드는데 사용되는 필터링 방법이며,
계산법은 다음과 같다.
1. N개의 데이터 포인트를 사용하는 이동 평균
i) N개의 연속된 데이터 포인트를 선택한다.
ii) 이 N개의 데이터 표인트의 합을 구한다.
iii) 이 팝을 N으로 나눠서 평균값을 계산한다.
- 이동평균의 길이 N은 유저가 선택하는 값이며,
길면 길수록 더 평활한 결과를 얻지만, 시간이 오래 걸린다.
2. 새로운 데이터 포인트 추가
- 새로은 데이터 포인트가 사용 가능하게 되면,
기존 이동평균에서 가장 오래된 데이터 포인트를 제거하고
새로운 데이터 포인트를 추가한다.
- 이렇게 새로운 데이터 포인트가 추가될 때마다 이동 평균을 업데이트할 수 있다.
예시)
4bit짜리 데이터를 이동평균 방식으로 필터링
i) 4bit짜리 데이터를 16개(2의 제곱수가 편함)의 배열 생성
▶ [3:0] data [15:0];
ii) 16개의 배열을 각각 다 더한 뒤, 16으로 나눔
▶ 여기서 배열의 각 요소들이 데이터 포인트이며,
최초 16개 전까지는 이상한 값이 나오겠지만, 그 뒤부터는 정상적으로 출력
iii) 새로운 데이터 포인트 추가
▶ 새로운 데이터를 쉬프트 방식으로 받으면 가장 오래된 데이터는 소멸
1~16 까지, 2~17까지, 3~18까지 이런 식으로 최근 16개를 평균냄
// 이렇게 각각 데이터를 받아서 temp_vlaue 배열에 하나씩 저장해둔다.
S_WAIT_NEDGE : begin
LED_bar[3] <= 1;
if(echo_nedge) begin
// temp_value[0] <= count_usec; // 0번 index에 접근
temp_value[index] <= count_usec;
index <= index + 1; // 2의 거듭제곱 쓰면 if문 써서 조건 추가할 필요가 없다. 15다음 16, 7다음8이니까~ 자동적으로 1더하면 0으로 초기화됨
count_usec_e <= 0;
next_state <= S_IDLE;
end
else begin // 안 써줘도 되는 건데, 가독성을 위해 작성
count_usec_e <= 1;
next_state <= S_WAIT_NEDGE;
end
end
// sum_value 계산
reg [5:0] i;// 4bit로 하면 16보다 작기 때문에 for문에서 못 빠져나온다. 마지막 비트에서 16을 못 넘어감
always@(posedge clk_usec, posedge rst) begin
if(rst) begin
sum_value <= 0;
i <= 0;
end
else begin
sum_value = 0;
for(i = 0; i < 16 ; i = i+1) begin // 이 for문은 1 clk에 안 끝날 것 같으므로 clk_usec을 쓰자(clk쓰면 아마 WNS 가 음수가 될 것이다.)
sum_value = sum_value + temp_value[i]; // temp_vlaue에 저장된 값을
end
end
end
// 동작적 모델링으로 만들어준다. assign써가지고 구조적으로 만들면 pdt 발생 확률 증가
always @(posedge clk_usec, posedge rst) begin
if(rst) begin
distance_cm <= 0;
end
else begin
// distance_cm <= echo_pw / 58;
distance_cm <= sum_value[20:4] / 58;
end
end
✅ for문의 변수 i가 4bit로 선언하면 안 되는 이유
: i가 4bit로 선언된다면, 0~15까지(2⁴ = 16개)의 값을 표현할 수 있다.
for문에서는 i가 0~15까지 증가하도록 설정되어 있고
for문이 종료되려면 루프가 총 16번(0~15) 반복된 뒤, 17번쨰(16)를 찍어야 종료가 된다.
하지만, 4bit의 i는 15까지만 표현이 가능하므로 for문은 16에 도달하지 못하여
종료되지 않는다.
✅ 16으로 나눠주지 않고 sum_value[20:4] 이렇게 쓰는 이유
: 위 방식은 비트 슬라이싱이라는 방식인데,
같은 값을 16번 더하는 것은 2진수의 관점에서 4자리 좌시프트하는 것과 같다.
중간에 튀는 값이 더해진다 해도 다 더해진 값을 4자리 좌시프트한 뒤
MSB부터 16bit만큼 잘라서 쓰면, 다 더해서 16으로 나눠 평균을 구한 것과
같은 효과를 낸다.
📌 Result
이렇게 여러 번 측정하여 평균내는 방식으로 값을 필터링해주면,
이전처럼 값이 튀지 않게 되지만, 출력이 좀 늦어진다.
근데 불편할 정도는 아니니까 적극 활용해보자.
Full Code
module UltraSonic_Profsr(
input clk, rst,
input echo,
output reg trig,
output reg [15:0] distance_cm,
output reg [7:0] LED_bar
);
// 얘는 input output이 따로 있기 때문에
// 사실 FSM을 안 써도 된다.
// 각각 따로 놀게끔 만들어줘도 됨.
// 2'b00, 2'b01, 2'b10, 2'b11 이렇게 해줘도 되는데, 베릴로그는 아래처럼 만들어버림.
// 상태머신 쓸 떄는 원환코드로 만들어주면 된다.
parameter S_IDLE = 4'b0001;
parameter S_TRIG = 4'b0010;
parameter S_WAIT_PEDGE = 4'b0100;
parameter S_WAIT_NEDGE = 4'b1000;
reg [16:0] count_usec; // 넉넉히 주면 좋다. 메뉴얼에는 60ms의 여유를 주라 했는데, 실제로는 70ms 넘어서도 걸린다.
wire clk_usec;
reg count_usec_e;
clock_usec usec_clk(.clk(clk), .reset_p(rst), .clk_usec(clk_usec));
// usec Counter
always @(negedge clk, posedge rst) begin
if(rst) count_usec = 0; // 리셋 누르면 출력이 0되도록 설계
else begin
// data가 들어올 때 동안만 count
if(clk_usec && count_usec_e) count_usec = count_usec + 1; // enable이 1이고, clk_usec가 들어올 때만 count++
else if(!count_usec_e) count_usec = 0;
end
end
//Edge를 보내서 쓰는 걸로 clk 동기화
wire echo_pedge, echo_nedge;
edge_detector_n edg_echo(.clk(clk), .cp_in(echo), .rst(rst), .p_edge(echo_pedge), .n_edge(echo_nedge)); // Edge Detector
reg [5:0] state, next_state;
// State Machine
always @(negedge clk, posedge rst) begin
if(rst) state = S_IDLE;
else state = next_state; // 매 클락마다 state를 next_state로 바꿔준다.
end
// reg [16:0] echo_pw; // count_usec 저장하는 echo pulse width 레지스터
reg [16:0] temp_value [15:0]; // 배열은 뒤에다 선언한다 17bit짜리 16개짜리 // 배열은 짝수 개로 해주는 것이 좋다. 그래야 나누기도 쉽고 보기에도 편함
reg [20:0] sum_value; // 좌로 시프트 4개 한다는 의미. 16번 더하는 것. -> 21bit니까 17bit에서 4bit 더 필요하다
reg [3:0] index; // 0~15까지만 나오면 충분하니까 4bit
always@(posedge clk, posedge rst) begin
if(rst) begin
// echo_pw <= 0;
// state <= S_IDLE;
LED_bar <= 8'b0000_0000;
count_usec_e <= 0; // always문 안에서 건드리는 변수는 reset에서 꼭 초기화해줘야 된다.(시뮬레이션에서 Z로 뜸)
next_state <= 0;
trig <= 0;
index <= 0; // 어짜피 16넘어가면 0으로 돌아가는 링카운터 형태라 굳이 리셋 안 해줘도 되는에 일단 해주는게 편함.
end
else begin
case(state)
S_IDLE : begin
if(count_usec < 22'd100_000) begin
count_usec_e <= 1;
LED_bar[0] <= 1;
end
else begin
count_usec_e <= 0;
next_state <= S_TRIG;
LED_bar <= 8'b0000_0000;
end
end
S_TRIG : begin
if(count_usec < 22'd12) begin
count_usec_e <= 1;
trig <= 1;
LED_bar[1] <= 1;
end
else begin
next_state <= S_WAIT_PEDGE;
count_usec_e <= 0;
trig <= 0;
end
end
S_WAIT_PEDGE : begin
LED_bar[2] <= 1;
if(echo_pedge) begin
count_usec_e <= 1;
next_state <= S_WAIT_NEDGE;
end
else begin // 따지고 보면, pedge 발생 안 하면 아무 것도 안 하는 상태이다. 사실 아무 것도 안 아면, else 생략 가능
count_usec_e <= 0;
next_state <= S_WAIT_PEDGE;
end
end
S_WAIT_NEDGE : begin
LED_bar[3] <= 1;
if(echo_nedge) begin
// temp_value[0] <= count_usec; // 0번 index에 접근
temp_value[index] <= count_usec;
index <= index + 1; // 2의 거듭제곱 쓰면 if문 써서 조건 추가할 필요가 없다. 15다음 16, 7다음8이니까~ 자동적으로 1더하면 0으로 초기화됨
count_usec_e <= 0;
next_state <= S_IDLE;
end
else begin // 안 써줘도 되는 건데, 가독성을 위해 작성
count_usec_e <= 1;
next_state <= S_WAIT_NEDGE;
end
end
default : begin
count_usec_e <= 0;
next_state <= S_IDLE;
end
endcase
end
end
// reg clk_div_2;
// always@(posedge clk) clk_div_2 = ~clk_div_2;
// sum_value 계산
reg [5:0] i;// 4bit로 하면 16보다 작기 때문에 for문에서 못 빠져나온다. 마지막 비트에서 16을 못 넘어감
always@(posedge clk_usec, posedge rst) begin
if(rst) begin
sum_value <= 0;
i <= 0;
end
else begin
sum_value = 0;
for(i = 0; i < 16 ; i = i+1) begin // 이 for문은 1 clk에 안 끝날 것 같으므로 clk_usec을 쓰자(clk쓰면 아마 WNS -뜰 것이다.)
sum_value = sum_value + temp_value[i];
end
end
end
// 동작적 모델링으로 만들어준다. assign써가지고 구조적으로 만들면 pdt 발생 확률 증가
always @(posedge clk_usec, posedge rst) begin
if(rst) begin
distance_cm <= 0;
end
else begin
// distance_cm <= echo_pw / 58;
distance_cm <= sum_value[20:4] / 58;
end
end
endmodule
✅ [16:0] temp_value[15:0] 에 대한 해석
: 17bit짜리 데이터를 16개의 배열에 각각 저장할 것이다.
- Data Sheet에는 60ms의 대기시간이면 충분하다 했지만,
실제로 프로세스가 완전히 수행되는 시간은 70ms가 넘을 수도 있다.
16bit 즉, 2의 16승은 65536밖에 안 되어 70ms까지 부족하다.
따라서 17bit(131,072)로 설정하여 대기 시간을 넉넉히 줄 수 있다.
- 16개의 데이터 포인트 설정(배열은 짝수개 or 2의 제곱수로 해주는 것이 편하다.)
✅ [20:0] sum_value 에 대한 해석
i) 어떠한 값을 16번 더하는 것은 4자리만큼 좌시프트한 값과 같다.(2진수로 표현 시)
ex) 4'b1101(13)을 16번 더하면, 4'b1101_0000(208)
ii) 17bit의 값을 16번 더하면, 4자리만큼 좌시프트된 값이 되므로 총 21bit가 필요하다.
따라서 sum_value는 21bit로 선언해야 모든 값을 저장할 수 있다.
cf) 17bit에 최대로 저장되는 값은 17'b1_1111_1111_1111_1111(131,071) 이다.
이 값을 총 16번 더하면 21'b1_1111_1111_1111_1111_0000(2,097,136)
21bit를 넘어가지 않는다.
'# Semiconductor > - Semicon Academy' 카테고리의 다른 글
[Harman 세미콘 아카데미] 58일차 - Verilog(PWM 코드 및 시뮬레이션) (0) | 2023.09.15 |
---|---|
[Harman 세미콘 아카데미] 58일차 - Verilog(Ultrasonic : 오류 수정, FSM 없이 구현, Testbench) (0) | 2023.09.15 |
[Harman 세미콘 아카데미] 56일차 - Verilog(Mission : UltraSonic module의 구현) (0) | 2023.09.14 |
[Harman 세미콘 아카데미] 55일차 - Verilog(온습도 센서 : Testbench, LED Debugging) (0) | 2023.09.12 |
[Harman 세미콘 아카데미] 54일차 - Verilog(온습도 센서 개요 및 통신프로토콜 Coding) (0) | 2023.09.11 |