李尚宸
$ sudo apt install build-essential verilator gtkwave
$ curl -s "https://get.sdkman.io" | bash
$ source "$HOME/.sdkman/bin/sdkman-init.sh"
$ sdk install java 11.0.21-tem
$ sdk install sbt
You don't have to copy and paste Chisel code. Instead, discuss and reflect what you have done. Seek for compliance and improvements.
...
[info] Register File of Single Cycle CPU
[info] - should read the written content
[info] - should x0 always be zero
[info] - should read the writing content
[info] Run completed in 18 seconds, 613 milliseconds.
[info] Total number of tests run: 9
[info] Suites: completed 7, aborted 0
[info] Tests: succeeded 9, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 26 s, completed Jan 16, 2025, 9:41:13 AM
class InstructionFetchTest extends AnyFlatSpec with ChiselScalatestTester {
behavior.of("InstructionFetch of Single Cycle CPU")
it should "fetch instruction" in {
test(new InstructionFetch).withAnnotations(TestAnnotations.annos) { c =>
val entry = 0x1000
var pre = entry
var cur = pre
c.io.instruction_valid.poke(true.B)
var x = 0
for (x <- 0 to 100) {
Random.nextInt(2) match {
case 0 => // no jump
cur = pre + 4
c.io.jump_flag_id.poke(false.B)
c.clock.step()
c.io.instruction_address.expect(cur)
pre = pre + 4
case 1 => // jump
c.io.jump_flag_id.poke(true.B)
c.io.jump_address_id.poke(entry)
c.clock.step()
c.io.instruction_address.expect(entry)
pre = entry
}
}
}
}
}
In the InstructionFetchTest
, the goal is to verify the module's ability to correctly set the PC
(0x1000
) by using a multiplexer to choose between PC + 4.U
and jump_address_id
, with the selection determined randomly using Random.nextInt(2)
.
Original entry address: 0x1010
When io_jump_flag_id
set to 0, entry address = PC + 4.U
= 0x1014
When io_jump_flag_id
set to 1, entry address = 0x1000
class InstructionDecoderTest extends AnyFlatSpec with ChiselScalatestTester {
behavior.of("InstructionDecoder of Single Cycle CPU")
it should "produce correct control signal" in {
test(new InstructionDecode).withAnnotations(TestAnnotations.annos) { c =>
c.io.instruction.poke(0x00a02223L.U) // S-type
c.io.ex_aluop1_source.expect(ALUOp1Source.Register)
c.io.ex_aluop2_source.expect(ALUOp2Source.Immediate)
c.io.regs_reg1_read_address.expect(0.U)
c.io.regs_reg2_read_address.expect(10.U)
c.clock.step()
c.io.instruction.poke(0x000022b7L.U) // lui
c.io.regs_reg1_read_address.expect(0.U)
c.io.ex_aluop1_source.expect(ALUOp1Source.Register)
c.io.ex_aluop2_source.expect(ALUOp2Source.Immediate)
c.clock.step()
c.io.instruction.poke(0x002081b3L.U) // add
c.io.ex_aluop1_source.expect(ALUOp1Source.Register)
c.io.ex_aluop2_source.expect(ALUOp2Source.Register)
c.clock.step()
}
}
}
In the InstructionDecoderTest
, the task is to set io_memory_read_enable
and io_memory_write_enable
depending on the opcode.
For the first instruction sw x10, 4(x0)
:
io_memory_read_enable
should be 0io_memory_write_enable
should be 1For the second instruction lui x5, 2
:
io_memory_read_enable
should be 0io_memory_write_enable
should be 0FOr the third instruction add x3, x1, x2
:
io_memory_read_enable
should be 0io_memory_write_enable
should be 0class ExecuteTest extends AnyFlatSpec with ChiselScalatestTester {
behavior.of("Execution of Single Cycle CPU")
it should "execute correctly" in {
test(new Execute).withAnnotations(TestAnnotations.annos) { c =>
c.io.instruction.poke(0x001101b3L.U) // x3 = x2 + x1
var x = 0
for (x <- 0 to 100) {
val op1 = scala.util.Random.nextInt(429496729)
val op2 = scala.util.Random.nextInt(429496729)
val result = op1 + op2
val addr = scala.util.Random.nextInt(32)
c.io.reg1_data.poke(op1.U)
c.io.reg2_data.poke(op2.U)
c.clock.step()
c.io.mem_alu_result.expect(result.U)
c.io.if_jump_flag.expect(0.U)
}
// beq test
c.io.instruction.poke(0x00208163L.U) // pc + 2 if x1 === x2
c.io.instruction_address.poke(2.U)
c.io.immediate.poke(2.U)
c.io.aluop1_source.poke(1.U)
c.io.aluop2_source.poke(1.U)
c.clock.step()
// equ
c.io.reg1_data.poke(9.U)
c.io.reg2_data.poke(9.U)
c.clock.step()
c.io.if_jump_flag.expect(1.U)
c.io.if_jump_address.expect(4.U)
// not equ
c.io.reg1_data.poke(9.U)
c.io.reg2_data.poke(19.U)
c.clock.step()
c.io.if_jump_flag.expect(0.U)
c.io.if_jump_address.expect(4.U)
}
}
}
In the ExecuteTest
, the task is to connect alu_func
and set alu.io.op1
and alu.io.op2
based on their respective sources.
alu.io.op1
should be io_op1
alu.io.op2
should be io_op2
For the first instruction add x3, x1, x2
:
For the second instruction, beq x1, x2, 2
:
When x1 != x2
, io_if_jump_flag
should be 0
When x1 == x2
, io_if_jump_flag
should be 1
The M standard extension is for integer multiplication and division instructions. RV32IM
is the RV32I
instruction set with the M standard extension, which includes the mul
, mulh
, mulhsu
, mulhum
, div
, divu
, rem
, and remu
instructions.
Code can be found in
src/main/scala/riscv/core
Add the corresponding operations for each M instruction respectively.
The diagrams below show the instruction codes for RV32I
and RV32M
. Both share the same opcode (0110011
for R-type instructions), but RV32M
is identified by funct7 = 0x01
, while RV32I uses other funct7
values 0x00
.
We can distinguish between the two using funct7
, as shown in the following code:
...
is(InstructionTypes.RM) {
when(io.funct7 === "b0000000".U) {
io.alu_funct := MuxLookup(
io.funct3,
ALUFunctions.zero,
IndexedSeq(
InstructionsTypeR.add_sub -> Mux(io.funct7(5), ALUFunctions.sub, ALUFunctions.add),
InstructionsTypeR.sll -> ALUFunctions.sll,
InstructionsTypeR.slt -> ALUFunctions.slt,
InstructionsTypeR.sltu -> ALUFunctions.sltu,
InstructionsTypeR.xor -> ALUFunctions.xor,
InstructionsTypeR.or -> ALUFunctions.or,
InstructionsTypeR.and -> ALUFunctions.and,
InstructionsTypeR.sr -> Mux(io.funct7(5), ALUFunctions.sra, ALUFunctions.srl)
)
)
}.elsewhen(io.funct7 === "b0000001".U) {
io.alu_funct := MuxLookup(
io.funct3,
ALUFunctions.zero,
IndexedSeq(
InstructionsTypeM.mul -> ALUFunctions.mul,
InstructionsTypeM.mulh -> ALUFunctions.mulh,
InstructionsTypeM.mulhsu -> ALUFunctions.mulhsu,
InstructionsTypeM.mulhum -> ALUFunctions.mulhum,
InstructionsTypeM.div -> ALUFunctions.div,
InstructionsTypeM.divu -> ALUFunctions.divu,
InstructionsTypeM.rem -> ALUFunctions.rem,
InstructionsTypeM.remu -> ALUFunctions.remu
)
)
}.otherwise {
io.alu_funct := ALUFunctions.zero
}
}
Reference: RISCOF Official Documentation
The riscv-arch-test is an official RISC-V Architectural Test Framework used to verify compliance with the RISC-V Instruction Set Architecture (ISA) specifications. It ensures that a processor implementation correctly implements the required features of the RISC-V ISA.
$ sudo apt-get install python3.6
$ pip3 install --upgrade pip
$ pip3 install git+https://github.com/riscv/riscof.git
$ git clone https://github.com/riscv-non-isa/riscv-arch-test
$ cd riscv-arch-test
$ cd riscv-ctg
$ pip3 install --editable .
$ cd riscv-isac
$ pip3 install --editable .
The RISC-V GNU Toolchain is a complete set of tools based on the GNU Project, customized for the RISC-V architecture. It provides essential tools for compiling, linking, and debugging applications targeting RISC-V processors.
The official website provides the following installation steps. However, I encountered an error while executing the command:
$ sudo apt-get install autoconf automake autotools-dev curl python3 libmpc-dev \
libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool \
patchutils bc zlib1g-dev libexpat-dev
$ git clone --recursive https://github.com/riscv/riscv-gnu-toolchain
$ git clone --recursive https://github.com/riscv/riscv-opcodes.git
$ cd riscv-gnu-toolchain
$ ./configure --prefix=/path/to/install --with-arch=rv32gc --with-abi=ilp32d # for 32-bit toolchain
$ [sudo] make # sudo is required depending on the path chosen in the previous setup
To resolve this issue, I opted to download the prebuilt RISC-V GNU toolchain.
Then add the path /path/to/install
to $PATH in the .bashrc/cshrc
.
Install SPIKE (riscv-isa-sim)
$ sudo apt-get install device-tree-compiler
$ git clone https://github.com/riscv-software-src/riscv-isa-sim.git
$ cd riscv-isa-sim
$ mkdir build
$ cd build
$ ../configure --prefix=/path/to/install
$ make
$ [sudo] make install
Install SAIL (SAIL C-emulator)
$ sudo apt-get install libgmp-dev pkg-config zlib1g-dev curl
$ curl --location https://github.com/rems-project/sail/releases/download/0.18-linux-binary/sail.tar.gz | [sudo] tar xvz --directory=/path/to/install --strip-components=1
$ git clone https://github.com/riscv/sail-riscv.git
$ cd sail-riscv
$ ARCH=RV32 make
$ ARCH=RV64 make
I rewrote three RISC-V programs to test the correctness of the M extension. The following are my implementations.
This program multiplies two numbers using the mul instruction in RISC-V and stores the result
.global _start
_start:
li a1, 9 # Load multiplier value
li a3, 7 # Load multiplicand value
mul t0, a1, a3 # Perform multiplication (t0 = a1 * a3)
loop:
j loop # Infinite loop to halt execution
This program implements Ancient Egyptian Multiplication using the M extension for multiplication and division.
.global _start
_start:
li t0, 13 # Load the first number (num1) into t0
li t1, 7 # Load the second number (num2) into t1
li t2, 0 # Initialize the result (t2) to 0
loop:
andi t3, t0, 1 # Check if the least significant bit of t0 is 1 (i.e., if t0 is odd)
beq t3, x0, skip_add # If t0 is even, skip the addition step
# If t0 is odd, add t1 (num2) to the current result in t2
add t2, t2, t1 # Accumulate the value of t1 in t2
skip_add:
li t3, 2 # Load immediate value 2 into t3
mul t1, t1, t3 # Multiply t1 by 2 (t1 = t1 * 2)
div t0, t0, t3 # Divide t0 by 2 (t0 = t0 / 2)
bnez t0, loop # If t0 is not zero, continue the loop
end:
nop # Placeholder for any further operations
This program calculates the square of an integer (a0 = n) and returns the result in a0.
.global _start
_start:
li a0, 13 # Load the number (13) into register a0
# Square the number directly in a0
mul a0, a0, a0 # a0 = a0 * a0 (square the number using M extension)
end:
nop # Placeholder for any further operations
makefile
to enable M Extension+ CROSS_COMPILE ?= riscv32-unknown-elf-
- CROSS_COMPILE ?= riscv-none-elf-
+ ASFLAGS = -march=rv32im_zicsr -mabi=ilp32
- ASFLAGS = -march=rv32i_zicsr -mabi=ilp32
+ CFLAGS = -O0 -Wall -march=rv32im_zicsr -mabi=ilp32
- CFLAGS = -O0 -Wall -march=rv32i_zicsr -mabi=ilp32
LDFLAGS = --oformat=elf32-littleriscv
AS := $(CROSS_COMPILE)as
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy
%.o: %.S
$(AS) -R $(ASFLAGS) -o $@ $<
%.elf: %.S
$(AS) -R $(ASFLAGS) -o $(@:.elf=.o) $<
$(CROSS_COMPILE)ld -o $@ -T link.lds $(LDFLAGS) $(@:.elf=.o)
%.elf: %.c init.o
$(CC) $(CFLAGS) -c -o $(@:.elf=.o) $<
$(CROSS_COMPILE)ld -o $@ -T link.lds $(LDFLAGS) $(@:.elf=.o) init.o
%.asmbin: %.elf
$(OBJCOPY) -O binary -j .text -j .data $< $@
BINS = \
fibonacci.asmbin \
hello.asmbin \
mmio.asmbin \
quicksort.asmbin \
sb.asmbin \
+ quiz2_problem_a.asmbin \
+ quiz2_problem_d.asmbin \
+ quiz4_problem_a.asmbin
# Clear the .DEFAULT_GOAL special variable, so that the following turns
# to the first target after .DEFAULT_GOAL is not set.
.DEFAULT_GOAL :=
all: $(BINS)
update: $(BINS)
cp -f $(BINS) ../src/main/resources
clean:
$(RM) *.o *.elf *.asmbin
class MultiplyTest extends AnyFlatSpec with ChiselScalatestTester {
behavior.of("Arithmetic Operations using M Extension")
it should "perform multiplication correctly" in {
test(new TestTopModule("quiz2_problem_a.asmbin")).withAnnotations(TestAnnotations.annos) { c =>
for (i <- 1 to 50) {
c.clock.step(1000)
c.io.mem_debug_read_address.poke((i * 4).U) // Avoid timeout
}
c.io.regs_debug_read_address.poke(5.U) // Address of "t0"
c.clock.step()
c.io.regs_debug_read_data.expect(63.U) // Verify multiplication result
}
}
}
class EgyptianMultiplicationTest extends AnyFlatSpec with ChiselScalatestTester {
behavior.of("Arithmetic Operations using M Extension")
it should "implement Ancient Egyptian multiplication correctly" in {
test(new TestTopModule("quiz2_problem_d.asmbin")).withAnnotations(TestAnnotations.annos) { c =>
for (i <- 1 to 50) {
c.clock.step(1000)
c.io.mem_debug_read_address.poke((i * 4).U) // Avoid timeout
}
c.io.regs_debug_read_address.poke(7.U) // Address of "t2"
c.clock.step()
c.io.regs_debug_read_data.expect(91.U) // Verify multiplication result
}
}
}
class SquareTest extends AnyFlatSpec with ChiselScalatestTester {
behavior.of("Arithmetic Operations using M Extension")
it should "compute the square of a number correctly" in {
test(new TestTopModule("quiz4_problem_a.asmbin")).withAnnotations(TestAnnotations.annos) { c =>
for (i <- 1 to 50) {
c.clock.step(1000)
c.io.mem_debug_read_address.poke((i * 4).U) // Avoid timeout
}
c.io.regs_debug_read_address.poke(10.U) // Address of "a0"
c.clock.step()
c.io.regs_debug_read_data.expect(169.U) // Verify square calculation result
}
}
}