Full RV32IMC Processor with ML Inference Engine & Hardware Security
This project implements a complete RISC-V based System-on-Chip featuring a 5-stage pipelined RV32IMC core, custom vector accelerator for machine learning workloads, AXI4 interconnect fabric, DDR4 memory controller, and hardware security module. The design is synthesized on Xilinx Zynq UltraScale+ achieving 200MHz operation with integrated ML inference capabilities for edge AI applications.
5-stage pipeline with branch prediction, forwarding unit, and compressed instruction support
Custom SIMD unit for ML workloads with 8x8 systolic array
High-performance crossbar with QoS support and burst transactions
Hardware crypto engine with secure key storage and attestation
// RISC-V RV32IMC Core Top Module
module riscv_core #(
parameter XLEN = 32,
parameter RESET_ADDR = 32'h0000_0000
)(
input wire clk,
input wire rst_n,
// Instruction Memory Interface
output wire [XLEN-1:0] imem_addr,
output wire imem_req,
input wire [XLEN-1:0] imem_rdata,
input wire imem_ready,
// Data Memory Interface
output wire [XLEN-1:0] dmem_addr,
output wire [XLEN-1:0] dmem_wdata,
output wire [3:0] dmem_we,
output wire dmem_req,
input wire [XLEN-1:0] dmem_rdata,
input wire dmem_ready,
// Interrupt Interface
input wire ext_irq,
input wire timer_irq
);
// Pipeline Registers
reg [XLEN-1:0] if_id_pc;
reg [31:0] if_id_instr;
reg if_id_valid;
reg [XLEN-1:0] id_ex_pc;
reg [4:0] id_ex_rs1;
reg [4:0] id_ex_rs2;
reg [4:0] id_ex_rd;
reg [XLEN-1:0] id_ex_imm;
reg [6:0] id_ex_alu_op;
reg id_ex_valid;
// Fetch Stage
always_ff @(posedge clk) begin
if (!rst_n) begin
pc <= RESET_ADDR;
end else if (branch_taken) begin
pc <= branch_target;
end else if (!stall) begin
pc <= pc_next;
end
end
// Decode Stage with Compressed Instruction Support
always_comb begin
if (if_id_instr[1:0] != 2'b11) begin
// Compressed instruction - expand to 32-bit
case (if_id_instr[1:0])
2'b00: decoded_instr = expand_c0(if_id_instr[15:0]);
2'b01: decoded_instr = expand_c1(if_id_instr[15:0]);
2'b10: decoded_instr = expand_c2(if_id_instr[15:0]);
default: decoded_instr = 32'h0000_0013; // NOP
endcase
end else begin
decoded_instr = if_id_instr;
end
end
// Hazard Detection Unit
hazard_detection_unit hdu (
.id_ex_rd(id_ex_rd),
.id_ex_mem_read(id_ex_mem_read),
.if_id_rs1(if_id_rs1),
.if_id_rs2(if_id_rs2),
.stall(pipeline_stall)
);
// Forwarding Unit
forwarding_unit fwd (
.ex_mem_rd(ex_mem_rd),
.mem_wb_rd(mem_wb_rd),
.id_ex_rs1(id_ex_rs1),
.id_ex_rs2(id_ex_rs2),
.forward_a(forward_a),
.forward_b(forward_b)
);
endmodule
// 8x8 Systolic Array for Matrix Multiplication
module systolic_array #(
parameter DATA_WIDTH = 8,
parameter ARRAY_SIZE = 8
)(
input wire clk,
input wire rst_n,
input wire start,
// Input matrices
input wire [DATA_WIDTH-1:0] a_in [0:ARRAY_SIZE-1],
input wire [DATA_WIDTH-1:0] b_in [0:ARRAY_SIZE-1],
input wire valid_in,
// Output matrix
output reg [2*DATA_WIDTH+7:0] c_out [0:ARRAY_SIZE-1][0:ARRAY_SIZE-1],
output reg valid_out
);
// Processing Elements
reg [DATA_WIDTH-1:0] pe_a [0:ARRAY_SIZE-1][0:ARRAY_SIZE-1];
reg [DATA_WIDTH-1:0] pe_b [0:ARRAY_SIZE-1][0:ARRAY_SIZE-1];
reg [2*DATA_WIDTH+7:0] pe_c [0:ARRAY_SIZE-1][0:ARRAY_SIZE-1];
// Systolic data flow
generate
genvar i, j;
for (i = 0; i < ARRAY_SIZE; i++) begin : row
for (j = 0; j < ARRAY_SIZE; j++) begin : col
processing_element #(
.DATA_WIDTH(DATA_WIDTH)
) pe (
.clk(clk),
.rst_n(rst_n),
.a_in((j == 0) ? a_in[i] : pe_a[i][j-1]),
.b_in((i == 0) ? b_in[j] : pe_b[i-1][j]),
.c_in(pe_c[i][j]),
.a_out(pe_a[i][j]),
.b_out(pe_b[i][j]),
.c_out(pe_c[i][j])
);
end
end
endgenerate
// Output assignment
always_ff @(posedge clk) begin
if (!rst_n) begin
valid_out <= 1'b0;
end else begin
c_out <= pe_c;
valid_out <= (cycle_count == ARRAY_SIZE * 3 - 1);
end
end
endmodule
// Individual Processing Element
module processing_element #(
parameter DATA_WIDTH = 8
)(
input wire clk,
input wire rst_n,
input wire [DATA_WIDTH-1:0] a_in,
input wire [DATA_WIDTH-1:0] b_in,
input wire [2*DATA_WIDTH+7:0] c_in,
output reg [DATA_WIDTH-1:0] a_out,
output reg [DATA_WIDTH-1:0] b_out,
output reg [2*DATA_WIDTH+7:0] c_out
);
wire [2*DATA_WIDTH-1:0] mult_result;
// Multiply-Accumulate operation
assign mult_result = a_in * b_in;
always_ff @(posedge clk) begin
if (!rst_n) begin
a_out <= 0;
b_out <= 0;
c_out <= 0;
end else begin
a_out <= a_in; // Pass through horizontally
b_out <= b_in; // Pass through vertically
c_out <= c_in + mult_result; // Accumulate
end
end
endmodule
// 2-Way Set Associative Cache
module cache_2way #(
parameter ADDR_WIDTH = 32,
parameter DATA_WIDTH = 32,
parameter CACHE_SIZE = 32768, // 32KB
parameter LINE_SIZE = 64, // 64 bytes per line
parameter ASSOCIATIVITY = 2
)(
input wire clk,
input wire rst_n,
// CPU Interface
input wire [ADDR_WIDTH-1:0] cpu_addr,
input wire [DATA_WIDTH-1:0] cpu_wdata,
input wire cpu_we,
input wire cpu_req,
output reg [DATA_WIDTH-1:0] cpu_rdata,
output reg cpu_ready,
// Memory Interface
output reg [ADDR_WIDTH-1:0] mem_addr,
output reg [LINE_SIZE*8-1:0] mem_wdata,
output reg mem_we,
output reg mem_req,
input wire [LINE_SIZE*8-1:0] mem_rdata,
input wire mem_ready
);
localparam NUM_LINES = CACHE_SIZE / LINE_SIZE;
localparam NUM_SETS = NUM_LINES / ASSOCIATIVITY;
localparam SET_BITS = $clog2(NUM_SETS);
localparam OFFSET_BITS = $clog2(LINE_SIZE);
localparam TAG_BITS = ADDR_WIDTH - SET_BITS - OFFSET_BITS;
// Cache storage
reg [LINE_SIZE*8-1:0] cache_data [0:ASSOCIATIVITY-1][0:NUM_SETS-1];
reg [TAG_BITS-1:0] cache_tags [0:ASSOCIATIVITY-1][0:NUM_SETS-1];
reg cache_valid [0:ASSOCIATIVITY-1][0:NUM_SETS-1];
reg cache_dirty [0:ASSOCIATIVITY-1][0:NUM_SETS-1];
reg cache_lru [0:NUM_SETS-1]; // LRU bit per set
// Address decomposition
wire [TAG_BITS-1:0] tag = cpu_addr[ADDR_WIDTH-1:ADDR_WIDTH-TAG_BITS];
wire [SET_BITS-1:0] index = cpu_addr[OFFSET_BITS+SET_BITS-1:OFFSET_BITS];
wire [OFFSET_BITS-1:0] offset = cpu_addr[OFFSET_BITS-1:0];
// Hit detection
wire hit_way0 = cache_valid[0][index] && (cache_tags[0][index] == tag);
wire hit_way1 = cache_valid[1][index] && (cache_tags[1][index] == tag);
wire cache_hit = hit_way0 || hit_way1;
wire hit_way = hit_way1;
// LRU replacement policy
always_ff @(posedge clk) begin
if (!rst_n) begin
cache_lru <= '0;
end else if (cache_hit && cpu_req) begin
cache_lru[index] <= !hit_way;
end
end
endmodule
// Hardware AES-256 Engine
module aes256_engine (
input wire clk,
input wire rst_n,
input wire [255:0] key,
input wire [127:0] plaintext,
input wire start,
output reg [127:0] ciphertext,
output reg done
);
// AES round function components
reg [127:0] state;
reg [127:0] round_key [0:14];
reg [3:0] round_counter;
// S-Box lookup table
wire [7:0] sbox [0:255];
// Key expansion
always_ff @(posedge clk) begin
if (start) begin
round_key[0] <= key[255:128];
round_key[1] <= key[127:0];
// Expand remaining round keys
for (int i = 2; i <= 14; i++) begin
round_key[i] <= key_expansion(round_key[i-1], i);
end
end
end
// AES rounds
always_ff @(posedge clk) begin
if (!rst_n) begin
state <= 0;
round_counter <= 0;
done <= 0;
end else if (start) begin
state <= plaintext ^ round_key[0];
round_counter <= 1;
done <= 0;
end else if (round_counter > 0 && round_counter < 14) begin
state <= mix_columns(shift_rows(sub_bytes(state))) ^ round_key[round_counter];
round_counter <= round_counter + 1;
end else if (round_counter == 14) begin
ciphertext <= shift_rows(sub_bytes(state)) ^ round_key[14];
done <= 1;
round_counter <= 0;
end
end
endmodule
// UVM Test Environment
class riscv_test_env extends uvm_env;
`uvm_component_utils(riscv_test_env)
// Environment components
riscv_agent cpu_agent;
memory_agent mem_agent;
axi4_agent axi_agent;
scoreboard sb;
coverage_collector cov;
function new(string name = "riscv_test_env", uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
cpu_agent = riscv_agent::type_id::create("cpu_agent", this);
mem_agent = memory_agent::type_id::create("mem_agent", this);
axi_agent = axi4_agent::type_id::create("axi_agent", this);
sb = scoreboard::type_id::create("sb", this);
cov = coverage_collector::type_id::create("cov", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// Connect monitors to scoreboard
cpu_agent.monitor.ap.connect(sb.cpu_export);
mem_agent.monitor.ap.connect(sb.mem_export);
// Connect to coverage collector
cpu_agent.monitor.ap.connect(cov.analysis_export);
endfunction
endclass
// Constrained Random Test
class riscv_random_test extends uvm_test;
`uvm_component_utils(riscv_random_test)
riscv_test_env env;
task run_phase(uvm_phase phase);
riscv_sequence seq;
phase.raise_objection(this);
// Run random instruction sequences
repeat(10000) begin
seq = riscv_sequence::type_id::create("seq");
seq.randomize() with {
instr_type dist {
ALU_OP := 40,
LOAD_OP := 20,
STORE_OP := 15,
BRANCH := 20,
SYSTEM := 5
};
};
seq.start(env.cpu_agent.sequencer);
end
phase.drop_objection(this);
endtask
endclass
| Resource | Used | Available | Utilization % |
|---|---|---|---|
| LUTs | 45,230 | 274,080 | 16.5% |
| Flip-Flops | 38,450 | 548,160 | 7.0% |
| Block RAM | 128 | 912 | 14.0% |
| DSP Slices | 64 | 2,520 | 2.5% |
| MMCM/PLL | 2 | 8 | 25.0% |
// ML Inference on Custom Accelerator
#include "riscv_soc.h"
#include "ml_accelerator.h"
// Custom CSR for accelerator control
#define CSR_ML_CTRL 0x7C0
#define CSR_ML_STATUS 0x7C1
#define CSR_ML_ADDR_A 0x7C2
#define CSR_ML_ADDR_B 0x7C3
#define CSR_ML_ADDR_C 0x7C4
// Matrix multiplication using systolic array
void matmul_accelerated(int8_t *A, int8_t *B, int32_t *C, int size) {
// Configure DMA for matrix transfer
dma_config_t config = {
.src_addr = (uint32_t)A,
.dst_addr = ML_ACCEL_BASE + ML_MATRIX_A_OFFSET,
.size = size * size,
.burst_len = 16
};
// Transfer matrix A to accelerator
dma_transfer(&config);
// Transfer matrix B
config.src_addr = (uint32_t)B;
config.dst_addr = ML_ACCEL_BASE + ML_MATRIX_B_OFFSET;
dma_transfer(&config);
// Start systolic array computation
write_csr(CSR_ML_CTRL, ML_START | ML_MODE_MATMUL);
// Wait for completion
while (!(read_csr(CSR_ML_STATUS) & ML_DONE));
// Read results back
config.src_addr = ML_ACCEL_BASE + ML_MATRIX_C_OFFSET;
config.dst_addr = (uint32_t)C;
config.size = size * size * sizeof(int32_t);
dma_transfer(&config);
}
// Benchmark function
void benchmark_ml_inference(void) {
uint64_t start, end;
int8_t input[8][8], weights[8][8];
int32_t output[8][8];
// Initialize test data
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
input[i][j] = (i * 8 + j) % 128;
weights[i][j] = (j * 8 + i) % 128;
}
}
// Measure performance
start = read_csr(mcycle);
for (int iter = 0; iter < 1000; iter++) {
matmul_accelerated(&input[0][0], &weights[0][0], &output[0][0], 8);
}
end = read_csr(mcycle);
printf("ML Inference Performance:\n");
printf(" Cycles per inference: %llu\n", (end - start) / 1000);
printf(" Throughput: %.2f GOPS\n",
(8.0 * 8.0 * 8.0 * 2.0 * 1000.0) / (end - start) * 200.0);
}
int main(void) {
// Initialize system
system_init();
// Configure ML accelerator
ml_accel_init();
// Run benchmark
benchmark_ml_inference();
// Run neural network inference
run_nn_inference();
return 0;
}
// Two-bit saturating counter branch predictor
module branch_predictor #(
parameter BTB_SIZE = 256,
parameter ADDR_WIDTH = 32
)(
input wire clk,
input wire rst_n,
// Prediction interface
input wire [ADDR_WIDTH-1:0] pc_fetch,
output wire predict_taken,
output wire [ADDR_WIDTH-1:0] predict_target,
// Update interface
input wire update_en,
input wire [ADDR_WIDTH-1:0] update_pc,
input wire actual_taken,
input wire [ADDR_WIDTH-1:0] actual_target
);
// Branch Target Buffer
reg [ADDR_WIDTH-1:0] btb_target [0:BTB_SIZE-1];
reg [1:0] btb_state [0:BTB_SIZE-1]; // 2-bit counter
reg btb_valid [0:BTB_SIZE-1];
// Hash function for BTB indexing
wire [7:0] fetch_idx = pc_fetch[9:2] ^ pc_fetch[17:10];
wire [7:0] update_idx = update_pc[9:2] ^ update_pc[17:10];
// Prediction logic
assign predict_taken = btb_valid[fetch_idx] && (btb_state[fetch_idx] >= 2'b10);
assign predict_target = btb_target[fetch_idx];
// State machine for 2-bit counter
always @(posedge clk) begin
if (!rst_n) begin
for (int i = 0; i < BTB_SIZE; i++) begin
btb_valid[i] <= 1'b0;
btb_state[i] <= 2'b01; // Weakly not taken
end
end else if (update_en) begin
btb_valid[update_idx] <= 1'b1;
btb_target[update_idx] <= actual_target;
// Update 2-bit counter
case (btb_state[update_idx])
2'b00: btb_state[update_idx] <= actual_taken ? 2'b01 : 2'b00; // Strongly not taken
2'b01: btb_state[update_idx] <= actual_taken ? 2'b10 : 2'b00; // Weakly not taken
2'b10: btb_state[update_idx] <= actual_taken ? 2'b11 : 2'b01; // Weakly taken
2'b11: btb_state[update_idx] <= actual_taken ? 2'b11 : 2'b10; // Strongly taken
endcase
end
end
endmodule
Tomasulo's algorithm implementation with reservation stations:
2D mesh topology for multi-core scalability:
// NoC Router Module
module noc_router #(
parameter DATA_WIDTH = 64,
parameter ADDR_WIDTH = 32,
parameter X_COORD = 0,
parameter Y_COORD = 0
)(
input wire clk,
input wire rst_n,
// North, South, East, West, Local ports
input wire [DATA_WIDTH-1:0] data_in [0:4],
input wire valid_in [0:4],
output wire ready_out[0:4],
output wire [DATA_WIDTH-1:0] data_out [0:4],
output wire valid_out[0:4],
input wire ready_in [0:4]
);
// Routing logic
function [2:0] route_xy;
input [7:0] dest_x, dest_y;
begin
if (dest_x > X_COORD)
route_xy = 3'b010; // East
else if (dest_x < X_COORD)
route_xy = 3'b011; // West
else if (dest_y > Y_COORD)
route_xy = 3'b000; // North
else if (dest_y < Y_COORD)
route_xy = 3'b001; // South
else
route_xy = 3'b100; // Local
end
endfunction
endmodule
MESI cache coherence protocol implementation:
// MESI Cache Controller
typedef enum logic [1:0] {
INVALID = 2'b00,
SHARED = 2'b01,
EXCLUSIVE = 2'b10,
MODIFIED = 2'b11
} mesi_state_t;
module cache_controller_mesi (
input wire clk,
input wire rst_n,
// CPU interface
input wire cpu_read,
input wire cpu_write,
input wire [31:0] cpu_addr,
// Snoop interface
input wire snoop_read,
input wire snoop_write,
input wire [31:0] snoop_addr,
// State output
output mesi_state_t cache_state
);
mesi_state_t current_state, next_state;
always_ff @(posedge clk) begin
if (!rst_n)
current_state <= INVALID;
else
current_state <= next_state;
end
always_comb begin
next_state = current_state;
case (current_state)
INVALID: begin
if (cpu_read && !snoop_write)
next_state = SHARED;
else if (cpu_write)
next_state = MODIFIED;
end
SHARED: begin
if (cpu_write)
next_state = MODIFIED;
else if (snoop_write)
next_state = INVALID;
end
EXCLUSIVE: begin
if (cpu_write)
next_state = MODIFIED;
else if (snoop_read)
next_state = SHARED;
else if (snoop_write)
next_state = INVALID;
end
MODIFIED: begin
if (snoop_read) begin
// Write back and transition to SHARED
next_state = SHARED;
end else if (snoop_write) begin
// Write back and invalidate
next_state = INVALID;
end
end
endcase
end
endmodule
Compliant with RISC-V External Debug Support Version 0.13.2:
// Performance counter access
uint64_t read_cycle_counter() {
uint32_t lo, hi;
asm volatile("rdcycle %0" : "=r"(lo));
asm volatile("rdcycleh %0" : "=r"(hi));
return ((uint64_t)hi << 32) | lo;
}
uint64_t read_instruction_counter() {
uint32_t lo, hi;
asm volatile("rdinstret %0" : "=r"(lo));
asm volatile("rdinstreth %0" : "=r"(hi));
return ((uint64_t)hi << 32) | lo;
}
// Custom performance counter for cache misses
uint32_t read_cache_misses() {
uint32_t misses;
asm volatile("csrr %0, 0x7C5" : "=r"(misses)); // Custom CSR
return misses;
}