blob: 294a7043d3a005be88861a66d8e310a51291844e [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "ecc32_mem_area.h"
#include "secded_enc.h"
#include <cassert>
#include <cstring>
#include <stdexcept>
Ecc32MemArea::Ecc32MemArea(const std::string &scope, uint32_t size,
uint32_t width_32)
: MemArea(scope, size, 4 * width_32) {
// Check that multiplying by 4 didn't discard a bit
assert(4 * width_32 > width_32);
// No need to worry about ranges here: we've checked in the base class that
// width_byte isn't too big.
uint32_t phy_width_bits = 39 * width_32;
// This is a stronger check than the one in the base class (which
// makes sure there's enough space for the un-expanded memory width)
assert(phy_width_bits <= SV_MEM_WIDTH_BITS);
}
void Ecc32MemArea::LoadVmem(const std::string &path) const {
throw std::runtime_error(
"vmem files are not supported for memories with ECC bits");
}
void Ecc32MemArea::WriteBuffer(uint8_t buf[SV_MEM_WIDTH_BYTES],
const std::vector<uint8_t> &data,
size_t start_idx, uint32_t dst_word) const {
int log_width_32 = width_byte_ / 4;
int phy_width_bits = 39 * log_width_32;
int phy_width_bytes = (phy_width_bits + 7) / 8;
// Start by collecting our width_byte_ input bytes into (width_byte_ / 4)
// 32-bit groupings and adding check bits. (Eventually, we'll be adding
// proper ECC check bits here but, since we're not checking yet, let's
// zero-pad for now).
//
// TODO: Add proper ECC check bits here!
struct expanded_t {
uint8_t bytes[5];
};
std::vector<expanded_t> expanded(log_width_32);
for (int i = 0; i < log_width_32; ++i) {
// Store things little-endian, so the "real bits" go in bytes 0 to 3 and
// the check bits go in byte 4. Bytes 5 to 7 are zero.
expanded_t next;
memcpy(next.bytes, &data[start_idx + 4 * i], 4);
next.bytes[4] = enc_secded_39_32(next.bytes);
expanded[i] = next;
}
// Now write to buf, one output byte at a time.
for (int i = 0; i < phy_width_bytes; ++i) {
int out_bit = i * 8;
// Acc is the accumulator we're building up for the byte that should be
// written out. out_lsb is the LSB in acc to which we're writing at the
// moment.
uint8_t acc = 0;
int out_lsb = 0;
// in_word_idx is the input word that we're reading from and in_word_lsb is
// the first bit of that word that we're reading.
int in_word_idx = out_bit / 39;
int in_word_lsb = out_bit % 39;
// bits_left is the number of bits that we need to read for this byte. It's
// usually initialised to 8, except for the last byte of the output word,
// which might just have a few bits to contribute.
int bits_left = std::min(8, phy_width_bits - out_bit);
while (bits_left) {
// in_byte_idx is the index of the byte within the (expanded_t) input
// word that we're reading from. in_byte_lsb is the bit position within
// that byte.
int in_byte_idx = in_word_lsb / 8;
int in_byte_lsb = in_word_lsb % 8;
// Most of the bytes in the expanded_t hold 8 bits of data, except the
// top one, which only holds 7 (bits 39:32). bits_at_byte is the number
// of bits that we're reading from this input byte, constrained by the
// number of bits available and the number of bits that we want.
int in_byte_width = (in_byte_idx == 4) ? 7 : 8;
int bits_at_byte = std::min(in_byte_width - in_byte_lsb, bits_left);
// Extract bits_at_byte bits of input data from the relevant input byte,
// starting at in_byte_lsb.
uint8_t in_data = expanded[in_word_idx].bytes[in_byte_idx] >> in_byte_lsb;
uint8_t in_mask = (((uint32_t)1 << bits_at_byte) - 1) & 0xff;
uint8_t masked = in_data & in_mask;
// Add the extracted bits to acc, shifting them into position and
// updating out_lsb for next time around.
acc |= masked << out_lsb;
out_lsb += bits_at_byte;
// Update input pointers to step over the byte we just consumed.
if (in_byte_idx == 4) {
++in_word_idx;
in_word_lsb = 0;
} else {
in_word_lsb += bits_at_byte;
}
// Subtract the bits we just read from the count we're expecting to read.
bits_left -= bits_at_byte;
}
buf[i] = acc;
}
}
void Ecc32MemArea::ReadBuffer(std::vector<uint8_t> &data,
const uint8_t buf[SV_MEM_WIDTH_BYTES],
uint32_t src_word) const {
for (int i = 0; i < width_byte_; ++i) {
int in_word = i / 4;
int in_idx = (7 * in_word + 8 * i) / 8;
int in_lsb = (7 * in_word + 8 * i) % 8;
uint8_t acc = 0;
int bits_left = 8;
int out_lsb = 0;
while (bits_left) {
uint8_t in_data = buf[in_idx] >> in_lsb;
int bits_at_idx = std::min(8 - in_lsb, bits_left);
// The mask for the bits to take from in_data.
uint8_t in_mask = ((1u << bits_at_idx) - 1) & 0xff;
acc |= (in_data & in_mask) << out_lsb;
in_lsb = 0;
out_lsb += bits_at_idx;
bits_left -= bits_at_idx;
++in_idx;
}
data.push_back(acc);
}
}