Single-cycle processor 에서는 하나의 Instruction이 한 Clock 주기 안에 완료 되어야 한다.
Data path 대부분의 연산을 combinational circuit으로 구현하고, **state(상태)를 갖는 요소를 최소한으로 제한하는 것이 구조를 단순하게 만든다.
본 설계는 state를 갖는 PC, Register File, Memory를 sequential logic으로 구현했고,
나머지 연산은 combiantial logic으로 구현했다.
다만, 하나의 clock 내에 연산을 끝내기 위해, Register File과 Memory는 Reading를 combiantional logic으로, 무결성을 위해 Writing을 sequential logic으로 구현했다.
이러한 선택은 제어를 단순하게 만들고 설계 흐름을 명확하게 하지만,
clock 주기가 전체 연산 경로에 의해 제한된다는 trade-off를 가진다.
이는 single-cycle 구조가 가지는 근본적인 한계이기도 하다.
Instruction cycle
Fetch -> Decode -> Excute -> Memory -> Write_back
크게 6가지 Module으로 위 cycle을 구현한다.
명령어 주소 계산. 계산한 명령어 주소는 Instruction Memory의 input으로 들어가 명령어를 찾는다.
각 명령어를 구분하는 기준이 되는 module이다.
따라서 clk에 따라 값을 갱신하고, sequential circuit로 구현한다.
combinational circuit로 구현한다.
Figure 2. R-type RV32I Register type
두 개의 source register (rs1, rs2)를 동시에 읽고, 하나의 destination register (rd) 에 값을 저장하도록 만들었다.
레지스터에 Writing은 postive clock edge에만 발생하도록해 무결성을 보장했다.
Reading은 즉각적인 값 전달을 위해 combinational circuit으로 구현한다.
combinational circuit로 구현하면 될 듯하다.clk에 따라 값의 입력과 출력이 결정되게 구현한다. 즉 sequence circuit.combinational circuit으로 구현한다. Computer Organization Design 에서 설계 철학을 강조한다. 따라서 나만의 설계철학을 바탕으로 구현했다.
RISC-V ISA의 각 명령어 타입 (R, I, S, B, J ,U)를 하나씩 이해하며 필요한 데이터 경로와 모듈을 점진적으로 추가하는 (Bottom up) 방식을 채택
(ISA 내용 : https://velog.io/@liquetxnx22/1.-%EC%9D%B4%EB%A1%A0-RV32I-ISA-%EC%A0%95%EB%A6%AC)
R-type 명령어는 memory access와 branch를 포함하지 않기 때문에, 가장 단순한 data path를 가진다. 이 data path를 바탕으로 Instruction type의 경로를 확장해 나간다.
이유 : 직접 설계해보며 하드웨어 설계 감각 증진
예외적인 전용 경로 추가보다, 기존 경로 재사용을 늘리고, 규칙적인 구조 유지를 우선함
예시 :
AUIPC 명령어는 Branch adder가 아닌 ALU에서 PC + IMM으로 처리
PC 선택을 PC MUX로 통합 등
이유 : 구조가 규칙적이면 제어가 단순해지고 확장이 쉬워진다.
가장 추상화된 top module을 설계한다. 크게 Data가 흐르는 Data Path와 그 Data의 흐름을 제어하는 Control Path로 나눈다.
Data path는 명령어가 각 값들을 계산하는 과정을 모두 나타낸다.
Control path는 Control Unit으로 구현하며, 명령어의 Opcode, funct7, funct3의 정보를 받고 명령어의 계산 과정을 제어할 신호를 보낸다.
이후 각 Instruction type에 따라 data path를 설계하고 그에 맞는 필요한 control signal을 만든다.
Register_File 은 rs1, rs2, rd 정보를 얻어야 하니 이것들을 input으로 갖는다. 또한 계산결과를 레지스터파일에 다시 저장해야하니 또다른 input, WD을 갖는다.
Control_Unit은 Opcode, funct7, funct3을 input으로 입력받는다.
Opcode로 Instr type을 결정한다
위 예시는 R-type임으로 funct7, funct3에 따라 무슨 계산을 할지, ALU에 알려줘야하는 signal이 필요하다. -> 총 10가지 명령어이니, ALU_CONTROL [3:0] 으로 구현
또한
Register_File에 결과값을 입력해야 한다. 즉 Register_File에 작성 요청 signal이 필요하다. (RegWrite)
따라서 output으로 [3:0] ALU_control 과 RegWrite을 갖는다.
I-type은 IMM 값을 계산 인자로 사용하기에 ImmGen이라는 sign-extension 수행 block이 필요했다.
ImmGen은 Instr의 12bit를 sign-extension을 시행하는 block 이다.
Instr 상에서 Imm 값의 위치는 각 Instr type 마다 다르다. 따라서 ImmSrc를 추가해 무슨 타입인지 ImmGen에 알려 적절히 상수값 계산을 하게 돕는다.
ImmGen이 Opcode을 입력받지 않고 ImmSrc로 따로 입력받아 Instruction type 결정하는 이유
-> 모듈간 역할을 정확히 분리하기 위해서. Control unit은 signal만 보내고, Immgen은 부호 확장만 하게해 모듈을 명확히 추상화해 분리한다. 이를 통해 유지 보수성과 파이프라인 확장성에 더 도움이 된다.
->예를 들어 내가 Hazard 같은거 구현하려 할때, Immgen은 그대로 두고 Control unit만 업데이트하면 됨.
input으로 사용할지 결정하는 ALU MUX를 추가했다. (후에 알게된 사실인데 ALU Decoder을 굳이 module로 둘 필요는 없다. 각 module들을 top module에서 이을 때, top module에 mux로 구분지으면 될 듯하다.)
LW 명령어의 Data Flow와 Control Flow는 다음과 같다.
LW 명령어는 1. 주소값 계산 후, 2.주소에 맞는 Data를 메모리에서 찾아, 3. Register File에 저장한다.
Data Loading을 위한 Memory block이 추가되었다.
기존 ALU 연산 결과와 Memory Data 중 Register File에 저장될 값을 고르는 Write Back Mux를 추가했다.
JALR 명령어는 1. 주소값 계산 후, 2. 계산한 주소 값을 다음 분기 주소로 사용하고, 3. 기존의 분기 주소는 Register File에 저장한다.
기존에 4-byte 씩 증가시켰던 Instr address에 더불어, 새롭게 분기할 주소를 결정할 필요가 있었다.
-따라서 PC block을 변경하려 했는데, 그 과정 중 Chip Design 설계철학을 고려하기 시작했다.
따라서 PC adder 라는 일반적인 4-byte
기존 ALU 연산 결과와 Memory Data 중 Register File에 저장될 값을 고르는 Write Back Mux를 추가했다.