contributed by ???
Set a time unit CNT_MAX
, and change the value of io.led
(blkReg
's value) for each time unit to achieve the blinking effect of the LED light.
// Hello World in Chisel
class Hello extends Module {
val io = IO(new Bundle {
val led = Output(UInt(1.W))
})
val CNT_MAX = (50000000 / 2 - 1).U;
val cntReg = RegInit(0.U(32.W))
val blkReg = RegInit(0.U(1.W))
cntReg := cntReg + 1.U
when(cntReg === CNT_MAX) {
cntReg := 0.U
blkReg := ~blkReg
}
io.led := blkReg
}
Once the first three files are completed, connect them with other components to form a complete CPU.
During the IF (Instruction Fetch) stage, determine the PC source by checking io.jump_flag_id. If a JUMP is not taken, the instructions enter the CPU in their original order, and the next instruction is PC + 4.
From the above diagram, it can be observed that with each clock cycle change, the personal computer (PC) will continue to increment by 4 until io_jump_flag_id
is set to 1 (on the rising edge of the wave). This signifies that the instruction is a jump, and the next instruction will not be PC+4.
When the opcode of the instruction is type L, which is a LOAD instruction, it is necessary to read the instruction from memory. In this case, set io.memory_read_enable to 1.
object InstructionsTypeL {
val lb = "b000".U
val lh = "b001".U
val lw = "b010".U
val lbu = "b100".U
val lhu = "b101".U
}
When the opcode of the instruction is type S, which is a STORE instruction, it is necessary to write the instruction to memory. In this case, set io.memory_write_enable to 1.
object InstructionsTypeS {
val sb = "b000".U
val sh = "b001".U
val sw = "b010".U
}
From the above diagram, it can be seen that when the opcode is 0x23
, indicating an S-type instruction, io_memory_write_enable
should be set to 1 (on the rising edge of the wave). This signifies that data can be written into memory.
io.aluop1_source
determines the first source of the ALU. When io.aluop1_source
is set to 1 (on the rising edge of the wave), alu.io.op1
is connected to io.instruction_address
. When io.aluop1_source
is set to 0 (on the falling edge of the wave), alu.io.op1
is connected to io.reg1_data
.io.aluop2_source
determines the second source of the ALU. When io.aluop2_source
is set to 1 (on the rising edge of the wave), alu.io.op2
is connected to io.immediate
. When io.aluop2_source
is set to 0 (on the falling edge of the wave), alu.io.op2
is connected to io.reg2_data
.ALU, as an arithmetic logic unit, performs mathematical operations based on the specified function. The demonstration above illustrates addition. ex:io_reg1_data
+io_reg2_data
= alu_io_result
In the preceding stages, the connections within each component have been roughly established. In CPU.scala
, simply connecting the inputs and outputs between different components completes a ready-made CPU.
Transplant the test.S
file from HW2 to the target directory. Modify the makefile to convert the .S
file to a .asmbin
file for execution on Chisel. Simultaneously, add a class named HW2Test
in CPUTest.scala
to test the output of test.asmbin
.
The following is the content of HW2Test
:
class HW2Test extends AnyFlatSpec with ChiselScalatestTester {
behavior.of("Single Cycle CPU")
it should "perform HW2" in {
test(new TestTopModule("test.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.mem_debug_read_address.poke(0.U)
c.clock.step()
c.io.mem_debug_read_data.expect(0.U)
c.io.mem_debug_read_address.poke(4.U)
c.clock.step()
c.io.mem_debug_read_data.expect(0.U)
c.io.mem_debug_read_address.poke(8.U)
c.clock.step()
c.io.mem_debug_read_data.expect(1.U)
c.io.mem_debug_read_address.poke(12.U)
c.clock.step()
c.io.mem_debug_read_data.expect(1.U)
}
}
}
The following figure depicts the experimental results.
[info] lab2Test:
[info] Single Cycle CPU
[info] - should perform lab2
[info] Run completed in 7 seconds, 114 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 12 s, completed Nov 27, 2023, 10:26:36 PM
The original code printed the results through an ecall. In this experiment, a notable difference is that you store the experimental results in the target memory. During testing, you input the corresponding answers at the respective memory locations. If it is "success", it indicates that the experimental results match your expectations.
First instruction
addi sp,sp,-16
ff010113
is assembly code for addi sp,sp,-16
.addi
is an I-type instruction, so the immediate value is -16 (0xFFFFFFF0).sp
is 0, adding -16 results in -16. Therefore, io_result
and io_regs_write_data
have values of FFFFFFF0.Second instruction
la t0, cmp_data_1
Chisel does not have the la
instruction. As shown in the diagram, Chisel simulates the functionality of la
using two instructions: 00000297
and 26028293
.
Third instruction
sw t0, 0(sp)
00512023
is assembly code for sw t0, 0(sp)
.sw
is an S-type instruction, so the immediate value is 0.t0
is 00001264, coming from io_read_data2
.