PC에서 인스트럭션 메모리를 통해 메모리에 저장되어 있던 명령어가 인출되고 인스트럭션디코더를 통해 연산종류 오퍼랜드 등이 결정되어 28bit 제어신호가 발생한다.그리고 데이터패스 연산 후 레지스터에 저장하게 되며 각 스텝은 각각 한 개 의 클럭 사이클에 실행된다. 즉, Instrunction 1개 당 3clock 필요하다.
•IF 상태
IR ←M[PC] , PC 지정주소에서 instr read
PC ←PC+1 , 다음 instr을 위해 자동증가
•EX0 상태
- IR값에 따라 instr decoding
- opcode 값에 따라
필요한 마이크로 연산 실행상태로 분기
오른쪽 그림은 3cycle에 1개의 instr실행하는 ASM 이다. 첫번째로 IF 상태 - pc는 instr이 저장되어 있는 메모리 주소값을 가리킨다. (pc = 0번지부터 메모리 주소 가리킴) pc가 가리키는 메모리 주소에 저장되어 있는 istr이 IR(Instr Reg)에 할당된다. 이 IR 값에 따라 instr decoging되고 opcode값에 따라 28bit제어 신호 발생, 마이크로 연산 실행된다.
왼쪽 표를 보면 IF의 next address는 11111111이고 이는 EX0을 가리킨다. EX0의 na는 00000000인데, 디코더 값에 따라 instr을 실행한다. 그리고 instr들의 na는 IF, 즉 다시 IF 상태로 돌아온다. 이게 3cycle이다.
왼쪽에서 MUX C의 sel에 따라 control memory 주소로 들어오는 값을 살펴보자. 1일 땐 OPcode 7bit 앞에 0이 붙어서 들어오고 0일 땐 na 값이 들어오는 걸 확인할 수 있다. 오른쪽 표를 보면, IF 상태로 갈 경우와 EX0상태로 갈 경우만 MSB가 1이므로, 이 때는 mc가 0이고 na값이 들어간다. 하지만 EX0 상태에서 instr 실행시, instr의 주소는 7bit만으로 표현가능하므로 맨앞에 0을 붙여줘도 상관없는 것이고 mc는 1이 되어 opcode값이 주소가 된다.
이 부분을 보면 ZNCV 등의 flag가 pc로 가는데, 이는 오른쪽 표처럼, flag 발생에 따라 PC (메모리 주소) 값을 바꿔주기 위함이다. 예는 branch 명령어에서 조건으로 활용되고 있다. 아래의 테이블에서도 확인할 수 있다.
branch instr이 실행될 때 PI와 PL은 1 1 이 들어오고, 이 때 dr, sa, sb 값은 의미하는 바가 달라진다. 다른 instr들은 실행될 때마다 PC +1 이 실행되어 순서대로 다음 메모리 주소의 instr이 실행되지만, branch명령어는 원하는 주소로 건너뛰게 된다.
pl 과 pi 값에 따라 pc 값이 어떻게 바뀌는지 확인할 수 있는 control 회로 내의 코드이다.
MUXM의 d0에는 {1'b0,pc}가 들어오고, d1에는 oprA[8:0] 가 들어온다. 28bit 표를 보면 IF상태일 때만 mm이 1이기 때문에 pc값에 따라 명령어를 인출하게 되고, 다른 상태 (EX0, instr)일 때는 mm이 0이다. mm이 0일 때 쓰레기값이 들어가도 MUXD가 1(LOAD)이 아니면 출력되지 않기 때문에 상관이 없게 된다.
추가로, MUX address가 9bit인 이유는, 사용하는 Memory depth가 512 (=2의 9승)이기 때문이다. -> 512x16b RAM
Register file을 구현하기 위해 Decoder 와 D-f/f, 8x1 MUX를 먼저 만들었다. 이때 load enable을 decoder 블럭에서 enable로써 사용하였다.
module reg_16b (clk, start, enb, din, dout);
parameter DW = 16;
input clk, start, enb;
input [DW-1:0] din;
output reg [DW-1:0] dout;
always @ (posedge clk or negedge start)
begin
if (!start)
dout <= {DW{1'b0}};
else if (enb)
dout <= din;
end
endmodule
module mux8x1 (sel,d0,d1,d2,d3,d4,d5,d6,d7,dout);
parameter BW = 16;
input [2:0] sel;
input [BW-1:0] d0, d1, d2, d3, d4, d5, d6, d7;
output reg [BW-1:0] dout;
always @ (sel,d0,d1,d2,d3,d4,d5,d6,d7)
begin
if (sel==3'b000) dout = d0;
else if (sel==3'b001) dout = d1;
else if (sel==3'b010) dout = d2;
else if (sel==3'b011) dout = d3;
else if (sel==3'b100) dout = d4;
else if (sel==3'b101) dout = d5;
else if (sel==3'b110) dout = d6;
else dout = d7;
end
endmodule
D-f/f, 8x1 MUX이다.
다음 디코더 구현이다.
기본 컨셉은, 일단 8개의 레지스터의 Din에는 func unit의 output값이 들어온다. 이 때 R/W 신호에 따라 레지스터에 저장할 수 있도록 디코더에 en을 만들어줬다. (R/W = load enable) decoder의 출력은 8비트인데, 이게 한 비트씩 레지스터의 en으로 들어간다. 예를 들어 000을 디코더의 sel신호로 주면, 00000001이 출력되고 dec_out[0]만 1이기 때문에 R0만 Din이 Q로 나가서 MUX로 들어가게 된다.
위에서 만든 세 모듈을 인스턴시에이션하여 register file unit을 완성하였다. r0_reg 는 R0의 출력값을 FND로 확인하고자 포트로 빼준 것이다. 유의할 점은 16비트 reg_out을 8개를 만들어야 하는데 array로 작성하지 않아서 에러가 발생했었다.
마지막으로 register file과 function unit을 연결하고, MUXB, MUXD, MUXM을 assign문으로 적절히 만들어주었다. flag 포트들 앞에 레지스터를 다 달아주었는데, memory에 access할 때 등은 flag값이 바뀌지 않고, fout이 register file로 들어와서
rw할 때만 flag값이 로딩될 수 있도록 하기 위함이다. 레지스터를 달아주지 않으면 예컨데, memory에 값을 저장하기 위한 oprA, B값에 대한 flag도 쓸데없이 계속 로딩되는 것이다.
구조가 복잡하여 필요한 변수에만 값을 주어 결과를 확인해보았다. 위의 회로 그림에서 첫 시작은 constant값을 주고 MUXB는 1로 셀렉하여 function unit에서 oprB shift 시키고 register file로 들어와서 oprB에 쉬프트된 값이 제대로 들어오는지 확인한다.
그 다음은 다시 func unit을 거쳐 register file로 들어와서 oprA에 값을 저장하고 확인하는 것이 목적이었다. 그 후 address out이 나오도록 MUXM을 0으로 셀렉하여 oprA값이 잘 나오는지 확인하였다.
opcode에서 10010 ~1111? 의 기능을 구현해야 한다. 즉, 1bit shift, arithmetic right shift, 1bit rotate, n-bit rotate(barrel shift)
를 구현해야 한다. 각각의 회로를 구현한 후 한 모듈에 합치는 방법도 있겠지만 더 효율적인 방법을 고안해보았다.
barrel shifter 회로에 logic을 추가하면 나머지 기능도 모두 구현 가능할 것 같았다.
위의 그림과 같이 먼저 right rotate하는 BR shifter를 생각했다. 31 bit짜리 tmp를 선언한 후 오퍼랜드(oprB) 값 16bit를 할당하고 그 뒤에 15bit를 할당한다. 그리고 16 bit 짜리 먹스 16개를 만들어서 그림과 같이 한 비트씩 shift되도록 할당한다.
그럼 결국 select 값에 따라, select값 만큼 rotate시킬 수 있게 된다. 예를 들어 1bit rotate 시키고 싶다면 sel = 0001을 주고 그렇게 되면 모든 MUX의 d1이 output으로 출력되고 결국 한 비트 rotate된 값이 출력되는 것이다.
16 bit 짜리 MUX를 인스턴시에이션하여 generate ~ for문을 사용하여 16 bit MUX * 16개를 만들어주었다.
그리고 case문으로 sel_mode값 (FS[3:1])에 따라 기능을 선택할 수 있다. 1bit shift left/ right는 MSB, LSB 쪽에 0을 할당해서 1bit shift된 값으로 출력되게 만들었고, arith는 MSB 고정하고 1bit shfit right를 하도록 했다. 여기서 1bit만 움직이기 위해 mux select값에 1이 들어가도록 mux_tmp를 따로 선언하여 값을 주었다. 그리고 n-bit rotate값은 n-bit rotate를 위해 기존대로 mux_sel값을 할당했다. (FS[0],SA[2:0])
한 가지 느낀 점은 값을 할당할 때 bit수에 유의한다는 점이다. 할당된 비트를 전부 채우지 않았을 때 latch가 생기는 등 회로가 원하는대로 구현되지 않았다.
아래는 shifter 회로이다.
만약 1bit shift와 1bit rotate등의 회로를 각각 구현한 후 합쳤다면 리소스를 훨씬 많이 사용했을 텐데, 위와 같이 설계하였기 때문에 배럴쉬프트 MUX16개 앞에 MUX 2개와 연산로직 하나만 추가된 형태의 최적화된 회로를 구현할 수 있었다.
16 비트 중에 7비트는 Opcode이고, 이 Opcode에 따라 뒤의 9비트가 어떤 포맷인지 정해진다. 그림을 보면 Register, Immediate, Jump and Branch에 대해 나타내고 있으며 아래의 예시를 통해 각각 어떤 동작을 위한 Instruction format인지 알 수 있다.
Register 방식 : R0 ← R1 + R2
Immediate 방식 : R0 ← R1 + 3
Jump and Branch 방식 : R0에 저장되어 있는 주소로 가서 값을 가져올 때 (PC값 변화)
- PC 상대주소지정방식: PC를 기준으로 주소 offset을 2의 보수 형태로 저장
8개의 레지스터가 있을 때, 각 오퍼랜드는 3bit를 사용한다.
한 클럭 사이클에 한 개의 명령어를 실행할 수 있다. 위의 그림을 보면 최소 3 사이클이 필요한 것을 알 수 있다.
1. PC에서 명령어를 인출하여 메모리에 저장한다.
2. Instruction decoder - 명령어해독, 연산 종류, 오퍼랜드 결정, 제어 신호 발생시킨다.
3. 오퍼랜드로부터 ALU 연산하여 data 메모리에 R/W
PC는 현재 명령어 인출 후 +1이 자동으로 증가되며, Branch/Jump 시는 증가 이전 값에 offset 처리된다.
예시의 그림은 Memory가 asynch였기 때문에 위와 같은 구조에서도 3사이클이 가능했다. 하지만 현재는 sync memory를 사용하기 때문에 MUXS의 연결을 끊고 PC와 바로 연결해주어 3 사이클로 만들었다.
위의 표를 보면,
1. IF 상태일 때, IR ← M[PC] PC 지정주소에서 instr read
PC ← PC+1 다음 instr을 위해 자동증가
2. EX0 상태일 때, IR값에 따라 instruction decoding -> opcode값에 따라 필요한 마이크로 연산 실행로 분기 (MUXC에서 MC =1)
3. opcode에 맞는 명령 수행 후 IF로 돌아감
2. CPU - ALU unit설계
2-1 Arithmetic Circuit
ALU unit의 Arithmetic Circuit을 먼저 만든다.
module fa1 (a, b, ci, co, g);
input a, b, ci;
output co, g;
assign co = ( a & b) | (ci & a ) | (ci & b);
assign g = a ^ b ^ ci;
endmodule
module func_Arith (A, B, S, Cin, G, tcout);
parameter BW = 16;
input [BW-1:0] A, B;
input Cin;
input [1:0] S;
output [BW-1:0] G;
output [1:0] tcout;
reg [BW-1:0] Y;
wire [BW:0] Cout;
assign Cout [0] = Cin;
assign tcout = Cout [16 : 15];
always @ (*)
begin
case(S)
2'b00 : begin Y= 16'b0; end
2'b01 : begin Y= B; end
2'b10 : begin Y= ~B; end
default : begin Y= 16'b1111111111111111; end
endcase
end
genvar i;
generate
for (i=0; i<BW; i=i+1) begin : add
fa1 FA_16b (.a(A[i]), .b(Y[i]), .ci(Cout[i]), .g(G[i]), .co(Cout[i+1]));
end
endgenerate
endmodule
1bit짜리 에더를 instanciation하여 16비트 짜리로 만들었다. 그리고 Y0 앞의 logic을 표현해줘야 했는데, 구조적 모델링 방식으로 gate를 만들어 연결하는 식으로 구현할 수도 있지만, 최대한 구조적 모델링을 지양하고 동작적 모델링으로 작성하였다. truth table을 보면 B의 값과 상관없이 Select신호에 따라 Y가 결정되기 때문에 wire Y를 선언하여 case문을 사용하여 작성해주었다. 이 때 16비트로 동일하게 만들었다. 이 때 유의할 점은 assign문에서 쓰일 땐 wire로, always ~case문에서 쓸 땐 reg로 선언해주어야 한다는 점이다. tcout은 c_flag와 v_flag (carry out, overflow) 생성을 위해 두 비트 할당하여 포트로 뽑아주었다.
2-2 Logical Circuit
module func_Logical (A, B, S, G);
parameter BW = 16;
input [BW-1:0] A, B;
input [1:0] S;
output reg [BW-1:0] G;
always @ (A,B,S,G)
begin
case(S)
2'b00 : begin G = A & B; end
2'b01 : begin G = A | B; end
2'b10 : begin G = A ^ B; end
default : begin G = ~A; end
endcase
end
endmodule
위에서 작성한 두 모듈을 인스턴시에이션하여 ALU unit을 완성하였다. arith와 logical 은 완성이 되어 있고, 그림과 표를 보면 결국 S[2]에 따라 arith 혹은 logical 의 결과가 ALU의 output으로 결정되는 것이기 때문에 assign으로 나타내주었다. arith에서 뽑아낸 Carry out 두 비트를 이용하여 v_flag와 c_flag를 만들었고, ALU output negative flag와 zero detect flag도 작성해주었다.
- v_flag : arith의 마지막 에더의 carry in과 carry out 비교
- c_flag : carry out값 (0 or 1)
- n_flag : ALU 연산 결과값의 MSB (부호) negative인지 비교
- z_flag : ALU 연산 결과값 모든 비트(16비트) NOR하여 0이 있는지 detect (0이 있으면 1)
RTL 표시 방식은 위와 같이 Text RTL, VHDL, Verilog가 있으며, 각각 operation 기호가 다르다.
1-2 MUX를 활용한 전송 연결
source register가 여러 개 일때 MUX를 활용하여 연결해줄 수 있다. K1 : RO ←R1, K'K2 : R0 ←R2 를 구현한다고 할 때, 2x1 MUX를 이용하여 R0 앞에 MUX를 붙여 조건에 따라 R1 혹은 R2가 R0에 연결되도록 만들 수 있다.
1-3 버스방식 전송 연결
(a) Dedicated Multiplexers 방식의 경우 자원을 많이 쓰지만 자유도가 높고 (b)single bus 방식의 경우 자원은 덜 사용하지만 자유도가 낮다.
2.데이터 처리 장치 (data path)
ALU : 마이크로 연산 실행 장치
구성 - 조합연산회로와 accumlator(reg)
실행시간 - 1uo/ 1ck
2 operand 입력, 1 출력
- CPU 주요 구성부
(1) data path 블록 - ALU, Shifter, Register, MUX, decoder, BUS연결회로 등
(2) 제어 신호 - uo를 수행하기 위한 적절한 신호 발생
간단히 그림에 대해 설명하자면, Decoder를 통해 Destination을 select할 수 있으며 이 신호와 load Enable 신호로 register를 select한다.
A select MUX를 통해 나온 Adata는 ALU로 들어오고, B data는 MUX로 통한다. 이 때 MB select가 0이면 B data가 ALU로, 1이면 constant in이 ALU로 들어간다. 이 MUX는 즉, 상수 계산 처리를 위해 존재한다. 특정 연산에 대한 instunction이 메모리 주소마다 저장되어 있다. G select 신호로 연산 종류를 선택하고, MF select에서는 ALU 출력을 내보낼지, Shift 출력을 내보낼지 정한다. 마지막으로 MUX D에서는 MD select로 F 값 혹은 메모리에서 나온 data값 중에 선택한다.
ex) R1 ← R2 + R3 구현
(1) A select = 10, R2를 A BUS에, B select=11, R3을 B BUS로 지정
(2) MB =0
(3) G → A+B연산 선택 (4) MF=0, ALU 출력선택 (5) MD=0, MUX F → D bus (6) D select=01, 즉 R1 선택 (7) load enable=1, clock edge에서 R1에 값 저장
3. ALU 설계
data path의 Function unit 구성 요소 중 하나인 ALU 설계이다.
ALU 설계를 위해 산술/논리 연산 logic이 필요하다. 연산의 종류는 S 를 통해 select 된다.
3-1 산술연산회로 설계
select 신호에 따라 Y값이 정해지고, output G는 input X, Y, Cin 값으로 결정된다. select신호와 B가 어떤 input logic을 통해 Y값이 결정되는지 아래의 그림에서 자세히 알 수 있다.
표를 보면 B값과 상관없이 S에 따라 Y값이 정해지는 것을 알 수 있고, 카르노 맵으로 식을 도출하면 오른쪽과 같이 input logic이 만들어진다.
3-2 논리연산회로 설계
S1S0에 의해 4개의 연산 중에 1개의 결과값이 output으로 출력되는 구조이다.
3-3 ALU 설계
위에서 만든 산술연산회로와 논리연산회로를 조합하여 ALU를 설계할 수 있다. 이 때 S2 신호로 두 연산 회로 중 원하는 연산회로를 선택할 수 있다. 따라서 최종적으로 S2 ~ S0신호와 Cin 으로 원하는 function을 구현하기 위한 Operation을 지정할 수 있다.
4. Shifter 회로
data path의 Function unit 구성 요소 중 하나인 Shifter 회로에 대한 설명이다.
S1S0 신호에 따라 hold/ Shift R/ Shift L 동작을 하는 회로이다. 위의 회로를 보면 S가 00일 때 각 MUX가 d0이 select되어 output으로 나간다. 이 경우 결국 원래 자리 그대로 비트가 출력되기 때문에 hold이다.
S가 01일때 d1이 select되어 output으로 나간다. 이 때 1bit씩 오른쪽으로 shift되고 맨 왼쪽 비트는 0이 채워진다. (IR값)
이 때 IR을 1'b0이 아닌 B0와 연결해준다면 rotate 기능이 된다.
S가 10일때 d2가 select되어 output으로 나간다. 즉 1bit씩 왼쪽으로 shift된 결과를 얻을 수 있다.
Barrel Shifter는 한 클럭에 n비트를 이동할 수 있는 shifter이다. 그림과 같이 회로가 구성되며 정확히는 shift라기보다 값이 rotate되는 구조이다.
5. Control word 제어 워드
마이크로 연산의 데이터처리를 위해서는 적절한 제어신호들이 필요하다. 이를 제어 워드라고 하며, RW, MD, FS, MB, BA, AA, DA 로 구성되어 있다. 적절한 코드 값을 줌으로써 원하는 데이터 처리 및 동작이 가능하다.
예를 들어 위의 그림과 같이 제어 워드를 정할 수 있다. DA/AA/BA는 연산에 필요한 register를 정하고, MB는 B data를 constant로 할지 Register값으로 할지를 정한다. FS는 연산의 종류를, MD는 Function값을 내보낼지 메모리부터 온 Data In값을 내보낼지를, RW는 레지스터에 write여부를 정한다.
위의 예시를 통해 다양한 마이크로 연산을 위해 제어워드를 어떻게 지정해야 하는지 알 수 있다.
1. FSM의 state를 parameter로 정의하여 사용하는 것이 좋다. `define을 이용할 경우 컴파일 과정에서 광역적 영향을 미치므로 다수의 FSM에서 동일한 state 사용 시 오류가 발생할 수 있기 때문이다.
2. FSM이 async reset을 할 수 있도록 설계해야 한다.
3. next state logic/ state register/ output logic 을 분리하여 코딩한다.
4. next state logic은 case문을 사용한다.
1-2 응용 예
/////////////// 3.3-1 Moore
module moore_m (clk,rst,x,z,ps,ns);
input wire clk, rst, x;
output reg [1:0] ps, ns;
output wire z;
parameter S0= 2'b00, S1= 2'b01, S2= 2'b10, S3= 2'b11;
always @ (posedge rst, posedge clk)
begin
if (rst) ps <= S0; //async reset
else ps <= ns; //present state <= next state
end
always @ (ps or x)
begin
case (ns)
S0 : if (x) ns = S1; else ns = S0;
S1 : if (x) ns = S2; else ns = S1;
S2 : if (x) ns = S3; else ns = S1;
S3 : ns = S0;
default : ns = S0;
endcase
end
assign z = (ps == S3) ? 1'b1 : 1'b0;
endmodule
output z가 동기형 출력인 이유는, ps가 S3상태로 가면 z값이 바로 결정되기 때문이다. (입력값 영향 x) -> moore 머신
테스트벤치를 보면 S0상태에서 x가 1이 되는 순간 ns가 1로 바뀌고 clock의 rising edge마다 ps <=ns가 되는 것을 확인할 수 있다.
밀리 머신의 경우 if (w) ~ else 문을 보면 알 수 있듯이 현재 상태와 입력 w 에 따라 출력이 변한다.
1-3 패턴 검사기 설계해보기
페턴 감사기 : 011011 감지
FSM 설계
module detect (clk,rst, st, x,y,ps,ns);
input wire clk, rst, st, x;
output [2:0] ps, ns;
output reg y;
reg [2:0] ps, ns;
parameter S0= 3'b000, S1= 3'b001, S2= 3'b010, S3= 3'b011, S4=3'b100, S5=3'b101, S6=3'b110, S7=3'b111;
always @ (posedge rst, posedge clk)
begin
if (rst) ps <= S0; //async reset
else ps <= ns; //present state <= next state
end
always @ (ps or x or st)
begin
case (ns)
S0 : begin y = 1'b0; if (st) ns = S1; else ns = S0; end
S1 : begin y = 1'b0; if (x) ns = S1; else ns = S2; end
S2 : begin y = 1'b0; if (x) ns = S3; else ns = S2; end
S3 : begin y = 1'b0; if (x) ns = S4; else ns = S2; end
S4 : begin y = 1'b0; if (x) ns = S1; else ns = S5; end
S5 : begin y = 1'b0; if (x) ns = S6; else ns = S2; end
S6 : begin y = 1'b0; if (x) ns = S7; else ns = S2; end
S7 : begin y = 1'b1; if (x) ns = S1; else ns = S5; end
default : begin y = 1'b0; ns = S0; end
endcase
end
endmodule
FSM에 따라 상태를 기술해주었다
module tb_detect;
reg clk = 1'b0, rst, st, x;
wire y;
wire [2:0] ps, ns;
detect dt1 (clk,rst, st, x,y,ps,ns);
always #10 clk = ~clk;
initial
begin
rst = 1'b1; st = 0'b0; x =1'b0;
#20 rst = 1'b0; st = 0'b1; x =1'b1;
#20 st = 0'b0; x =1'b0;
#20 x =1'b0;
#20 x =1'b1;
#20 x =1'b1;
#20 x =1'b0;
#20 x =1'b1;
#20 x =1'b1;
#20 x =1'b1;
#100;
$stop;
end
endmodule
테스트벤치를 작성할때, clock의 주기마다 x에 값을 할당해주었다. clock의 주기보다 짧게 delay를 줄 경우 ns의 값이 여러 번 바뀌어 의도대로 동작하지 않을 수 있다.
위의 시뮬레이션 결과를 확인해보면 정상적으로 011011을 detect하여 y값이 1이 된 것을 볼 수 있다.
4x1 MUX를 위와 같이 작성했다. assign문으로도 만들 수 있고 always ~ if 문으로도 만들 수 있지만, 중요한 것은 if 문 혹은 case문 사용시 꼭 default / else 를 써줘야 한다는 점이다.
위와 같이 else를 써주지 않고 mux_output m2를 만든 결과 스키매틱을 보면 MUX가 아닌 래치가 발생한 것을 확인할 수 있다.
1-2 비교기 (A greater than B)
module gt (a, b, agtb);
parameter BW = 4;
input [BW-1:0] a, b;
output reg agtb;
//reg agtb;
always @ (a, b)
begin
agtb = 1'b0;
if (a[3]>b[3]) agtb=1'b1;
else if ((a[3]==b[3]) && (a[2]>b[2])) agtb=1'b1;
else if ((a[3:2]==b[3:2]) && (a[1]>b[1])) agtb=1'b1;
else if ((a[3:1]==b[3:1]) && (a[0]>b[0])) agtb=1'b1;
else agtb=1'b0;
end
endmodule
a와 b의 값을 비교하여 a의 값이 더 클 경우 1을 출력하는 비교기이다. 유의해야 할 점은 자리수를 비교할 때, 백의 자리를 비교한다면, 조건문에 - 천의자리는 같다는 전제 하에 백의 자리는 클 경우로 작성해주어야 한다.
<테스트벤치>
module tb_abgt;
reg [3:0] a, b;
wire agtb;
gt U1 (a,b,agtb);
initial begin
a = 4'b0000; b = 4'b0000;
#10 a = 4'b1000; b = 4'b0000;
#10 a = 4'b1000; b = 4'b1000;
#10 a = 4'b1100; b = 4'b1000;
#10 a = 4'b1100; b = 4'b1100;
#10 a = 4'b1110; b = 4'b1100;
#10 a = 4'b1110; b = 4'b1110;
#10 a = 4'b1111; b = 4'b1110;
#10 a = 4'b1111; b = 4'b1111;
#10 a = 4'b0111; b = 4'b1111;
#10 a = 4'b1110; b = 4'b1111;
#10;
end
endmodule
테스트벤치로 시뮬레이션 돌린 결과이다. 파형을 보면 A가 B보다 클 때만 출력이 1이 되는 것을 확인할 수 있다.
조합회로 모델링 시 always @ () 구문에서 감시신호 목록에 input 신호들을 모두 포함시켜야 한다. 혹은 * 표시로 간단히 나타낼 수도 있지만 불필요한 리소스가 사용될 수 있어 input들을 직접 기술하는 방식이 권장된다.
if조건문과 case문 사용시 else/ default 를 꼭 기술해줘야 래치가 발생하지 않는다.
코드 작성 시 논리 최적화를 고려해야 한다. 즉, 최소의 게이트 수와 치소 지연경로를 갖는 회로가 합성되도록 해야 한다.
또한 소스코드를 간결하게 모델링하여 가독성과 디버깅을 용이하도록 해야 한다.
1-2 인코더 / 디코더
간단히 인코더는 비트의 압축, 디코더는 비트 압축 해제 이다.
위의 두 예시를 보면, 왼쪽 예시는 일반적인 인코더이고 오른쪽은 우선순위 인코더이다. 최상위 비트부터 0인지 1인지를 비교하여 0일 때 출력을 111부터 000까지 할당하고 있다.
위의 예시는 파라미터를 사용하여 어떻게 NtoM 디코더를 구현하는지에 대한 코드이다. 3to8 디코더 같은 경우는 모든 case를 작성하는 일이 부담스럽지 않지만 NtoM이 크다면 일일이 적기가 쉽지 않다. 이러한 경우 오른쪽과 같이 파라미터 값을 지정하고 always ~ if문을 사용하여 NtoM디코더를 구현할 수 있다.
2. 합성
2-1 기본 컨셉
합성 (synthesis) 이란? 동작적 또는 추상적(abstract) 형태의 논리 기술로 부터 구체적인 논리 구조 또는 회로를 생성 (최적화 포함)
→ synthesis tool 이 정확한 합성을 하기 위해, 그 이전 단계부터 회로를 정확히 모델링하는 것이 중요
Synthesis –Elaboration 후 회로를 생성하는 과정
Implementation – FPGA에 Place & Route 하는 과정
2-2 조합회로 모델링 유의사항
※ always block : 조합회로의 입력에 변화가 있을 때 마다 출력이 평가되는 동작 설계 시 사용
- 감지신호 목록 기입 - 좌변 출력변수는 reg 형으로 선언 (실제로 reg가 생성되는 것은 아님)
- if 문에서 else 문 사용 - case 문에서 default 사용
2-3 조합회로 모델링 연습 - comination 회로
8bit comb 회로를 사용하여 모델링 연습을 해본다. 사실 위 코드는 소프트웨어로 처리되고 있는데 이는 하드웨어 설계에 적합하지 않다. 너무 많은 리소스가 사용되기 때문이다.
그림은 합성 후의 회로이기 때문에 SUB MULTI 등의 로직이 최적화되어 나타내어진다.
다음은 comb 모듈의 BW 값을 수정하여 8bit와 16bit comb 회로의 차이를 확인해본다. 컴파일 후 Report Utilization 확인하면 사용된 하드웨어의 크기를 알 수 있다.
<8bit>16bit
두 util 결과를 확인해보면 16bit 회로가 리소스를 더 많이 사용하고 있음을 확인할 수 있다.