Bao’s Design Notebook
Week of September 14th
Team Meeting
- September 15th: I attended the first team meeting of the semester on Zoom, which went through semester goals and possible group assignments.
Group B
- I fully examined the RV32I documentation.
Leads Support
- After having my request to assist with the leads accepted by Hans, I assisted Hans and Paul with assessing the first onboarding lab for new members.
Week of September 21st
Team Meeting
- September 22nd: I attended the first team meeting of the semester on Zoom, which went through RV32I.
Leads Support
- I advised the leads to add numeric first arguments to
$fatalcalls and clarifyalways_comb’s execution at simulation start, combinational intent, and single-writer restrictions for Group A’s Onboarding Lab 2.
Week of September 28th
Team Meeting
- September 29th: I attended the second team meeting of the semester on Zoom, which went through specifications of the modules briefly for purposes of aiding in the creation of the microarchitecture schema for Group B’s Week 3 tasks
Group B
- I designed the JAL operand and control-path diagram, including J-type immediate generation and 32-bit sign extension.
- I mapped connections to shared adders and multiplexers to calculate
PC + immJand writePC + 4to rd. - I labeled bus widths and control signals, leaving final mux encodings and exception handling for shared integration
Leads Support
- I advised the leads to print success only after all checks pass and then call
$finish, add separate watchdog timeouts adjustable for longer tests, explicitly cast randomized data to 8 bits and control signals to 1 bit, and require positive integerWIDTHand power-of-two integerDEPTH >= 2for Group A’s Onboarding Lab 3.
Week of October 5th
Team Meeting
- October 6th: I attended the third team meeting of the semester on Zoom.
Week of October 12th
Team Meeting
- October 13th: I attended the forth team meeting of the semester on Zoom
Core Modules
- I registered my interest to work on top.sv (primary interest) and control_unit.sv (secondary interest) on the project repository.
Week of October 19th
Team Meeting
- October 13th: I attended the fifth team meeting of the semester in person
Core Modules
- I created a when2meet to find suitable time to work with Ray and Yuhan.
- I started working on the top-level module’s testbench.
Week of October 26th
Core Modules
- I implemented
top.svto connect all six core modules through the 32-bit PC, operand/writeback muxes, and branch/jump paths, with named memory and mux constants. - I added 16,384-halfword, little-endian instruction memory with configurable image loading, missing-file diagnostics, and alignment/range guards; invalid fetches return zero without trap handling.
- I built
top_tb.svwith four 10 ns clock cycles, VCD output, andSMOKE_CYCLES + 1preloaded NOPs. It confirms simulation completion only, without assertions or randomization; four dependencies remain unfinished. - I enabled strict top lint and updated the Level 1 runner to reject failed commands, timeouts, and memory-image diagnostics. CI passed with Icarus 12 and Verilator 5.020: 3 passed, 0 failed, and 4 skipped.
src/top.sv
// single-cycle integration with a byte-addressed PC and halfword instruction memory
module top #(
parameter INSTR_MEM_FILE = "assembler/out/instr.mem"
) (
input logic clk
);
// fixed power-of-two halfword capacity determines the address slices
localparam int INSTR_MEM_HALFWORDS = 16384;
localparam int INSTR_INDEX_BITS = $clog2(INSTR_MEM_HALFWORDS);
// mux encodings match docs/control_table.csv
localparam logic [1:0] ALUA_RS1 = 2'd0;
localparam logic [1:0] ALUA_ZERO = 2'd1;
localparam logic [1:0] ALUA_PC = 2'd2;
localparam logic [1:0] WB_ALU = 2'd0;
localparam logic [1:0] WB_LINK = 2'd1;
localparam logic [1:0] WB_MEM = 2'd2;
// instruction memory and address paths
logic [15:0] instr_mem [0:INSTR_MEM_HALFWORDS-1];
logic [INSTR_INDEX_BITS-1:0] instr_index;
logic [31:0] instr;
logic [31:0] pc;
logic [31:0] pc_next;
logic [31:0] pc_plus4;
logic [31:0] pc_branch;
// controls use the encodings in docs/control_table.csv
logic branch_decoder_on;
logic [2:0] funct3;
logic mux_PC;
logic [1:0] mux_reg;
logic RegWrite;
logic MemWrite;
logic [1:0] mux_ALUA;
logic mux_ALUB;
logic [3:0] ALU_funct;
logic sign_val;
logic [1:0] read_size;
// register, immediate, execution, and writeback paths
logic [31:0] rs1_data;
logic [31:0] rs2_data;
logic [31:0] imm;
logic [31:0] alu_a;
logic [31:0] alu_b;
logic [31:0] alu_out;
logic zflag;
logic branch_taken;
logic [31:0] mem_data;
logic [31:0] writeback_data;
`ifndef SYNTHESIS
// simulation-only image handle
integer image_file;
`endif
// initialize PC before the first clock edge and load the image from the working directory
// assembler images are padded; sparse fixtures must provide every fetched halfword
initial begin
pc = 32'b0;
// an empty filename skips loading without clearing memory
// external simulation initialization must finish before the first clock edge
if (INSTR_MEM_FILE != "") begin
`ifndef SYNTHESIS
image_file = $fopen(INSTR_MEM_FILE, "r");
// stop simulation when the requested image cannot be opened
if (image_file == 0) begin
$fatal(1, "top: cannot open instruction image '%s'", INSTR_MEM_FILE);
end
$fclose(image_file);
`endif
$readmemh(INSTR_MEM_FILE, instr_mem, 0, INSTR_MEM_HALFWORDS-1);
end
end
// reject misaligned or out-of-range fetches before truncating the address
// zero is an invalid-fetch marker, not a NOP or a reported architectural trap
assign instr_index = pc[INSTR_INDEX_BITS:1]; // convert byte addresses to halfword indices
// aligned instructions pair an even low halfword with the next odd halfword
assign instr = (pc[31:INSTR_INDEX_BITS+1] == '0 && pc[1:0] == 2'b0)
? {instr_mem[{instr_index[INSTR_INDEX_BITS-1:1], 1'b1}], instr_mem[instr_index]}
: 32'b0;
// reuse PC plus four for sequential execution and link writeback
assign pc_plus4 = pc + 32'd4;
assign pc_branch = pc + imm;
assign pc_next = mux_PC ? (branch_taken ? pc_branch : pc_plus4) : alu_out;
// advance PC on the same rising edge as register and memory writes
always @(posedge clk) begin
pc <= pc_next;
end
// decode execution controls from the fetched instruction
control_unit u_control_unit (
.instr(instr),
.branch_decoder_on(branch_decoder_on),
.funct3(funct3),
.mux_PC(mux_PC),
.mux_reg(mux_reg),
.RegWrite(RegWrite),
.MemWrite(MemWrite),
.mux_ALUA(mux_ALUA),
.mux_ALUB(mux_ALUB),
.ALU_funct(ALU_funct),
.sign_val(sign_val),
.read_size(read_size)
);
// read both source registers and write the selected result
reg_file u_reg_file (
.rs1(instr[19:15]),
.rs2(instr[24:20]),
.rd(instr[11:7]),
.data_in(writeback_data),
.RegWrite(RegWrite),
.clk(clk),
.rs1_out(rs1_data),
.rs2_out(rs2_data)
);
// reconstruct the immediate without rescaling branch or jump offsets
instr_imm u_instr_imm (
.instr(instr),
.imm(imm)
);
// select rs1, zero for LUI, or PC for AUIPC and JAL
always_comb begin
case (mux_ALUA)
ALUA_RS1: alu_a = rs1_data;
ALUA_ZERO: alu_a = 32'b0;
ALUA_PC: alu_a = pc;
default: alu_a = 32'b0;
endcase
end
// select the second register or decoded immediate
assign alu_b = mux_ALUB ? imm : rs2_data;
// execute arithmetic and comparisons, including JALR low-bit clearing
alu u_alu (
.inA(alu_a),
.inB(alu_b),
.ALU_funct(ALU_funct),
.out(alu_out),
.zflag(zflag)
);
// combine the branch condition with ALU comparison results
branch_decoder u_branch_decoder (
.branch_decoder_on(branch_decoder_on),
.funct3(funct3),
.zflag(zflag),
.ALU_out_lsb(alu_out[0]),
.branch_decision(branch_taken)
);
// stores always use rs2, independently of the ALU operand muxes
data_mem u_data_mem (
.addr(alu_out),
.data_in(rs2_data),
.sign_val(sign_val),
.read_size(read_size),
.clk(clk),
.MemWrite(MemWrite),
.data_out(mem_data)
);
// select the ALU result, link address, or memory data
always_comb begin
case (mux_reg)
WB_ALU: writeback_data = alu_out;
WB_LINK: writeback_data = pc_plus4;
WB_MEM: writeback_data = mem_data;
default: writeback_data = 32'b0;
endcase
end
endmodule
testbench/level1_tb/top_tb.sv
// level 1 compilation and clocked simulation without functional output checks
module top_tb;
// local timing keeps the clock independent of other modules
timeunit 1ns;
timeprecision 1ps;
// one extra instruction covers the fetch after the final PC update
localparam int SMOKE_CYCLES = 4;
localparam int FIXTURE_INSTRUCTIONS = SMOKE_CYCLES + 1;
localparam logic [31:0] NOP_INSTRUCTION = 32'h00000013;
// clock stimulus and optional waveform path
logic clk;
string wave_file;
// initialize the fixture below instead of loading an external image
top #(
.INSTR_MEM_FILE("")
) dut (
.clk(clk)
);
// generate a waveform without replacing unfinished production modules
initial begin
clk = 1'b0;
// preload little-endian NOPs before the first rising edge
for (int index = 0; index < FIXTURE_INSTRUCTIONS; index++) begin
dut.instr_mem[2 * index] = NOP_INSTRUCTION[15:0]; // low halfword first
dut.instr_mem[2 * index + 1] = NOP_INSTRUCTION[31:16];
end
// accept the existing level 1 runner's waveform destination
if (!$value$plusargs("wave=%s", wave_file)) begin
wave_file = "top_tb.vcd";
end
$dumpfile(wave_file);
$dumpvars(0, top_tb);
$display("top: smoke simulation only; no functional output checks");
$display("top: unfinished dependencies may propagate unknown values");
// use the same cycle count for fixture sizing and clock stimulus
repeat (SMOKE_CYCLES) begin
#5 clk = 1'b1; // 10 ns clock period
#5 clk = 1'b0;
end
// the runner marker reports simulation completion only
$display("TEST PASSED: top compiled and completed %0d clock cycles", SMOKE_CYCLES);
$finish;
end
endmodule
Week of November 2nd
Microcontroller
- I completed a custom RISC-V microcontroller for another project.