mirror of
https://github.com/handsomezhuzhu/2025-yatcpu.git
synced 2026-02-20 20:10:14 +00:00
Lab3 pipelined CPU renewed
- added tutorial - fix ID reg addr invalid in certain types of instructions - renamed some variables for better understanding
This commit is contained in:
@@ -23,17 +23,19 @@ class Top extends Module {
|
||||
val io = IO(new CPUBundle)
|
||||
|
||||
val cpu = Module(new CPU(implementation = ImplementationType.ThreeStage))
|
||||
|
||||
io.device_select := 0.U
|
||||
cpu.io.debug_read_address := io.debug_read_address
|
||||
io.debug_read_data := cpu.io.debug_read_data
|
||||
|
||||
// intercept UART signals
|
||||
io.device_select := cpu.io.device_select
|
||||
|
||||
// CPU instruction input is controlled by external codes
|
||||
io.memory_bundle <> cpu.io.memory_bundle
|
||||
io.instruction_address := cpu.io.instruction_address
|
||||
cpu.io.instruction := io.instruction
|
||||
cpu.io.instruction_valid := io.instruction_valid
|
||||
|
||||
cpu.io.interrupt_flag := io.interrupt_flag
|
||||
cpu.io.instruction_valid := io.instruction_valid
|
||||
}
|
||||
|
||||
object VerilogGenerator extends App {
|
||||
|
||||
@@ -82,6 +82,11 @@ class Top(binaryFilename: String = "say_goodbye.asmbin") extends Module {
|
||||
cpu.io.memory_bundle <> mem.io.bundle
|
||||
}
|
||||
}
|
||||
|
||||
when (uart.io.bundle.write_enable) {
|
||||
val the_char = cpu.io.memory_bundle.write_data(7, 0)
|
||||
printf(cf"${the_char.asUInt}%c")
|
||||
}
|
||||
}
|
||||
|
||||
// LED, blinks every second
|
||||
|
||||
@@ -81,6 +81,11 @@ class Top(binaryFilename: String = "say_goodbye.asmbin") extends Module {
|
||||
cpu.io.memory_bundle <> mem.io.bundle
|
||||
}
|
||||
}
|
||||
|
||||
when (uart.io.bundle.write_enable) {
|
||||
val the_char = cpu.io.memory_bundle.write_data(7, 0)
|
||||
printf(cf"${the_char.asUInt}%c")
|
||||
}
|
||||
}
|
||||
|
||||
// LED, blinks every second
|
||||
|
||||
@@ -61,8 +61,8 @@ class CPU extends Module {
|
||||
inst_fetch.io.rom_instruction := io.instruction
|
||||
inst_fetch.io.instruction_valid := io.instruction_valid
|
||||
|
||||
if2id.io.stall := ctrl.io.if_stall
|
||||
if2id.io.flush := ctrl.io.if_flush
|
||||
if2id.io.stall := ctrl.io.if2id_stall
|
||||
if2id.io.flush := ctrl.io.if2id_flush
|
||||
if2id.io.instruction := inst_fetch.io.id_instruction
|
||||
if2id.io.instruction_address := inst_fetch.io.instruction_address
|
||||
if2id.io.interrupt_flag := io.interrupt_flag
|
||||
@@ -78,7 +78,7 @@ class CPU extends Module {
|
||||
id.io.interrupt_assert := clint.io.id_interrupt_assert
|
||||
id.io.interrupt_handler_address := clint.io.id_interrupt_handler_address
|
||||
|
||||
id2ex.io.flush := ctrl.io.id_flush
|
||||
id2ex.io.flush := ctrl.io.id2ex_flush
|
||||
id2ex.io.instruction := if2id.io.output_instruction
|
||||
id2ex.io.instruction_address := if2id.io.output_instruction_address
|
||||
id2ex.io.reg1_data := regs.io.read_data1
|
||||
|
||||
@@ -28,16 +28,16 @@ class Control extends Module {
|
||||
val memory_read_enable_mem = Input(Bool())
|
||||
val rd_mem = Input(UInt(Parameters.PhysicalRegisterAddrWidth))
|
||||
|
||||
val if_flush = Output(Bool())
|
||||
val id_flush = Output(Bool())
|
||||
val if2id_flush = Output(Bool())
|
||||
val id2ex_flush = Output(Bool())
|
||||
val pc_stall = Output(Bool())
|
||||
val if_stall = Output(Bool())
|
||||
val if2id_stall = Output(Bool())
|
||||
})
|
||||
|
||||
// Lab3(Final)
|
||||
io.if_flush := false.B
|
||||
io.id_flush := false.B
|
||||
io.if2id_flush := false.B
|
||||
io.id2ex_flush := false.B
|
||||
io.pc_stall := false.B
|
||||
io.if_stall := false.B
|
||||
io.if2id_stall := false.B
|
||||
// Lab3(Final) End
|
||||
}
|
||||
|
||||
@@ -170,8 +170,10 @@ class InstructionDecode extends Module {
|
||||
val rs1 = io.instruction(19, 15)
|
||||
val rs2 = io.instruction(24, 20)
|
||||
|
||||
io.regs_reg1_read_address := Mux(opcode === Instructions.lui, 0.U(Parameters.PhysicalRegisterAddrWidth), rs1)
|
||||
// Lab3(Final) ID rs
|
||||
io.regs_reg1_read_address := rs1
|
||||
io.regs_reg2_read_address := rs2
|
||||
// Lab3(Final) ID rs End
|
||||
io.ex_immediate := MuxLookup(
|
||||
opcode,
|
||||
Cat(Fill(20, io.instruction(31)), io.instruction(31, 20)),
|
||||
@@ -208,10 +210,10 @@ class InstructionDecode extends Module {
|
||||
Instructions.jalr -> RegWriteSource.NextInstructionAddress
|
||||
)
|
||||
)
|
||||
io.ex_reg_write_enable := (opcode === InstructionTypes.RM) || (opcode === InstructionTypes.I) ||
|
||||
(opcode === InstructionTypes.L) || (opcode === Instructions.auipc) || (opcode === Instructions.lui) ||
|
||||
(opcode === Instructions.jal) || (opcode === Instructions.jalr) || (opcode === Instructions.csr)
|
||||
io.ex_reg_write_address := io.instruction(11, 7)
|
||||
// Lab3(Final) ID rd
|
||||
io.ex_reg_write_enable := false.B
|
||||
io.ex_reg_write_address := rd
|
||||
// Lab3(Final) ID rd End
|
||||
io.ex_csr_address := io.instruction(31, 20)
|
||||
io.ex_csr_write_enable := (opcode === Instructions.csr) && (
|
||||
funct3 === InstructionsTypeCSR.csrrw || funct3 === InstructionsTypeCSR.csrrwi ||
|
||||
|
||||
@@ -58,15 +58,15 @@ class CPU extends Module {
|
||||
inst_fetch.io.rom_instruction := io.instruction
|
||||
inst_fetch.io.instruction_valid := io.instruction_valid
|
||||
|
||||
if2id.io.stall := ctrl.io.if_stall
|
||||
if2id.io.flush := ctrl.io.if_flush
|
||||
if2id.io.stall := ctrl.io.if2id_stall
|
||||
if2id.io.flush := ctrl.io.if2id_flush
|
||||
if2id.io.instruction := inst_fetch.io.id_instruction
|
||||
if2id.io.instruction_address := inst_fetch.io.instruction_address
|
||||
if2id.io.interrupt_flag := io.interrupt_flag
|
||||
|
||||
id.io.instruction := if2id.io.output_instruction
|
||||
|
||||
id2ex.io.flush := ctrl.io.id_flush
|
||||
id2ex.io.flush := ctrl.io.id2ex_flush
|
||||
id2ex.io.instruction := if2id.io.output_instruction
|
||||
id2ex.io.instruction_address := if2id.io.output_instruction_address
|
||||
id2ex.io.reg1_data := regs.io.read_data1
|
||||
|
||||
@@ -25,16 +25,16 @@ class Control extends Module {
|
||||
val memory_read_enable_ex = Input(Bool())
|
||||
val rd_ex = Input(UInt(Parameters.PhysicalRegisterAddrWidth))
|
||||
|
||||
val if_flush = Output(Bool())
|
||||
val id_flush = Output(Bool())
|
||||
val if2id_flush = Output(Bool())
|
||||
val id2ex_flush = Output(Bool())
|
||||
val pc_stall = Output(Bool())
|
||||
val if_stall = Output(Bool())
|
||||
val if2id_stall = Output(Bool())
|
||||
})
|
||||
|
||||
// Lab3(Forward)
|
||||
io.if_flush := false.B
|
||||
io.id_flush := false.B
|
||||
io.if2id_flush := false.B
|
||||
io.id2ex_flush := false.B
|
||||
io.pc_stall := false.B
|
||||
io.if_stall := false.B
|
||||
io.if2id_stall := false.B
|
||||
// Lab3(Forward) End
|
||||
}
|
||||
|
||||
@@ -156,8 +156,10 @@ class InstructionDecode extends Module {
|
||||
val rs1 = io.instruction(19, 15)
|
||||
val rs2 = io.instruction(24, 20)
|
||||
|
||||
io.regs_reg1_read_address := Mux(opcode === Instructions.lui, 0.U(Parameters.PhysicalRegisterAddrWidth), rs1)
|
||||
// Lab3(Forwarding) ID rs
|
||||
io.regs_reg1_read_address := rs1
|
||||
io.regs_reg2_read_address := rs2
|
||||
// Lab3(Forwarding) ID rs End
|
||||
io.ex_immediate := MuxLookup(
|
||||
opcode,
|
||||
Cat(Fill(20, io.instruction(31)), io.instruction(31, 20)),
|
||||
@@ -194,10 +196,10 @@ class InstructionDecode extends Module {
|
||||
Instructions.jalr -> RegWriteSource.NextInstructionAddress
|
||||
)
|
||||
)
|
||||
io.ex_reg_write_enable := (opcode === InstructionTypes.RM) || (opcode === InstructionTypes.I) ||
|
||||
(opcode === InstructionTypes.L) || (opcode === Instructions.auipc) || (opcode === Instructions.lui) ||
|
||||
(opcode === Instructions.jal) || (opcode === Instructions.jalr) || (opcode === Instructions.csr)
|
||||
io.ex_reg_write_address := io.instruction(11, 7)
|
||||
// Lab3(Forwarding) ID rd
|
||||
io.ex_reg_write_enable := false.B
|
||||
io.ex_reg_write_address := rd
|
||||
// Lab3(Forwarding) ID rd End
|
||||
io.ex_csr_address := io.instruction(31, 20)
|
||||
io.ex_csr_write_enable := (opcode === Instructions.csr) && (
|
||||
funct3 === InstructionsTypeCSR.csrrw || funct3 === InstructionsTypeCSR.csrrwi ||
|
||||
|
||||
@@ -59,15 +59,15 @@ class CPU extends Module {
|
||||
inst_fetch.io.rom_instruction := io.instruction
|
||||
inst_fetch.io.instruction_valid := io.instruction_valid
|
||||
|
||||
if2id.io.stall := ctrl.io.if_stall
|
||||
if2id.io.flush := ctrl.io.if_flush
|
||||
if2id.io.stall := ctrl.io.if2id_stall
|
||||
if2id.io.flush := ctrl.io.if2id_flush
|
||||
if2id.io.instruction := inst_fetch.io.id_instruction
|
||||
if2id.io.instruction_address := inst_fetch.io.instruction_address
|
||||
if2id.io.interrupt_flag := io.interrupt_flag
|
||||
|
||||
id.io.instruction := if2id.io.output_instruction
|
||||
|
||||
id2ex.io.flush := ctrl.io.id_flush
|
||||
id2ex.io.flush := ctrl.io.id2ex_flush
|
||||
id2ex.io.instruction := if2id.io.output_instruction
|
||||
id2ex.io.instruction_address := if2id.io.output_instruction_address
|
||||
id2ex.io.reg1_data := regs.io.read_data1
|
||||
|
||||
@@ -27,17 +27,17 @@ class Control extends Module {
|
||||
val rd_mem = Input(UInt(Parameters.PhysicalRegisterAddrWidth))
|
||||
val reg_write_enable_mem = Input(Bool())
|
||||
|
||||
val if_flush = Output(Bool())
|
||||
val id_flush = Output(Bool())
|
||||
val if2id_flush = Output(Bool())
|
||||
val id2ex_flush = Output(Bool())
|
||||
val pc_stall = Output(Bool())
|
||||
val if_stall = Output(Bool())
|
||||
val if2id_stall = Output(Bool())
|
||||
})
|
||||
|
||||
// Lab3(Stall)
|
||||
io.if_flush := false.B
|
||||
io.id_flush := false.B
|
||||
io.if2id_flush := false.B
|
||||
io.id2ex_flush := false.B
|
||||
|
||||
io.pc_stall := false.B
|
||||
io.if_stall := false.B
|
||||
io.if2id_stall := false.B
|
||||
// Lab3(Stall) End
|
||||
}
|
||||
|
||||
@@ -156,8 +156,10 @@ class InstructionDecode extends Module {
|
||||
val rs1 = io.instruction(19, 15)
|
||||
val rs2 = io.instruction(24, 20)
|
||||
|
||||
io.regs_reg1_read_address := Mux(opcode === Instructions.lui, 0.U(Parameters.PhysicalRegisterAddrWidth), rs1)
|
||||
// Lab3(Stall) ID rs
|
||||
io.regs_reg1_read_address := rs1
|
||||
io.regs_reg2_read_address := rs2
|
||||
// Lab3(Stall) ID rs End
|
||||
io.ex_immediate := MuxLookup(
|
||||
opcode,
|
||||
Cat(Fill(20, io.instruction(31)), io.instruction(31, 20)),
|
||||
@@ -194,10 +196,10 @@ class InstructionDecode extends Module {
|
||||
Instructions.jalr -> RegWriteSource.NextInstructionAddress
|
||||
)
|
||||
)
|
||||
io.ex_reg_write_enable := (opcode === InstructionTypes.RM) || (opcode === InstructionTypes.I) ||
|
||||
(opcode === InstructionTypes.L) || (opcode === Instructions.auipc) || (opcode === Instructions.lui) ||
|
||||
(opcode === Instructions.jal) || (opcode === Instructions.jalr) || (opcode === Instructions.csr)
|
||||
io.ex_reg_write_address := io.instruction(11, 7)
|
||||
// Lab3(Stall) ID rd
|
||||
io.ex_reg_write_enable := false.B
|
||||
io.ex_reg_write_address := rd
|
||||
// Lab3(Stall) ID rd End
|
||||
io.ex_csr_address := io.instruction(31, 20)
|
||||
io.ex_csr_write_enable := (opcode === Instructions.csr) && (
|
||||
funct3 === InstructionsTypeCSR.csrrw || funct3 === InstructionsTypeCSR.csrrwi ||
|
||||
|
||||
@@ -71,3 +71,4 @@ class FiveStageCPUForwardTest extends AnyFlatSpec with ChiselScalatestTester {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ package riscv
|
||||
import chisel3._
|
||||
import chiseltest._
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import scala.util.Random
|
||||
import riscv.core.fivestage_stall._
|
||||
|
||||
|
||||
class FiveStageCPUStallTest extends AnyFlatSpec with ChiselScalatestTester {
|
||||
@@ -71,3 +73,86 @@ class FiveStageCPUStallTest extends AnyFlatSpec with ChiselScalatestTester {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class DecoderStallTest extends AnyFlatSpec with ChiselScalatestTester {
|
||||
behavior of "ID of Five-stage Pipelined CPU with Stalling"
|
||||
|
||||
def concatBits(values: (Int, Int)*): Int = {
|
||||
values.foldLeft(0) { case (result, (value, bits)) =>
|
||||
val mask = (1 << bits) - 1 // Create mask for the specified bit width
|
||||
val maskedValue = value & mask // Ensure value fits in specified bits
|
||||
(result << bits) | maskedValue
|
||||
}
|
||||
}
|
||||
|
||||
it should "generate correct reg addr" in {
|
||||
test(new InstructionDecode).withAnnotations(TestAnnotations.annos) { c =>
|
||||
|
||||
for (i <- 0 to 100) {
|
||||
val rs1 = Random.nextInt(32)
|
||||
val rs2 = Random.nextInt(32)
|
||||
val rd = Random.nextInt(32)
|
||||
|
||||
// for R-type instructions, rs2, rs1 and rd should be valid
|
||||
// val instR = 0.U(7.W) ## rs2 ## rs1 ## 1.U(3.W) ## rd ## InstructionTypes.RM
|
||||
val instR = concatBits(
|
||||
(0, 7), (rs2, 5), (rs1, 5), (1, 3), (rd, 5), (InstructionTypes.RM.litValue.toInt, 7)
|
||||
)
|
||||
c.io.instruction.poke(instR)
|
||||
c.io.regs_reg1_read_address.expect(rs1)
|
||||
c.io.regs_reg2_read_address.expect(rs2)
|
||||
c.io.ex_reg_write_address.expect(rd)
|
||||
c.io.ex_reg_write_enable.expect(true.B)
|
||||
c.clock.step()
|
||||
|
||||
// for I-type instructions, rs1 and rd should be valid
|
||||
val instI = concatBits((0, 12), (rs1, 5), (1, 3), (rd, 5), (InstructionTypes.I.litValue.toInt, 7))
|
||||
c.io.instruction.poke(instI)
|
||||
c.io.regs_reg1_read_address.expect(rs1)
|
||||
c.io.regs_reg2_read_address.expect(0.U)
|
||||
c.io.ex_reg_write_address.expect(rd)
|
||||
c.io.ex_reg_write_enable.expect(true.B)
|
||||
c.clock.step()
|
||||
|
||||
// for S-type instructions, rs2 and rs1 should be valid
|
||||
val instS = concatBits((0, 7), (rs2, 5), (rs1, 5), (1, 3), (2, 5), (InstructionTypes.S.litValue.toInt, 7))
|
||||
c.io.instruction.poke(instS)
|
||||
c.io.regs_reg1_read_address.expect(rs1)
|
||||
c.io.regs_reg2_read_address.expect(rs2)
|
||||
c.io.ex_reg_write_address.expect(0.U)
|
||||
c.io.ex_reg_write_enable.expect(false.B)
|
||||
c.clock.step()
|
||||
|
||||
// for B-type instructions, rs2 and rs1 should be valid
|
||||
val instB = concatBits((0, 7), (rs2, 5), (rs1, 5), (1, 3), (2, 5), (InstructionTypes.B.litValue.toInt, 7))
|
||||
c.io.instruction.poke(instB)
|
||||
c.io.regs_reg1_read_address.expect(rs1)
|
||||
c.io.regs_reg2_read_address.expect(rs2)
|
||||
c.io.ex_reg_write_address.expect(0.U)
|
||||
c.io.ex_reg_write_enable.expect(false.B)
|
||||
c.clock.step()
|
||||
|
||||
// for U-type instructions, rd should be valid
|
||||
val instU = concatBits((0, 20), (rd, 5), (Instructions.lui.litValue.toInt, 7))
|
||||
c.io.instruction.poke(instU)
|
||||
c.io.regs_reg1_read_address.expect(0.U)
|
||||
c.io.regs_reg2_read_address.expect(0.U)
|
||||
c.io.ex_reg_write_address.expect(rd)
|
||||
c.io.ex_reg_write_enable.expect(true.B)
|
||||
c.clock.step()
|
||||
|
||||
// for J-type instructions, rd should be valid
|
||||
val instJ = concatBits((0, 20), (rd, 5), (Instructions.jal.litValue.toInt, 7))
|
||||
c.io.instruction.poke(instJ)
|
||||
c.io.regs_reg1_read_address.expect(0.U)
|
||||
c.io.regs_reg2_read_address.expect(0.U)
|
||||
c.io.ex_reg_write_address.expect(rd)
|
||||
c.io.ex_reg_write_enable.expect(true.B)
|
||||
c.clock.step()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ class Memory {
|
||||
uint32_t read(size_t address) {
|
||||
address = address / 4;
|
||||
if (address >= memory.size()) {
|
||||
// printf("invalid read address 0x%08x\n", address * 4);
|
||||
printf("invalid read address 0x%08x\n", address * 4);
|
||||
return 0;
|
||||
}
|
||||
return memory[address];
|
||||
@@ -29,8 +29,8 @@ class Memory {
|
||||
uint32_t readInst(size_t address) {
|
||||
address = address / 4;
|
||||
if (address >= memory.size()) {
|
||||
// printf("invalid read Inst address 0x%08x\n", address * 4);
|
||||
return 0;
|
||||
printf("invalid read Inst address 0x%08x\n", address * 4);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return memory[address];
|
||||
@@ -44,7 +44,7 @@ class Memory {
|
||||
if (write_strobe[2]) write_mask |= 0x00FF0000;
|
||||
if (write_strobe[3]) write_mask |= 0xFF000000;
|
||||
if (address >= memory.size()) {
|
||||
// printf("invalid write address 0x%08x\n", address * 4);
|
||||
printf("invalid write address 0x%08x\n", address * 4);
|
||||
return;
|
||||
}
|
||||
memory[address] = (memory[address] & ~write_mask) | (value & write_mask);
|
||||
@@ -133,12 +133,12 @@ class Simulator {
|
||||
|
||||
if (auto it = std::find(args.begin(), args.end(), "-memory");
|
||||
it != args.end()) {
|
||||
memory_words = std::stoul(*(it + 1));
|
||||
memory_words = std::stoull(*(it + 1));
|
||||
}
|
||||
|
||||
if (auto it = std::find(args.begin(), args.end(), "-time");
|
||||
it != args.end()) {
|
||||
max_sim_time = std::stoul(*(it + 1));
|
||||
max_sim_time = std::stoull(*(it + 1));
|
||||
}
|
||||
|
||||
if (auto it = std::find(args.begin(), args.end(), "-vcd");
|
||||
@@ -179,9 +179,10 @@ class Simulator {
|
||||
uint32_t data_memory_read_word = 0;
|
||||
uint32_t inst_memory_read_word = 0;
|
||||
uint32_t timer_interrupt = 0;
|
||||
uint32_t counter = 0;
|
||||
uint32_t clocktime = 1;
|
||||
uint32_t counter = 0;
|
||||
uint32_t clocktime = 1;
|
||||
bool memory_write_strobe[4] = {false};
|
||||
int uart_write_time_counter = 0, uart_write_time_limit = 4; // every limit, an UART write completes; this is tricky part
|
||||
while (main_time < max_sim_time && !Verilated::gotFinish()) {
|
||||
++main_time;
|
||||
++counter;
|
||||
@@ -197,17 +198,23 @@ class Simulator {
|
||||
if (main_time > 2) {
|
||||
top->reset = 0;
|
||||
}
|
||||
// top->io_mem_slave_read_data = memory_read_word;
|
||||
// top->io_mem_slave_read_data = memory_read_word;
|
||||
top->io_memory_bundle_read_data = data_memory_read_word;
|
||||
top->io_instruction = inst_memory_read_word;
|
||||
top->clock = !top->clock;
|
||||
top->eval();
|
||||
top->io_interrupt_flag = 0;
|
||||
|
||||
data_memory_read_word = memory->read(top->io_memory_bundle_address);
|
||||
if (top->io_deviceSelect == 2 && top->io_memory_bundle_write_enable) {
|
||||
if (uart_write_time_counter == 0) std::cout << (char)top->io_memory_bundle_write_data << std::flush; // Output to UART
|
||||
uart_write_time_counter = (uart_write_time_counter + 1) % uart_write_time_limit;
|
||||
}
|
||||
else {
|
||||
uart_write_time_counter = 0;
|
||||
}
|
||||
|
||||
|
||||
inst_memory_read_word = memory->readInst(top->io_instruction_address);
|
||||
data_memory_read_word = memory->read(top->io_memory_bundle_address);
|
||||
inst_memory_read_word = memory->readInst(top->io_instruction_address);
|
||||
|
||||
if (top->io_memory_bundle_write_enable) {
|
||||
memory_write_strobe[0] = top->io_memory_bundle_write_strobe_0;
|
||||
@@ -223,6 +230,11 @@ class Simulator {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// print simulation progress in percentage every 1%
|
||||
if (main_time % (max_sim_time / 100) == 0) {
|
||||
std::cout << "Simulation progress: " << (main_time * 100 / max_sim_time) << "%" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (dump_signature) {
|
||||
|
||||
Reference in New Issue
Block a user