---
# System prepended metadata

title: SoC Lab4-2
tags: [SoC Laboratory, Lab]

---

# SoC Lab4-2 Cavarel FIR

[Github Link](https://github.com/dqrengg/SoC_Laboratory/tree/main/Lab4/Lab4-2)

## 1. Block Diagram
![caravel_fir_block_diagram](https://hackmd.io/_uploads/H1eFHGSeeg.png)

## 2. User Project
### 2.1 WB Decode
* `user_project_wrapper` serves as datapath.
* Selection singals. `0x3000_0000` mappes to `mprj`, `0x3800_0000` mappes to `mprjram`.
    ```
    assign mem_sel = (wbs_adr_i[31:22] == 10'b0011_1000_00);
    assign axi_sel = (wbs_adr_i[31:20] == 12'b0011_0000_0000);
    ```
* Cycle selection
    ```
    assign mem_cyc = wbs_cyc_i & mem_sel;
    assign axi_cyc = wbs_cyc_i & axi_sel;
    ```
* Shared signals (`ack`, `dat`)
    ```
    assign wbs_ack_o = mem_ack | axi_ack;
    assign wbs_dat_o = ({ 32{mem_sel} } & mem_dat) | ({ 32{axi_sel} } & axi_dat);
    ```

### 2.2 External Memory
* Read with 10-cycle latency
    ```
    always @(posedge wb_clk_i or posedge wb_rst_i) begin
        if (wb_rst_i) begin
            count <= 0;
        end else begin
            if (count == 4'd10) count <= 0;
            else if (wbs_cyc_i & !wbs_we_i) count <= count + 1;
        end
    end
    ```
* WB-BRAM conversion
    ```
    assign valid = wbs_cyc_i & wbs_stb_i;
    assign we = valid & wbs_we_i;
    assign en = 1'b1;
    ```
* Shared `ack`
    ```
    assign wbs_ack_o = ((valid & wbs_we_i) | count == 4'd10);
    ```
* BRAM `Do` connects to `wb_dat_o`

### 2.3 WB-AXI conversion
#### Address Decode
* `!we` for read, `we` for write
```
assign axilite_sel = (wbs_adr_i[31:8] == 24'h3000_00)
                   & (wbs_adr_i != 32'h3000_0040) & (wbs_adr_i != 32'h3000_0044);
assign axis_s_sel = (wbs_adr_i == 32'h3000_0040);
assign axis_m_sel = (wbs_adr_i == 32'h3000_0044);
```

#### Shared `ack`
* AXI-Lite read / write response singals
* AXIS waits for ready or valid
```
assign axilite_ack = (awready & wready) | rvalid;
assign axis_s_ack = axis_s_sel & ss_tready;
assign axis_m_ack = axis_m_sel & sm_tvalid;
assign axis_ack = axis_m_ack | axis_s_ack;
assign wbs_ack_o = wbs_cyc_i & (axilite_ack | axis_ack);
```

#### Shared `dat`
* Only read operations return data
```
assign wbs_dat_o = { 32{axis_m_sel} } & sm_tdata | { 32{rvalid} } & rdata;
```

#### AXI-Lite Write
```
assign awvalid = valid & wbs_we_i & axilite_sel;
assign awaddr = wbs_adr_i[11:0];
assign wvalid = valid & wbs_we_i & axilite_sel;
assign wdata = wbs_dat_i;
```

#### AXI-Lite Read
* de-assert `arvalid` after handshaking
```
always @(posedge wb_clk_i or posedge wb_rst_i) begin
    if (wb_rst_i) arvalid_en <= 1;
    else begin
        if (arvalid && arready) arvalid_en <= 0; 
        else if (rvalid && rready) arvalid_en <= 1;
    end
end
assign rready = wbs_cyc_i & !wbs_we_i & axilite_sel;
assign arvalid = valid & !wbs_we_i & axilite_sel & arvalid_en;
assign araddr = wbs_adr_i[11:0];
```

#### AXIS Master
* CPU reads from `0x3000_0044`
```
assign sm_tready = wbs_cyc_i & !wbs_we_i & axis_m_sel
```

#### AXIS Slave
* CPU writes to `0x3000_0040`
* Use counter to generated `ss_tlast`. Reset counter when writing to `0x3000_0010`
    ```
    assign ss_tvalid = valid & wbs_we_i & axis_s_sel;
    assign ss_tdata = wbs_dat_i;
    assign ss_tlast = ss_tvalid & (ss_last_count == 1);

    always @(posedge wb_clk_i) begin
        if (valid & wbs_we_i & (wbs_adr_i == 32'h3000_0010)) begin
            ss_last_count <= wbs_dat_i;
        end else if (ss_tvalid & ss_tready) begin
            ss_last_count <= ss_last_count - 1;
        end
    end
    ```

### 2.4 FIR
* FIR in Lab3.

## 3. Performance Analysis
### 3.1 Initial Version - Normal While Loop
* After CPU fetches code from user memory, the instructions of the FIR calculation loop will be inside the cache.
* firmware code
    ```
    uint32_t i = 0;
    while (i < N) {
        reg_fir_x = i;
        output_signals[i] = reg_fir_y;
        i++;
    }
    ```
* Performance issues
    * **Hardware latency**
    The firmware reads `Y` immediately after writing `X`. However, in FIR engine, it takes 3 cycles to compute `Y` once receiving corresponding `X`. Therefore, CPU needs to wait for hardware.
    * Pipeline hazards
* Waveform - 16 cycles per Y
![waveform1](https://hackmd.io/_uploads/r1z7YmSxgl.png)


### 3.2 Optimization 1 - Reorder Read/Write in While Loop
* Modify the firmware code, putting `bne` between 'write `X`' and 'read `Y`'. 
    ```
    uint32_t i = 0;
    reg_fir_x = i;
    while (i < N-1) {
        output_signals[i] = reg_fir_y;
        i++;
        reg_fir_x = i;
    }
    output_signals[i] = reg_fir_y;
    ```
* Performance issues: **Pipeline hazards**
  * **RAW**: `i` increment and 'read `X`'
* Waveform - 15 cycles per Y
![waveform2](https://hackmd.io/_uploads/B1A7Ymrglg.png)


### 3.3 Optimization 2 - Counter Increment after Write X
* firmware code
    ```
    uint32_t i = 1;
    reg_fir_x = 0;
    while (i < N) {
        output_signals[i-1] = reg_fir_y;
        reg_fir_x = i;
        i++;
    }
    output_signals[N] = reg_fir_y;
    ```
* Performance issues: **Pipeline hazards**
  * **RAW**: `addi` and `sw`, `addi` and `bne`
* Waveform - 19 cycles per Y
![waveform3](https://hackmd.io/_uploads/r154t7Helg.png)

### 3.4 Optimization 3 - Reorder Instructions to Solve Pipeline Hazards
* Original instruction order, RAW hazard between `addi` and first `sw`, as well as `addi` and `bne`.
    ```
    380000a0:	0446a603          	lw	a2,68(a3) # 30000044
    380000a4:	00470713          	addi	a4,a4,4 # a50004
    380000a8:	fec72e23          	sw	a2,-4(a4)
    380000ac:	04f6a023          	sw	a5,64(a3)
    380000b0:	00178793          	addi	a5,a5,1
    380000b4:	feb796e3          	bne	a5,a1,380000a0
    ```
* Optimized instruction order, eliminating RAW hazards
    ```
    380000a0:	0446a603          	lw	a2,68(a3) # 30000044
    380000a4:	00470713          	addi	a4,a4,4 # a50004
    380000a8:	04f6a023          	sw	a5,64(a3)
    380000ac:	00178793          	addi	a5,a5,1
    380000b0:	fec72e23          	sw	a2,-4(a4)
    380000b4:	feb796e3          	bne	a5,a1,380000a0
    ```
* Waveform - 12 cycles per Y
![waveform4](https://hackmd.io/_uploads/SJDHF7Sxeg.png)
* It seems that this CPU cannot support data forwarding in some cases, even the assembly code is reordered.

## 4. Observation
### Potential Bug
* In my implementation of WB-AXIS master, it returns `ack` in one cycle.
* In management core, WB data selection `slave_sel_r` has one cycle latency.
    ```
    slave_sel_r <= slave_sel;
    ```
    ```
    shared_dat_r = ((((((({32{slave_sel_r[0]}} & mgmtsoc_vexriscv_debug_bus_dat_r) 
                 | ({32{slave_sel_r[1]}} & dff_bus_dat_r)) 
                 | ({32{slave_sel_r[2]}} & dff2_bus_dat_r)) 
                 | ({32{slave_sel_r[3]}} & mgmtsoc_litespimmap_bus_dat_r)) 
                 | ({32{slave_sel_r[4]}} & mprj_dat_r)) 
                 | ({32{slave_sel_r[5]}} & hk_dat_r)) 
                 | ({32{slave_sel_r[6]}} & mgmtsoc_wishbone_dat_r));
    ```
* Therefore, WB read operation should have at least one cycle latency.
* In fact, running firmware code in 3.3 produces bug that WB can't get AXIS data and MPRJ_IO outputs incorrect final `Y` value.
* If changing firmware code below, the bug can be temporarily resolved, becasue the final `Y` is still computing and WB can't read the data in one cycle.
    ```
    uint32_t i = 2;
    reg_fir_x = 0;
    reg_fir_x = 1;
    while (i < N) {
        outputsignal[i-2] = reg_fir_y;
        reg_fir_x = i;
        i++;
    }
    outputsignal[N-2] = reg_fir_y;
    outputsignal[N-1] = reg_fir_y;
    ```
* Or WB-AXIS read operation can be designed completing in 2 cycle, but the FIR throughput will increase to 13 cycles/Y.
* Although the design running firmware code above passes the verification in this case, it may raise potential bugs in future design.
