quan m. nguyen

Research

Glorious One-Liners, Explained

Introduction

In my time perusing the Hwacha source code, I've seen a number of beautiful one-liners that don't have a single comment explaining what they do. Here's my chance to change that.

Read Operand Lines in lane.scala

This section will cover this tidbit of Scala, found in lane.scala, introduced with commit 22ed3b15 on lines 74-75.

val rbl = List(ropl0, rdata, ropl1, ropl0, rdata, rdata, rdata, rdata).zipWithIndex.map(
  rblgroup => rblen.zip(rblgroup._1).map(b => Fill(SZ_DATA, b._1(rblgroup._2)) & b._2).reduce(_|_))

In each bank, there are three read operand lines (ropl):

Some or all of these lines need to be connected to the various functional units:

All in all, these units require a total of eight of the banks' lines: ropl0, rdata, ropl1, ropl0, rdata, rdata, rdata, and rdata, in that order. Because there is more than one bank, we must connect all of the banks' lines together into each of these read operand lines.

Only one bank will assert any one of the eight lines at a time; the other ones will be masked by a signal.

To begin, let's assume that we have one of the lines (say, the first ropl0). First, each of these lines will be zipped with its index to yield:

(ropl0, 0)

Each member will be the value of "rblgroup" for the purpose of the map statement.

rblgroup = (ropl0, 0)

The first member in this tuple, ropl0, by the way, contains all the banks' ropl0 signals. This tuple will be mapped to this sub-expression:

rblgroup => rblen.zip(rblgroup._1).map(b => Fill(SZ_DATA, b._1(rblgroup._2)) & b._2).reduce(_|_)

The "read bit line enable" values, expressed as "rblen" in the code, is a bit-vector whose width is equal to the number of banks. We match each bank's ropl0 with its rblen bit:

rblgroup => rblen.zip(rblgroup._1).map(b => Fill(SZ_DATA, b._1(rblgroup._2)) & b._2).reduce(_|_)

Hwacha has eight banks, so ropl0 has eight data lines and enable lines. The bolded section of code generates a list of tuples that look like this, where b is the number of banks:

((rblen[0], ropl0[0]), ... (rblen[b - 1], ropl0[b - 1]))

The second section of the code takes the data/enable line and creates the masking logic.

rblgroup => rblen.zip(rblgroup._1).map(b => Fill(SZ_DATA, b._1(rblgroup._2)) & b._2).reduce(_|_)

Each tuple in the newly-zipped group will be known as "b" for the purpose of the map, where n refers to the nth bank:

b = (rblen[n], ropl0[n])

Each bank's data will be bitwise-ANDed to an filled bit-vector of the size of the data wire (SZ_DATA).

rblgroup => rblen.zip(rblgroup._1).map(b => Fill(SZ_DATA, b._1(rblgroup._2)) & b._2).reduce(_|_)

In Scala, tuples' members can be referenced by a dot, followed by an underscore, followed by the index, starting from 1. For example, b._2 refers to ropl0[n]. The bolded section replicates the enable bit (b._1) of that particular bank number (rblgroup._2) for SZ_DATA times. Then, it takes the data from the operand line (b._2) and bitwise-ANDs it together. For each bank, it produces this (Verilog-ish) expression:

(ropl0[n] & {{SZ_DATA}rblen[n]})

For all banks, the result of the map statement will look like this, where "b" here is the number of banks:

((ropl0[n] & {{SZ_DATA}rblen[n]}), " " 1, ..., " " b - 1)

Finally, we need to reduce this list. Easy.

rblgroup => rblen.zip(rblgroup._1).map(b => Fill(SZ_DATA, b._1(rblgroup._2)) & b._2).reduce(_|_)

The bolded statement reduces with a function that bitwise-ORs the banks' bits together. The underscores are placeholders for the left and right hand sides of the reduction operation.

This results in the ANDed then ORed expression for one operand read line. Then, we simply repeat this for every operand read line with the outermost map statement.

val rbl = List(ropl0, rdata, ropl1, ropl0, rdata, rdata, rdata, rdata).zipWithIndex.map(
  rblgroup => rblen.zip(rblgroup._1).map(b => Fill(SZ_DATA, b._1(rblgroup._2)) & b._2).reduce(_|_))

Isn't it wonderful?