├── libsv ├── __init__.py ├── math │ ├── half_adder.sv │ └── full_adder.sv ├── counters │ └── binary_counter.sv ├── latches │ └── sr_latch.sv ├── coders │ ├── gray_encoder.sv │ ├── gray_decoder.sv │ ├── onehot_priority_encoder.sv │ ├── priority_encoder.sv │ ├── bcd_encoder.sv │ └── bcd_decoder.sv ├── bit_ops │ └── rotate.sv ├── muxes │ └── onehot_mux.sv ├── synchronizers │ └── synchronizer.sv ├── fifos │ ├── sync_fifo.sv │ └── skid_buffer.sv └── arbiters │ └── ring_arbiter.sv ├── docs ├── requirements.txt ├── source │ ├── _static │ │ ├── style.css │ │ └── libsv_logo.svg │ ├── fifos.rst │ ├── muxes.rst │ ├── latches.rst │ ├── arbiters.rst │ ├── math.rst │ ├── bit_ops.rst │ ├── counters.rst │ ├── _templates │ │ └── layout.html │ ├── modules.rst │ ├── coders.rst │ ├── contact.rst │ ├── license.rst │ ├── useful_links.rst │ ├── half_adder.rst │ ├── full_adder.rst │ ├── binary_counter.rst │ ├── rotate.rst │ ├── bcd_encoder.rst │ ├── bcd_decoder.rst │ ├── onehot_mux.rst │ ├── sr_latch.rst │ ├── onehot_priority_encoder.rst │ ├── priority_encoder.rst │ ├── conf.py │ ├── skid_buffer.rst │ ├── ring_arbiter.rst │ ├── index.rst │ ├── contributing.rst │ ├── encoder_8b10b.rst │ └── decoder_8b10b.rst ├── Makefile └── make.bat ├── AUTHORS ├── .flake8 ├── tests ├── conftest.py ├── coders │ ├── test_bcd_decoder.py │ ├── test_bcd_encoder.py │ ├── test_onehot_priority_encoder.py │ ├── test_priority_encoder.py │ ├── test_gray_encoder.py │ ├── test_gray_decoder.py │ ├── test_encoder_8b10b.py │ └── test_decoder_8b10b.py ├── math │ ├── test_half_adder.py │ └── test_full_adder.py ├── latches │ └── test_sr_latch.py ├── muxes │ └── test_onehot_mux.py ├── synchronizers │ └── test_synchronizer.py ├── counters │ └── test_binary_counter.py ├── utils.py ├── bit_ops │ └── test_rotate.py ├── fifos │ ├── test_skid_buffer.py │ └── test_sync_fifo.py └── arbiters │ └── test_ring_arbiter.py ├── .gitignore ├── .github └── workflows │ ├── create-issue-branch.yml │ └── ci.yml ├── .readthedocs.yaml ├── Dockerfile.dev ├── LICENSE ├── pyproject.toml ├── Dockerfile ├── README.rst ├── .verible-verilog-format.yaml └── tools └── precommit.py /libsv/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx_rtd_theme -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Ben Sampson 2 | 3 | Contributors include:: 4 | 5 | -------------------------------------------------------------------------------- /docs/source/_static/style.css: -------------------------------------------------------------------------------- 1 | .wy-nav-content { 2 | max-width: none; 3 | } -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | max-doc-length = 88 4 | exclude = .git, dist -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | sys.path.append(os.path.dirname(__file__)) 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | docs/build/ 4 | .vscode/ 5 | *.pyc 6 | .pytest_cache 7 | .python-version -------------------------------------------------------------------------------- /docs/source/fifos.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | FIFOs 3 | ===== 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | skid_buffer -------------------------------------------------------------------------------- /docs/source/muxes.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Muxes 3 | ===== 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | onehot_mux -------------------------------------------------------------------------------- /docs/source/latches.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Latches 3 | ======= 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | sr_latch -------------------------------------------------------------------------------- /docs/source/arbiters.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Arbiters 3 | ======== 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | ring_arbiter -------------------------------------------------------------------------------- /docs/source/math.rst: -------------------------------------------------------------------------------- 1 | ==== 2 | Math 3 | ==== 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | half_adder 9 | full_adder -------------------------------------------------------------------------------- /docs/source/bit_ops.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | Bit Operations 3 | ============== 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | rotate -------------------------------------------------------------------------------- /docs/source/counters.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Counters 3 | ======== 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | binary_counter 9 | onehot_mux -------------------------------------------------------------------------------- /docs/source/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | {% block extrahead %} 3 | 4 | {% endblock %} -------------------------------------------------------------------------------- /docs/source/modules.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Modules 3 | ======= 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | arbiters 9 | bit_ops 10 | coders 11 | counters 12 | fifos 13 | latches 14 | math 15 | muxes -------------------------------------------------------------------------------- /docs/source/coders.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | Coders 3 | ====== 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | bcd_decoder 9 | bcd_encoder 10 | decoder_8b10b 11 | encoder_8b10b 12 | onehot_priority_encoder 13 | priority_encoder -------------------------------------------------------------------------------- /docs/source/contact.rst: -------------------------------------------------------------------------------- 1 | Contact 2 | ======= 3 | 4 | * `LibSV issue tracker `_ to report bugs or request features. 5 | * `LibSV discussions `_ at GitHub for general questions. -------------------------------------------------------------------------------- /docs/source/license.rst: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | Distributed under the terms of the `MIT `_ license, LibSV is free 5 | and open source software. 6 | 7 | .. literalinclude:: ../../LICENSE 8 | :language: text 9 | -------------------------------------------------------------------------------- /docs/source/useful_links.rst: -------------------------------------------------------------------------------- 1 | Useful Links 2 | ============ 3 | 4 | * `LibSV PyPI page `_ 5 | * `LibSV GitHub page `_ 6 | * `LibSV issue tracker `_ 7 | * `LibSV discussions `_ 8 | * :doc:`contributing` guide -------------------------------------------------------------------------------- /libsv/math/half_adder.sv: -------------------------------------------------------------------------------- 1 | `ifndef LIBSV_MATH_HALF_ADDER 2 | `define LIBSV_MATH_HALF_ADDER 3 | 4 | module half_adder ( 5 | input logic i_a, 6 | input logic i_b, 7 | output logic o_sum, 8 | output logic o_carry 9 | ); 10 | 11 | assign o_sum = i_a ^ i_b; 12 | assign o_carry = i_a & i_b; 13 | 14 | endmodule 15 | 16 | `endif /* LIBSV_MATH_HALF_ADDER */ 17 | -------------------------------------------------------------------------------- /libsv/math/full_adder.sv: -------------------------------------------------------------------------------- 1 | `ifndef LIBSV_MATH_FULL_ADDER 2 | `define LIBSV_MATH_FULL_ADDER 3 | 4 | module full_adder ( 5 | input logic i_a, 6 | input logic i_b, 7 | input logic i_carry, 8 | output logic o_sum, 9 | output logic o_carry 10 | ); 11 | 12 | assign o_sum = i_a ^ i_b ^ i_carry; 13 | assign o_carry = ((i_a | i_b) & i_carry) | (i_a & i_b); 14 | 15 | endmodule 16 | 17 | `endif /* LIBSV_MATH_FULL_ADDER */ 18 | -------------------------------------------------------------------------------- /.github/workflows/create-issue-branch.yml: -------------------------------------------------------------------------------- 1 | on: 2 | issues: 3 | types: [assigned] 4 | issue_comment: 5 | types: [created] 6 | pull_request: 7 | types: [closed] 8 | 9 | jobs: 10 | create_issue_branch_job: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Create Issue Branch 14 | uses: robvanderleek/create-issue-branch@main 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set OS and Python version 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.7" 13 | 14 | # Build documentation in the docs/source directory with Sphinx 15 | sphinx: 16 | builder: html 17 | configuration: docs/source/conf.py 18 | 19 | python: 20 | install: 21 | - requirements: docs/requirements.txt -------------------------------------------------------------------------------- /libsv/counters/binary_counter.sv: -------------------------------------------------------------------------------- 1 | `ifndef LIBSV_COUNTERS_BINARY_COUNTER 2 | `define LIBSV_COUNTERS_BINARY_COUNTER 3 | 4 | module binary_counter #( 5 | parameter integer N /* verilator public_flat_rd */ = 8 6 | ) ( 7 | input logic clk, 8 | input logic aresetn, 9 | output logic [N-1:0] q 10 | ); 11 | 12 | always_ff @(posedge clk or negedge aresetn) 13 | if (!aresetn) q <= 0; 14 | else q <= q + 1; 15 | 16 | endmodule 17 | 18 | `endif /* LIBSV_COUNTERS_BINARY_COUNTER */ 19 | -------------------------------------------------------------------------------- /libsv/latches/sr_latch.sv: -------------------------------------------------------------------------------- 1 | `ifndef LIBSV_LATCHES_SR_LATCH 2 | `define LIBSV_LATCHES_SR_LATCH 3 | 4 | module sr_latch ( 5 | input logic s, 6 | input logic r, 7 | output logic /* verilator lint_off UNOPTFLAT */ q /* verilator lint_on UNOPTFLAT */, 8 | output logic q_n 9 | ); 10 | 11 | assign q = ~(r | q_n); 12 | assign q_n = ~(s | q); 13 | 14 | endmodule 15 | 16 | `endif /* LIBSV_LATCHES_SR_LATCH */ 17 | -------------------------------------------------------------------------------- /tests/coders/test_bcd_decoder.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.triggers import Timer 3 | from utils import pytest_cocotb_run_test 4 | 5 | 6 | def test_bcd_decoder(pytestconfig): 7 | """Pytest fixture for BCD Decoder test""" 8 | pytest_cocotb_run_test(pytestconfig, __name__) 9 | 10 | 11 | @cocotb.test() 12 | async def cocotb_test_bcd_decoder(dut): 13 | """BCD Decoder test""" 14 | 15 | n = int(dut.N) 16 | 17 | for i in range(10**n): 18 | dut.i_bcd.value = int(str(int(i)), 16) 19 | await Timer(1) 20 | assert dut.o_bin == i 21 | -------------------------------------------------------------------------------- /tests/coders/test_bcd_encoder.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.triggers import Timer 3 | from utils import pytest_cocotb_run_test 4 | 5 | 6 | def test_bcd_encoder(pytestconfig): 7 | """Pytest fixture for BCD Encoder test""" 8 | pytest_cocotb_run_test(pytestconfig, __name__) 9 | 10 | 11 | @cocotb.test() 12 | async def cocotb_test_bcd_encoder(dut): 13 | """BCD Encoder test""" 14 | 15 | n = int(dut.N) 16 | 17 | for i in range(2**n): 18 | dut.i_bin.value = i 19 | await Timer(1) 20 | assert dut.o_bcd == int(str(int(dut.i_bin)), 16) 21 | -------------------------------------------------------------------------------- /Dockerfile.dev: -------------------------------------------------------------------------------- 1 | ARG TAG=main 2 | 3 | FROM bensampson5/libsv:${TAG} 4 | 5 | ARG DEBIAN_FRONTEND=noninteractive 6 | RUN apt-get update \ 7 | && apt-get install --no-install-recommends -y \ 8 | sudo \ 9 | && apt-get clean \ 10 | && rm -rf /var/lib/apt/lists/* 11 | 12 | ARG UNAME=developer 13 | ARG UID=1000 14 | ARG GID=1000 15 | RUN groupadd -g $GID -o $UNAME 16 | RUN useradd -m -u $UID -g $GID -o -s /bin/bash $UNAME \ 17 | && adduser $UNAME sudo \ 18 | && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers 19 | USER $UNAME 20 | 21 | WORKDIR /code 22 | CMD /bin/bash -------------------------------------------------------------------------------- /docs/source/half_adder.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Half Adder 3 | ========== 4 | 5 | A half adder. Takes two inputs, ``i_a`` and ``i_b``, and adds them together to generate a sum output, ``o_sum``, 6 | and a carry bit output, ``o_carry``. 7 | 8 | Parameters 9 | ---------- 10 | - None 11 | 12 | Ports 13 | ----- 14 | - ``i_a`` input a 15 | - ``i_b`` input b 16 | - ``o_sum`` output sum 17 | - ``o_carry`` output carry 18 | 19 | Source Code 20 | ----------- 21 | .. literalinclude:: ../../libsv/math/half_adder.sv 22 | :language: systemverilog 23 | :linenos: 24 | :caption: half_adder.sv 25 | -------------------------------------------------------------------------------- /libsv/coders/gray_encoder.sv: -------------------------------------------------------------------------------- 1 | `ifndef LIBSV_CODERS_GRAY_ENCODER 2 | `define LIBSV_CODERS_GRAY_ENCODER 3 | 4 | module gray_encoder #( 5 | parameter int DATA_WIDTH /* verilator public_flat_rd */ = 8 6 | ) ( 7 | input logic [DATA_WIDTH-1:0] i_bin, 8 | output logic [DATA_WIDTH-1:0] o_gray 9 | ); 10 | 11 | always_comb begin 12 | o_gray = '0; 13 | for (int i = 0; i < DATA_WIDTH - 1; ++i) begin 14 | o_gray[i] = i_bin[i] ^ i_bin[i+1]; 15 | end 16 | o_gray[DATA_WIDTH-1] = i_bin[DATA_WIDTH-1]; 17 | end 18 | 19 | endmodule 20 | 21 | `endif /* LIBSV_CODERS_GRAY_ENCODER */ 22 | -------------------------------------------------------------------------------- /docs/source/full_adder.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Full Adder 3 | ========== 4 | 5 | A full adder. Takes three inputs, ``i_a``, ``i_b``, and ``i_carry``, and adds them together to generate a sum output, ``o_sum``, 6 | and a carry bit output, ``o_carry``. 7 | 8 | Parameters 9 | ---------- 10 | - None 11 | 12 | Ports 13 | ----- 14 | - ``i_a`` input a 15 | - ``i_b`` input b 16 | - ``i_carry`` input carry 17 | - ``o_sum`` output sum 18 | - ``o_carry`` output carry 19 | 20 | Source Code 21 | ----------- 22 | .. literalinclude:: ../../libsv/math/full_adder.sv 23 | :language: systemverilog 24 | :linenos: 25 | :caption: full_adder.sv 26 | -------------------------------------------------------------------------------- /libsv/coders/gray_decoder.sv: -------------------------------------------------------------------------------- 1 | `ifndef LIBSV_CODERS_GRAY_DECODER 2 | `define LIBSV_CODERS_GRAY_DECODER 3 | 4 | module gray_decoder #( 5 | parameter int DATA_WIDTH /* verilator public_flat_rd */ = 8 6 | ) ( 7 | input logic [DATA_WIDTH-1:0] i_gray, 8 | output logic [DATA_WIDTH-1:0] o_bin 9 | ); 10 | 11 | always_comb begin 12 | o_bin = '0; 13 | for (int i = 0; i < DATA_WIDTH - 1; ++i) begin 14 | for (int j = i; j < DATA_WIDTH; ++j) begin 15 | o_bin[i] ^= i_gray[j]; 16 | end 17 | end 18 | o_bin[DATA_WIDTH-1] = i_gray[DATA_WIDTH-1]; 19 | end 20 | 21 | endmodule 22 | 23 | `endif /* LIBSV_CODERS_GRAY_DECODER */ 24 | -------------------------------------------------------------------------------- /libsv/bit_ops/rotate.sv: -------------------------------------------------------------------------------- 1 | `ifndef LIBSV_BIT_OPS_ROTATE 2 | `define LIBSV_BIT_OPS_ROTATE 3 | 4 | module rotate #( 5 | parameter int DATA_WIDTH /* verilator public_flat_rd */ = 8 6 | ) ( 7 | input logic [ DATA_WIDTH-1:0] i_data, 8 | input logic [$clog2(DATA_WIDTH)-1:0] i_amt, 9 | output logic [ DATA_WIDTH-1:0] o_data 10 | ); 11 | 12 | always_comb begin : rotate 13 | o_data = '0; 14 | for (int i = 0; i < DATA_WIDTH; ++i) begin 15 | int rotate_amt = (int'(i_amt) + i) % DATA_WIDTH; 16 | o_data[$bits(i_amt)'(rotate_amt)] = i_data[i]; 17 | end 18 | end : rotate 19 | 20 | endmodule : rotate 21 | 22 | `endif /* LIBSV_BIT_OPS_ROTATE */ 23 | -------------------------------------------------------------------------------- /tests/math/test_half_adder.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.triggers import Timer 3 | from utils import pytest_cocotb_run_test 4 | 5 | 6 | def test_half_adder(pytestconfig): 7 | """Pytest fixture for Half Adder test""" 8 | pytest_cocotb_run_test(pytestconfig, __name__) 9 | 10 | 11 | @cocotb.test() 12 | async def cocotb_test_half_adder(dut): 13 | """Half adder test""" 14 | 15 | for i in range(2**2): 16 | i_a = i & 1 17 | i_b = (i >> 1) & 1 18 | o_sum = i_a ^ i_b 19 | o_carry = i_a & i_b 20 | 21 | dut.i_a.value = i_a 22 | dut.i_b.value = i_b 23 | await Timer(1) 24 | assert dut.o_sum == o_sum 25 | assert dut.o_carry == o_carry 26 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /tests/latches/test_sr_latch.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.triggers import Timer 3 | from utils import pytest_cocotb_run_test 4 | 5 | 6 | def test_sr_latch(pytestconfig): 7 | """Pytest fixture for SR Latch test""" 8 | pytest_cocotb_run_test(pytestconfig, __name__) 9 | 10 | 11 | @cocotb.test() 12 | async def cocotb_test_sr_latch(dut): 13 | """SR Latch test""" 14 | 15 | dut.s.value = 0 16 | dut.r.value = 0 17 | await Timer(1) 18 | 19 | dut.s.value = 1 20 | await Timer(1) 21 | assert dut.q == 1 22 | assert dut.q_n == 0 23 | 24 | dut.s.value = 0 25 | await Timer(1) 26 | 27 | dut.r.value = 1 28 | await Timer(1) 29 | assert dut.q == 0 30 | assert dut.q_n == 1 31 | -------------------------------------------------------------------------------- /docs/source/binary_counter.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | Binary Counter 3 | ============== 4 | 5 | This module implements a parameterized binary counter with an active-low asynchronous reset. 6 | 7 | With every clock rising, the counter's output ``q`` is incremented by 1. And with an 8 | assertion of the active-low reset, ``aresetn``, ``q`` is set to 0. 9 | 10 | Parameters 11 | ---------- 12 | - ``N`` number of bits 13 | 14 | Ports 15 | ----- 16 | - ``clk`` clock 17 | - ``aresetn`` asynchoronous active-low reset 18 | - ``q`` count (N bits) 19 | 20 | Source Code 21 | ----------- 22 | .. literalinclude:: ../../libsv/counters/binary_counter.sv 23 | :language: systemverilog 24 | :linenos: 25 | :caption: binary_counter.sv 26 | 27 | -------------------------------------------------------------------------------- /libsv/muxes/onehot_mux.sv: -------------------------------------------------------------------------------- 1 | `ifndef LIBSV_MUXES_ONEHOT_MUX 2 | `define LIBSV_MUXES_ONEHOT_MUX 3 | 4 | module onehot_mux #( 5 | parameter int PORTS /* verilator public_flat_rd */ = 4, 6 | parameter int DATA_WIDTH /* verilator public_flat_rd */ = 8 7 | ) ( 8 | input logic [PORTS*DATA_WIDTH-1:0] i_data, 9 | input logic [ PORTS-1:0] i_select, 10 | output logic [ DATA_WIDTH-1:0] o_data 11 | ); 12 | 13 | always_comb begin 14 | o_data = '0; 15 | for (int i = 0; i < PORTS; ++i) begin 16 | o_data |= {(DATA_WIDTH) {i_select[i]}} & i_data[((i+1)*DATA_WIDTH-1)-:DATA_WIDTH]; 17 | end 18 | end 19 | 20 | endmodule : onehot_mux 21 | 22 | `endif /* LIBSV_MUXES_ONEHOT_MUX */ 23 | -------------------------------------------------------------------------------- /docs/source/rotate.rst: -------------------------------------------------------------------------------- 1 | .. _rotate: 2 | 3 | ====== 4 | Rotate 5 | ====== 6 | 7 | The rotate module is a combinatorial block that implements a bit-wise left rotation of the 8 | given input data, ``i_data``, by the amount, ``i_amt``. For example, an ``i_data`` of 9 | ``0b10010`` with an ``i_amt`` of ``0b001`` would result in an ``o_data`` of ``0b00101``. 10 | 11 | Parameters 12 | ---------- 13 | - ``DATA_WIDTH`` : data width in bits 14 | 15 | Ports 16 | ----- 17 | - ``i_data`` input data 18 | - ``i_amt`` amount to rotate 19 | - ``o_data`` output data (rotated version of input) 20 | 21 | Source Code 22 | ----------- 23 | .. literalinclude:: ../../libsv/bit_ops/rotate.sv 24 | :language: systemverilog 25 | :linenos: 26 | :caption: rotate.sv 27 | -------------------------------------------------------------------------------- /docs/source/bcd_encoder.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | BCD Encoder 3 | =========== 4 | 5 | The BCD (Binary-Coded Decimal) encoder is a combinatorial circuit that takes an ``N``-bit binary input, ``i_bin``, and converts it to a BCD value, 6 | ``o_bcd``, that is ``N + ceil{(N-4)/3} + 1`` bits long. It implements the conversion using the 7 | `double dabble `_ algorithm. 8 | 9 | Parameters 10 | ---------- 11 | - ``N`` number of bits in binary input 12 | 13 | Ports 14 | ----- 15 | - ``i_bin`` input binary value 16 | - ``o_bcd`` output BCD value 17 | 18 | Source Code 19 | ----------- 20 | .. literalinclude:: ../../libsv/coders/bcd_encoder.sv 21 | :language: systemverilog 22 | :linenos: 23 | :caption: bcd_encoder.sv 24 | -------------------------------------------------------------------------------- /libsv/coders/onehot_priority_encoder.sv: -------------------------------------------------------------------------------- 1 | `ifndef LIBSV_CODERS_ONEHOT_PRIORITY_ENCODER 2 | `define LIBSV_CODERS_ONEHOT_PRIORITY_ENCODER 3 | 4 | module onehot_priority_encoder #( 5 | parameter int DATA_WIDTH /* verilator public_flat_rd */ = 4 6 | ) ( 7 | input logic [DATA_WIDTH-1:0] i_data, 8 | output logic [DATA_WIDTH-1:0] o_data 9 | ); 10 | 11 | always_comb begin 12 | bit stop; 13 | o_data = '0; 14 | stop = 1'b0; 15 | 16 | for (int i = 0; i < DATA_WIDTH; ++i) begin 17 | if (i_data[i] == 1'b1 && !stop) begin 18 | o_data[i] = 1'b1; 19 | stop = 1'b1; 20 | end 21 | end 22 | end 23 | 24 | endmodule : onehot_priority_encoder 25 | 26 | `endif /* LIBSV_CODERS_ONEHOT_PRIORITY_ENCODER */ 27 | -------------------------------------------------------------------------------- /tests/math/test_full_adder.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.triggers import Timer 3 | from utils import pytest_cocotb_run_test 4 | 5 | 6 | def test_full_adder(pytestconfig): 7 | """Pytest fixture for Full Adder test""" 8 | pytest_cocotb_run_test(pytestconfig, __name__) 9 | 10 | 11 | @cocotb.test() 12 | async def cocotb_test_full_adder(dut): 13 | """Full adder test""" 14 | 15 | for i in range(2**3): 16 | i_a = i & 1 17 | i_b = (i >> 1) & 1 18 | i_carry = (i >> 2) & 1 19 | o_sum = i_a ^ i_b ^ i_carry 20 | o_carry = ((i_a | i_b) & i_carry) | (i_a & i_b) 21 | 22 | dut.i_a.value = i_a 23 | dut.i_b.value = i_b 24 | dut.i_carry.value = i_carry 25 | await Timer(1) 26 | assert dut.o_sum == o_sum 27 | assert dut.o_carry == o_carry 28 | -------------------------------------------------------------------------------- /tests/muxes/test_onehot_mux.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.triggers import Timer 3 | from utils import pytest_cocotb_run_test 4 | 5 | 6 | def test_onehot_mux(pytestconfig): 7 | """Pytest fixture for One-hot Mux test""" 8 | pytest_cocotb_run_test(pytestconfig, __name__) 9 | 10 | 11 | @cocotb.test() 12 | async def cocotb_test_onehot_mux(dut): 13 | """One-hot mux test""" 14 | 15 | ports = int(dut.PORTS) 16 | data_width = int(dut.DATA_WIDTH) 17 | 18 | # Generate counting pattern on input vectors 19 | dut_i_data = 0 20 | for i in range(ports): 21 | dut_i_data |= (i % data_width) << (i * data_width) 22 | dut.i_data.value = dut_i_data 23 | 24 | for i in range(ports): 25 | dut.i_select.value = 1 << i 26 | await Timer(1) 27 | assert i % data_width == int(dut.o_data) 28 | -------------------------------------------------------------------------------- /docs/source/bcd_decoder.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | BCD Decoder 3 | =========== 4 | 5 | The BCD (Binary-Coded Decimal) decoder is a combinatorial circuit takes a ``N`` decimal digit BCD input and converts it to a binary output. 6 | The BCD input , ``i_bcd``, is a ``4*N`` bit wide input vector where each 4 bits represents one decimal digit. The output binary value, ``o_bin``, 7 | is ``3*N + floor{(N+2)/3}`` bits long. It implements the conversion using the reverse `double dabble `_ algorithm. 8 | 9 | Parameters 10 | ---------- 11 | - ``N`` number of digits in BCD input 12 | 13 | Ports 14 | ----- 15 | - ``i_bcd`` input BCD value 16 | - ``o_bin`` output binary value 17 | 18 | Source Code 19 | ----------- 20 | .. literalinclude:: ../../libsv/coders/bcd_decoder.sv 21 | :language: systemverilog 22 | :linenos: 23 | :caption: bcd_decoder.sv 24 | -------------------------------------------------------------------------------- /libsv/synchronizers/synchronizer.sv: -------------------------------------------------------------------------------- 1 | `ifndef LIBSV_SYNCHRONIZERS_SYNCHRONIZER 2 | `define LIBSV_SYNCHRONIZERS_SYNCHRONIZER 3 | 4 | module synchronizer #( 5 | parameter int FF_STAGES /* verilator public_flat_rd */ = 2 6 | ) ( 7 | input logic i_clock, 8 | input logic i_aresetn, 9 | input logic i_data, 10 | output logic o_data 11 | ); 12 | 13 | logic [FF_STAGES-1:0] sync_mem; 14 | 15 | always_ff @(posedge i_clock, negedge i_aresetn) begin 16 | if (!i_aresetn) begin 17 | sync_mem <= '0; 18 | end else begin 19 | sync_mem[0] <= i_data; 20 | for (int i = 1; i < FF_STAGES; ++i) begin 21 | sync_mem[i] <= sync_mem[i-1]; 22 | end 23 | end 24 | end 25 | 26 | assign o_data = sync_mem[FF_STAGES-1]; 27 | 28 | endmodule 29 | 30 | `endif /* LIBSV_SYNCHRONIZERS_SYNCHRONIZER */ 31 | -------------------------------------------------------------------------------- /docs/source/onehot_mux.rst: -------------------------------------------------------------------------------- 1 | .. _one-hot-mux: 2 | 3 | =========== 4 | One-hot Mux 5 | =========== 6 | 7 | A parameterized implementation of a one-hot mux. Takes ``PORTS`` input ports each of width 8 | ``DATA_WIDTH`` as a concatenated input vector, ``i_data``, and using the one-hot select input, 9 | ``i_select``, muxes the selected input port to the output, ``o_data``. 10 | 11 | Parameters 12 | ---------- 13 | - ``PORTS`` number of input ports of each ``DATA_WIDTH`` bits 14 | - ``DATA_WIDTH`` data width in bits per input port 15 | 16 | Ports 17 | ----- 18 | - ``i_data`` concatenated input vector (``PORTS*DATA_WIDTH`` bits) 19 | - ``i_select`` select (``PORTS`` bits) 20 | - ``o_data`` output vector (``DATA_WIDTH`` bits) 21 | 22 | Source Code 23 | ----------- 24 | .. literalinclude:: ../../libsv/muxes/onehot_mux.sv 25 | :language: systemverilog 26 | :linenos: 27 | :caption: onehot_mux.sv 28 | -------------------------------------------------------------------------------- /libsv/coders/priority_encoder.sv: -------------------------------------------------------------------------------- 1 | `ifndef LIBSV_CODERS_PRIORITY_ENCODER 2 | `define LIBSV_CODERS_PRIORITY_ENCODER 3 | 4 | module priority_encoder #( 5 | parameter integer DATA_WIDTH /* verilator public_flat_rd */ = 4 6 | ) ( 7 | input logic [ DATA_WIDTH-1:0] i_data, 8 | output logic [$clog2(DATA_WIDTH)-1:0] o_data, 9 | output logic o_valid 10 | ); 11 | 12 | always_comb begin 13 | bit stop; 14 | o_data = '0; 15 | o_valid = 1'b0; 16 | stop = 1'b0; 17 | 18 | for (int i = 0; i < DATA_WIDTH; ++i) begin 19 | if (i_data[i] == 1'b1 && !stop) begin 20 | o_data = $clog2(DATA_WIDTH)'(i); 21 | o_valid = 1'b1; 22 | stop = 1'b1; 23 | end 24 | end 25 | end 26 | 27 | endmodule 28 | 29 | `endif /* LIBSV_CODERS_PRIORITY_ENCODER */ 30 | -------------------------------------------------------------------------------- /docs/source/sr_latch.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | SR Latch 3 | ======== 4 | 5 | This module implements a `SR NOR latch `_. 6 | 7 | While ``s`` (set) and ``r`` (reset) are both low, ``q`` and ``q_n`` are maintained in a constant state, with ``q_n`` being 8 | the complement of ``q``.If ``s`` goes high while ``r`` is held low, then ``q`` is forced high. Similarly, if ``r`` goes high 9 | while ``s`` is held low, then ``q`` is forced low. Having both ``s`` and ``r`` high (``1``) is a restricted combination. 10 | 11 | Parameters 12 | ---------- 13 | - None 14 | 15 | Ports 16 | ----- 17 | - ``s`` set 18 | - ``r`` reset 19 | - ``q`` output 20 | - ``q_n`` complemented output 21 | 22 | Source Code 23 | ----------- 24 | .. literalinclude:: ../../libsv/latches/sr_latch.sv 25 | :language: systemverilog 26 | :linenos: 27 | :caption: sr_latch.sv 28 | -------------------------------------------------------------------------------- /libsv/coders/bcd_encoder.sv: -------------------------------------------------------------------------------- 1 | `ifndef LIBSV_CODERS_BCD_ENCODER 2 | `define LIBSV_CODERS_BCD_ENCODER 3 | 4 | module bcd_encoder #( 5 | parameter integer N /* verilator public_flat_rd */ = 8 6 | ) ( 7 | input logic [ N-1:0] i_bin, 8 | output logic [N+(N-4)/3:0] o_bcd 9 | ); 10 | 11 | integer i, j; 12 | 13 | always_comb begin 14 | o_bcd = {{(N - 4) / 3 + 1{1'b0}}, i_bin}; // initialize with input vector in lower bits 15 | for (i = 0; i <= N - 4; i = i + 1) begin // iterate on structure depth 16 | for (j = 0; j <= i / 3; j = j + 1) begin // iterate on structure width 17 | if (o_bcd[N-i+4*j-:4] > 4) begin // if > 4 18 | o_bcd[N-i+4*j-:4] = o_bcd[N-i+4*j-:4] + 4'd3; // add 3 19 | end 20 | end 21 | end 22 | end 23 | 24 | endmodule 25 | 26 | `endif /* LIBSV_CODERS_BCD_ENCODER */ 27 | -------------------------------------------------------------------------------- /tests/synchronizers/test_synchronizer.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.clock import Clock 3 | from cocotb.triggers import FallingEdge 4 | from utils import pytest_cocotb_run_test 5 | 6 | 7 | def test_synchonrizer(pytestconfig): 8 | """Pytest fixture for Synchronizer test""" 9 | pytest_cocotb_run_test(pytestconfig, __name__) 10 | 11 | 12 | @cocotb.test() 13 | async def cocotb_test_synchonrizer(dut): 14 | """Synchronizer test""" 15 | 16 | ff_stages = int(dut.FF_STAGES) 17 | 18 | cocotb.start_soon(Clock(dut.i_clock, 2).start()) 19 | 20 | dut.i_aresetn.value = 0 21 | await FallingEdge(dut.i_clock) 22 | dut.i_aresetn.value = 1 23 | 24 | sync_value = 1 25 | dut.i_data.value = sync_value 26 | for _ in range(ff_stages): 27 | assert dut.o_data.value != sync_value 28 | await FallingEdge(dut.i_clock) 29 | 30 | assert dut.o_data.value == sync_value 31 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /tests/counters/test_binary_counter.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.clock import Clock 3 | from cocotb.triggers import FallingEdge, RisingEdge 4 | from utils import pytest_cocotb_run_test 5 | 6 | 7 | def test_binary_counter(pytestconfig): 8 | """Pytest fixture for Binary Counter test""" 9 | pytest_cocotb_run_test(pytestconfig, __name__) 10 | 11 | 12 | @cocotb.test() 13 | async def cocotb_test_binary_counter(dut): 14 | """Counter test""" 15 | 16 | n = int(dut.N) 17 | 18 | cocotb.start_soon(Clock(dut.clk, 2).start()) 19 | 20 | # reset 21 | dut.aresetn.value = 0 22 | await FallingEdge(dut.clk) 23 | dut.aresetn.value = 1 24 | 25 | # increment through all possible counter states 26 | for i in range(2**n): 27 | await RisingEdge(dut.clk) 28 | assert int(dut.q) == i 29 | 30 | # increment once more and check roll-over condition 31 | await RisingEdge(dut.clk) 32 | assert int(dut.q) == 0 33 | -------------------------------------------------------------------------------- /docs/source/onehot_priority_encoder.rst: -------------------------------------------------------------------------------- 1 | .. _one-hot-priority-encoder: 2 | 3 | ======================== 4 | One-hot Priority Encoder 5 | ======================== 6 | 7 | The One-hot Priority Encoder is a parameterized combinatorial implementation that takes input data, 8 | ``i_data``, and outputs a one-hot priority-encoded output, ``o_data``, where the least significant bit 9 | (bit 0) is the highest priority bit and the most significant bit (bit ``DATA_WIDTH-1``) is the lowest 10 | priority bit. For example, the input ``0b1001`` would result in the output ``0b0001``. 11 | 12 | Parameters 13 | ---------- 14 | - ``DATA_WIDTH`` : data width in bits 15 | 16 | Ports 17 | ----- 18 | - ``i_data`` : input data 19 | - ``o_data`` : one-hot priority-encoded output data 20 | 21 | Source Code 22 | ----------- 23 | .. literalinclude:: ../../libsv/coders/onehot_priority_encoder.sv 24 | :language: systemverilog 25 | :linenos: 26 | :caption: onehot_priority_encoder.sv 27 | -------------------------------------------------------------------------------- /tests/coders/test_onehot_priority_encoder.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.triggers import Timer 3 | from utils import pytest_cocotb_run_test 4 | 5 | 6 | def test_onehot_priority_encoder(pytestconfig): 7 | """Pytest fixture for Priority Encoder test""" 8 | pytest_cocotb_run_test(pytestconfig, __name__) 9 | 10 | 11 | @cocotb.test() 12 | async def cocotb_test_onehot_priority_encoder(dut): 13 | """One-hot Priority Encoder test""" 14 | 15 | data_width = int(dut.DATA_WIDTH) 16 | 17 | for i in range(2**data_width): 18 | dut.i_data.value = i # drive input 19 | 20 | await Timer(1) 21 | 22 | result = onehot_priority_encode(i, data_width) 23 | assert dut.o_data == result # check output 24 | 25 | 26 | def onehot_priority_encode(x: int, data_width: int) -> int: 27 | 28 | test = 0 29 | for shift_amt in range(data_width): 30 | 31 | test = x & (1 << shift_amt) 32 | 33 | if test > 0: 34 | return test 35 | 36 | return 0 37 | -------------------------------------------------------------------------------- /docs/source/priority_encoder.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | Priority Encoder 3 | ================ 4 | 5 | The Priority Encoder is a parameterized combinatorial implementation that takes input data, 6 | ``i_data``, and outputs a binary priority-encoded output, ``o_data``, where the least significant bit 7 | (bit 0) is the highest priority bit and the most significant bit (bit ``DATA_WIDTH-1``) is the lowest 8 | priority bit. For example, the input ``0b1001`` would result in the output ``0b00`` with the output 9 | valid signal being ``1``. Because the input can be all zeros, it's possible the output is not valid 10 | which is provided by the ``o_valid`` signal. 11 | 12 | Parameters 13 | ---------- 14 | - ``DATA_WIDTH`` : data width in bits 15 | 16 | Ports 17 | ----- 18 | - ``i_data`` : input data 19 | - ``o_data`` : binary priority-encoded output data 20 | - ``o_valid`` : valid output 21 | 22 | Source Code 23 | ----------- 24 | .. literalinclude:: ../../libsv/coders/priority_encoder.sv 25 | :language: systemverilog 26 | :linenos: 27 | :caption: priority_encoder.sv 28 | -------------------------------------------------------------------------------- /libsv/coders/bcd_decoder.sv: -------------------------------------------------------------------------------- 1 | `ifndef LIBSV_CODERS_BCD_DECODER 2 | `define LIBSV_CODERS_BCD_DECODER 3 | 4 | module bcd_decoder #( 5 | parameter integer N /* verilator public_flat_rd */ = 3 6 | ) ( 7 | input logic [ 4*N-1:0] i_bcd, 8 | output logic [3*N+(N+2)/3-1:0] o_bin 9 | ); 10 | 11 | integer i, j; 12 | 13 | logic [4*N-1:0] temp; // create temp vector as wide as i_bcd to hold intermediate values 14 | always_comb begin 15 | temp = i_bcd; // initialize with input vector 16 | for (i = 0; i < 4 * (N - 1); i = i + 1) begin // iterate on structure depth 17 | for (j = 0; j < N - i / 4 - 1; j = j + 1) begin // iterate on structure width 18 | if (temp[4+i+4*j-:4] > 7) begin // if > 7 19 | temp[4+i+4*j-:4] = temp[4+i+4*j-:4] - 4'd3; // subtract three 20 | end 21 | end 22 | end 23 | o_bin = temp[3*N+(N+2)/3-1:0]; // truncate final result in temp vector to get o_bin 24 | end 25 | 26 | endmodule 27 | 28 | `endif /* LIBSV_CODERS_BCD_DECODER */ 29 | -------------------------------------------------------------------------------- /tests/coders/test_priority_encoder.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.triggers import Timer 3 | from utils import pytest_cocotb_run_test 4 | 5 | 6 | def test_priority_encoder(pytestconfig): 7 | """Pytest fixture for Priority Encoder test""" 8 | pytest_cocotb_run_test(pytestconfig, __name__) 9 | 10 | 11 | @cocotb.test() 12 | async def cocotb_test_priority_encoder(dut): 13 | """Priority Encoder test""" 14 | 15 | data_width = int(dut.DATA_WIDTH) 16 | 17 | for i in range(2**data_width): 18 | dut.i_data.value = i # drive input 19 | 20 | await Timer(1) 21 | 22 | # check valid signal 23 | if i == 0: 24 | assert dut.o_valid == 0 25 | else: 26 | result = priority_encode(i, data_width) 27 | assert dut.o_valid == 1 28 | assert dut.o_data == result # check output 29 | 30 | 31 | def priority_encode(x: int, data_width: int) -> int: 32 | 33 | test = 0 34 | for shift_amt in range(data_width): 35 | 36 | test = x & (1 << shift_amt) 37 | 38 | if test > 0: 39 | return shift_amt 40 | 41 | return 0 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2021 Ben Sampson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/coders/test_gray_encoder.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.triggers import Timer 3 | from cocotb.log import SimLog 4 | from utils import pytest_cocotb_run_test 5 | 6 | 7 | def test_gray_encoder(pytestconfig): 8 | """Pytest fixture for Gray Encoder test""" 9 | pytest_cocotb_run_test(pytestconfig, __name__) 10 | 11 | 12 | @cocotb.test() 13 | async def cocotb_test_gray_encoder(dut): 14 | """Gray Encoder test""" 15 | 16 | log = SimLog("cocotb.test_gray_encoder") 17 | 18 | data_width = int(dut.DATA_WIDTH) 19 | 20 | for i in range(2**data_width): 21 | dut.i_bin.value = i 22 | await Timer(1) 23 | 24 | try: 25 | assert int(dut.o_gray.value) == gray_encode(i, data_width) 26 | except AssertionError as e: 27 | log.info( 28 | f"i_bin = {dut.i_bin.value}, o_gray = {dut.o_gray.value}, " 29 | + "gray_encode = " 30 | + format(gray_encode(i, data_width), f"0{data_width}b") 31 | ) 32 | raise e 33 | 34 | 35 | def gray_encode(binary: int, data_width: int) -> int: 36 | 37 | gray = binary & (1 << (data_width - 1)) 38 | for i in range(data_width - 1): 39 | gray |= (binary ^ (binary >> 1)) & (1 << i) 40 | 41 | return gray 42 | -------------------------------------------------------------------------------- /tests/coders/test_gray_decoder.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.triggers import Timer 3 | from cocotb.log import SimLog 4 | from utils import pytest_cocotb_run_test 5 | 6 | 7 | def test_gray_decoder(pytestconfig): 8 | """Pytest fixture for Gray Decoder test""" 9 | pytest_cocotb_run_test(pytestconfig, __name__) 10 | 11 | 12 | @cocotb.test() 13 | async def cocotb_test_gray_decoder(dut): 14 | """Gray Decoder test""" 15 | 16 | log = SimLog("cocotb.test_gray_decoder") 17 | 18 | data_width = int(dut.DATA_WIDTH) 19 | 20 | for i in range(2**data_width): 21 | 22 | dut.i_gray.value = i 23 | await Timer(1) 24 | 25 | try: 26 | assert int(dut.o_bin.value) == gray_decode(i, data_width) 27 | except AssertionError as e: 28 | log.info( 29 | f"i_gray = {dut.i_gray.value}, o_bin = {dut.o_bin.value}, " 30 | + "gray_decode = " 31 | + format(gray_decode(i, data_width), f"0{data_width}b") 32 | ) 33 | raise e 34 | 35 | 36 | def gray_decode(gray: int, data_width: int) -> int: 37 | 38 | binary = gray & (1 << (data_width - 1)) 39 | for i in range(data_width - 1): 40 | temp = gray & (1 << i) 41 | for j in range(i + 1, data_width): 42 | temp ^= gray >> (j - i) 43 | binary |= temp & (1 << i) 44 | 45 | return binary 46 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "libsv" 3 | version = "0.2.1" 4 | description = "An open source, parameterized SystemVerilog hardware IP library" 5 | authors = ["Ben Sampson "] 6 | maintainers = ["Ben Sampson "] 7 | license = "MIT" 8 | repository = "https://github.com/bensampson5/libsv" 9 | homepage = "https://libsv.readthedocs.io/en/latest/" 10 | documentation = "https://libsv.readthedocs.io/en/latest/" 11 | keywords = ["SystemVerilog", "Verilog", "Hardware", "IP", "Cocotb"] 12 | readme = "README.rst" 13 | classifiers = [ 14 | "Topic :: System :: Hardware", 15 | "Topic :: Scientific/Engineering", 16 | "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)" 17 | ] 18 | 19 | [tool.poetry.urls] 20 | "Bug Tracker" = "https://github.com/bensampson5/libsv/issues" 21 | 22 | [tool.poetry.dependencies] 23 | python = "^3.7,<4.0.0" 24 | 25 | [tool.poetry.dev-dependencies] 26 | cocotb = "^1.6.0" 27 | pytest = "^6.2.5" 28 | Sphinx = "^4.2.0" 29 | sphinx-rtd-theme = "^1.0.0" 30 | flake8 = "^4.0.1" 31 | black = "^22.3.0" 32 | click = "^8.0.3" 33 | colorama = "^0.4.4" 34 | cocotb-test = "^0.2.1" 35 | PyYAML = "^6.0" 36 | 37 | [build-system] 38 | requires = ["poetry-core>=1.0.0"] 39 | build-backend = "poetry.core.masonry.api" 40 | 41 | [tool.pytest.ini_options] 42 | testpaths = ["tests"] 43 | log_cli = true 44 | 45 | [tool.black] 46 | line-length = 88 47 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | from cocotb_test.simulator import run 4 | 5 | 6 | def pytest_cocotb_run_test(pytestconfig, test_name): 7 | os.environ["SIM"] = "verilator" # always use verilator as HDL simulator 8 | proj_path = Path(pytestconfig.rootpath) # capture top-level project path 9 | 10 | # get top level module name from file test name 11 | top_level = test_name.replace("test_", "") 12 | 13 | # determine verilog sources from top_level module 14 | src_dir = proj_path / "libsv" 15 | top_level_sv = list(src_dir.glob(f"**/{top_level}.sv")) 16 | if len(top_level_sv) == 1: 17 | top_level_sv = top_level_sv[0] 18 | with open(top_level_sv, "r") as f: 19 | 20 | found_top_level_module = False 21 | while not found_top_level_module: 22 | line = f.readline() 23 | if line.startswith(f"module {top_level} "): 24 | found_top_level_module = True 25 | 26 | if not found_top_level_module: 27 | raise RuntimeError("Could not find top level module") 28 | else: 29 | raise RuntimeError("Too many sv top level files") 30 | 31 | # determine build directory 32 | build_dir = Path(str(top_level_sv.parent).replace("libsv", "build")) 33 | 34 | # call to cocotb_test.simulator.run 35 | run( 36 | verilog_sources=[top_level_sv], 37 | toplevel=top_level, 38 | module=test_name, 39 | includes=[proj_path], 40 | sim_build=build_dir, 41 | waves=True, 42 | ) 43 | 44 | # rename waveform file 45 | wavefile = build_dir / "dump.fst" 46 | if wavefile.exists(): 47 | wavefile.rename(build_dir / f"{test_name}.fst") 48 | -------------------------------------------------------------------------------- /tests/bit_ops/test_rotate.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.triggers import Timer 3 | from cocotb.log import SimLog 4 | from utils import pytest_cocotb_run_test 5 | 6 | 7 | def test_rotate(pytestconfig): 8 | """Pytest fixture for Rotate test""" 9 | pytest_cocotb_run_test(pytestconfig, __name__) 10 | 11 | 12 | @cocotb.test() 13 | async def cocotb_test_rotate(dut): 14 | """Rotate test""" 15 | 16 | log = SimLog("cocotb.test_rotate") 17 | 18 | data_width = int(dut.DATA_WIDTH.value) 19 | amt_bits = len(dut.i_amt.value) 20 | 21 | for value in range(2**data_width): 22 | 23 | for amt in range(2**amt_bits): 24 | 25 | dut.i_data.value = value 26 | dut.i_amt.value = amt 27 | await Timer(1) 28 | 29 | o_data_expected = 0 30 | try: 31 | o_data_expected = do_rotate(value, amt, data_width) 32 | assert o_data_expected == dut.o_data.value 33 | log.debug( 34 | f"PASSED: i_data = 0b{dut.i_data.value}, i_amt = " 35 | f"0b{dut.i_amt.value}, o_data = 0b{dut.o_data.value}, " 36 | "o_data_expected = 0b" + format(o_data_expected, f"0{data_width}b") 37 | ) 38 | except AssertionError as e: 39 | log.critical( 40 | f"FAILED: i_data = 0b{dut.i_data.value}, i_amt = " 41 | f"0b{dut.i_amt.value}, o_data = 0b{dut.o_data.value}, " 42 | "o_data_expected = 0b" + format(o_data_expected, f"0{data_width}b") 43 | ) 44 | raise e 45 | 46 | 47 | def do_rotate(value: int, amt: int, data_width: int): 48 | data_width_mask = 2**data_width - 1 49 | adj_amt = amt % data_width # adjust amount if greater than data_width 50 | return ((value << adj_amt) & data_width_mask) | ( 51 | (value >> (data_width - adj_amt)) & data_width_mask 52 | ) 53 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | 13 | import os 14 | import sys 15 | 16 | sys.path.insert(0, os.path.abspath(".")) 17 | 18 | 19 | # -- Project information ----------------------------------------------------- 20 | 21 | project = "LibSV" 22 | copyright = "2020-2021, Ben Sampson" 23 | author = "Ben Sampson" 24 | 25 | 26 | # -- General configuration --------------------------------------------------- 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be 29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 | # ones. 31 | extensions = ["sphinx_rtd_theme"] 32 | 33 | # Add any paths that contain templates here, relative to this directory. 34 | templates_path = ["_templates"] 35 | 36 | # List of patterns, relative to source directory, that match files and 37 | # directories to ignore when looking for source files. 38 | # This pattern also affects html_static_path and html_extra_path. 39 | exclude_patterns = [] 40 | 41 | 42 | # -- Options for HTML output ------------------------------------------------- 43 | 44 | # The theme to use for HTML and HTML Help pages. See the documentation for 45 | # a list of builtin themes. 46 | # 47 | html_theme = "sphinx_rtd_theme" 48 | 49 | # Add any paths that contain custom static files (such as style sheets) here, 50 | # relative to this directory. They are copied after the builtin static files, 51 | # so a file named "default.css" will overwrite the builtin "default.css". 52 | html_static_path = ["_static"] 53 | 54 | html_logo = "_static/libsv_logo.svg" 55 | -------------------------------------------------------------------------------- /tests/fifos/test_skid_buffer.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.clock import Clock 3 | from cocotb.triggers import FallingEdge 4 | from utils import pytest_cocotb_run_test 5 | 6 | 7 | def test_skid_buffer(pytestconfig): 8 | """Pytest fixture for Skid Buffer test""" 9 | pytest_cocotb_run_test(pytestconfig, __name__) 10 | 11 | 12 | @cocotb.test() 13 | async def cocotb_test_skid_buffer(dut): 14 | """Skid Buffer test""" 15 | 16 | data_width = int(dut.DATA_WIDTH) 17 | 18 | cocotb.start_soon(Clock(dut.i_clock, 2).start()) 19 | 20 | # reset 21 | dut.i_aresetn.value = 0 22 | await FallingEdge(dut.i_clock) 23 | dut.i_aresetn.value = 1 24 | 25 | # generate data and have both input and output interfaces 26 | # be ready. 27 | i_data = 0 28 | o_data = 0 29 | dut.i_data.value = i_data 30 | dut.i_input_valid.value = 1 31 | dut.i_output_ready.value = 1 32 | 33 | # stream at full rate for 4 transactions 34 | for _ in range(4): 35 | await FallingEdge(dut.i_clock) 36 | o_data = i_data 37 | assert int(dut.o_data.value) == o_data # check output data 38 | i_data = (i_data + 1) % (2**data_width) 39 | dut.i_data.value = i_data # drive next input data 40 | 41 | # disable output ready so that skid buffer gets full 42 | dut.i_output_ready.value = 0 43 | await FallingEdge(dut.i_clock) 44 | for _ in range(4): 45 | await FallingEdge(dut.i_clock) 46 | assert int(dut.o_data.value) == o_data 47 | 48 | # re-enable output ready but disable input valid so that 49 | # buffer empties 50 | dut.i_output_ready.value = 1 51 | dut.i_input_valid.value = 0 52 | for _ in range(4): 53 | await FallingEdge(dut.i_clock) 54 | if o_data < i_data: 55 | o_data += 1 56 | assert int(dut.o_data.value) == o_data # check output data 57 | 58 | # continue streaming for another few transactions 59 | dut.i_input_valid.value = 1 60 | dut.i_output_ready.value = 1 61 | for _ in range(4): 62 | await FallingEdge(dut.i_clock) 63 | o_data = i_data 64 | assert int(dut.o_data.value) == o_data # check output data 65 | i_data = (i_data + 1) % (2**data_width) 66 | dut.i_data.value = i_data # drive next input data 67 | 68 | await FallingEdge(dut.i_clock) 69 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:jammy 2 | 3 | WORKDIR /tmp 4 | 5 | # Baseline apt-get installs 6 | ARG DEBIAN_FRONTEND=noninteractive 7 | RUN apt-get update \ 8 | && apt-get install --no-install-recommends -y \ 9 | apt-transport-https \ 10 | autoconf \ 11 | bc \ 12 | bison \ 13 | ca-certificates \ 14 | ccache \ 15 | curl \ 16 | flex \ 17 | g++ \ 18 | git \ 19 | gnupg \ 20 | help2man \ 21 | libfl2 \ 22 | libfl-dev \ 23 | libgoogle-perftools-dev \ 24 | make \ 25 | numactl \ 26 | perl \ 27 | perl-doc \ 28 | python3-dev \ 29 | python3-pip \ 30 | wget \ 31 | zlib1g \ 32 | zlib1g-dev \ 33 | && apt-get clean \ 34 | && rm -rf /var/lib/apt/lists/* 35 | 36 | # Add Bazel distribution URI as a package source 37 | RUN curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor >bazel-archive-keyring.gpg \ 38 | && mv bazel-archive-keyring.gpg /usr/share/keyrings \ 39 | && echo "deb [arch=amd64 signed-by=/usr/share/keyrings/bazel-archive-keyring.gpg] https://storage.googleapis.com/bazel-apt stable jdk1.8" | tee /etc/apt/sources.list.d/bazel.list 40 | 41 | # Install Bazel 42 | RUN apt-get update \ 43 | && apt-get install --no-install-recommends -y \ 44 | bazel \ 45 | && apt-get clean \ 46 | && rm -rf /var/lib/apt/lists/* 47 | 48 | # Install python packages using poetry 49 | RUN pip3 install poetry 50 | COPY pyproject.toml ./ 51 | RUN poetry config virtualenvs.create false 52 | RUN poetry install -n --no-ansi 53 | 54 | # Build and install Verilator v5.024 from source 55 | ARG REPO=https://github.com/verilator/verilator 56 | ARG TAG=v5.024 57 | RUN git clone --depth 1 --branch "${TAG}" "${REPO}" verilator \ 58 | && cd verilator \ 59 | && autoconf \ 60 | && ./configure \ 61 | && make -j$(nproc) \ 62 | && make install \ 63 | && cd .. \ 64 | && rm -rf verilator 65 | 66 | # Install Verible 67 | ARG VERIBLE_URL=https://github.com/chipsalliance/verible/releases/download/v0.0-3648-g5ef1624a/verible-v0.0-3648-g5ef1624a-linux-static-x86_64.tar.gz 68 | RUN wget ${VERIBLE_URL} -O verible.tar.gz \ 69 | && mkdir verible \ 70 | && tar -xf verible.tar.gz -C verible --strip-components=1 \ 71 | && cp -r verible/bin/* /usr/local/bin \ 72 | && rm -rf verible verible.tar.gz 73 | 74 | WORKDIR /root 75 | CMD /bin/bash -------------------------------------------------------------------------------- /tests/fifos/test_sync_fifo.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.clock import Clock 3 | from cocotb.triggers import FallingEdge 4 | from utils import pytest_cocotb_run_test 5 | 6 | 7 | def test_sync_fifo(pytestconfig): 8 | """Pytest fixture for Sync Fifo test""" 9 | pytest_cocotb_run_test(pytestconfig, __name__) 10 | 11 | 12 | @cocotb.test() 13 | async def cocotb_test_sync_fifo(dut): 14 | """Sync Fifo test""" 15 | 16 | data_width = int(dut.DATA_WIDTH) 17 | fifo_depth = int(dut.FIFO_DEPTH) 18 | 19 | cocotb.start_soon(Clock(dut.i_clock, 2).start()) 20 | 21 | # reset 22 | dut.i_aresetn.value = 0 23 | await FallingEdge(dut.i_clock) 24 | dut.i_aresetn.value = 1 25 | 26 | # fill up fifo completely 27 | dut.i_wr_en.value = 1 28 | for i in range(1, fifo_depth + 1): 29 | dut.i_data.value = i % data_width 30 | await FallingEdge(dut.i_clock) 31 | 32 | assert int(dut.o_full.value) == 1 33 | assert int(dut.o_empty.value) == 0 34 | 35 | # read out everything from fifo completely 36 | dut.i_wr_en.value = 0 37 | dut.i_rd_en.value = 1 38 | for i in range(1, fifo_depth + 1): 39 | assert int(dut.o_data.value) == i % data_width 40 | await FallingEdge(dut.i_clock) 41 | 42 | assert int(dut.o_full.value) == 0 43 | assert int(dut.o_empty.value) == 1 44 | 45 | # test streaming with 1 entry in fifo 46 | for i in range(1, 6): 47 | if i == 1: 48 | dut.i_wr_en.value = 1 49 | dut.i_data.value = i % data_width 50 | await FallingEdge(dut.i_clock) 51 | dut.i_rd_en.value = 1 52 | else: 53 | assert int(dut.o_data.value) == (i - 1) % data_width 54 | dut.i_data.value = i % data_width 55 | await FallingEdge(dut.i_clock) 56 | 57 | assert int(dut.o_empty.value) == 0 # never is empty 58 | 59 | # test streaming with fifo_depth-1 entries in fifo 60 | dut.i_wr_en.value = 1 61 | dut.i_rd_en.value = 0 62 | dut.i_aresetn.value = 0 63 | await FallingEdge(dut.i_clock) 64 | dut.i_aresetn.value = 1 65 | for i in range(1, fifo_depth + 5): 66 | if i < fifo_depth: # fill up fifo 67 | dut.i_data.value = i % data_width 68 | await FallingEdge(dut.i_clock) 69 | if i == fifo_depth - 1: 70 | dut.i_rd_en.value = 1 71 | else: 72 | assert int(dut.o_data.value) == (i - (fifo_depth - 1)) % data_width 73 | dut.i_data.value = i % data_width 74 | await FallingEdge(dut.i_clock) 75 | 76 | assert int(dut.o_full.value) == 0 # never is full 77 | 78 | await FallingEdge(dut.i_clock) 79 | -------------------------------------------------------------------------------- /docs/source/skid_buffer.rst: -------------------------------------------------------------------------------- 1 | .. _skid-buffer: 2 | 3 | =========== 4 | Skid Buffer 5 | =========== 6 | 7 | A skid buffer is used to break apart a combinatorial path between a sender and receiver interface by registering 8 | the data and handshake signals. The skid buffer uses two buffer entries, a main buffer and a "skid" buffer, that 9 | allows it to maintain the full throughput, thus pipelining the data path. Because there is a one clock cycle 10 | latency through the skid buffer for backpressuring the sender if it is backpressured by the receiver, the "skid" 11 | buffer is needed to absorb one extra transaction from the sender, before it can actually backpressure the sender. 12 | Skid buffers are commonly used for pipelining and resolving timing issues by breaking long combinatorial paths 13 | between a sender and receiver. Because there are only two buffer registers, it is also a very lightweight 14 | solution. 15 | 16 | This skid buffer implementation also provides two additional output status signals, ``o_accept`` and ``o_transmit``, 17 | which indicate when a transaction is successfully accepted from the input interface or successfully transmitted to 18 | the output interface. These two additional signals are provided so that a parent module can keep track of 19 | when the skid buffer is accepting or transmitting data. For example, this could be useful if the parent module 20 | needs that information for some of its own control logic or if it wants to keep track of how many transcations 21 | have been accepted and transmitted by the skid buffer. 22 | 23 | 24 | Parameters 25 | ---------- 26 | - ``DATA_WIDTH`` : data width in bits 27 | 28 | Ports 29 | ----- 30 | - ``i_clock`` : input clock 31 | - ``i_aresetn`` : asynchronous active-low reset 32 | - ``i_clear`` : synchronous clear 33 | - ``i_data`` : input data 34 | - ``i_input_valid`` : valid signal from input interface. Used with o_input_ready to handshake on input interface 35 | - ``i_output_ready`` : ready signal from output interface. Used with o_output_valid to handshake on output interface 36 | - ``o_data`` : output data 37 | - ``o_output_valid`` : valid signal from output interface. Used with i_output_ready to handshake on output interface 38 | - ``o_input_ready`` : ready signal from input interface. Used with i_input_valid to handshake on input interface 39 | - ``o_accept`` : accept status signal that indicates whenever input data is accepted by the skid buffer 40 | - ``o_transmit`` : transmit status signal that indicates whenever output data is transmitted by the skid buffer 41 | 42 | Source Code 43 | ----------- 44 | .. literalinclude:: ../../libsv/fifos/skid_buffer.sv 45 | :language: systemverilog 46 | :linenos: 47 | :caption: skid_buffer.sv 48 | -------------------------------------------------------------------------------- /libsv/fifos/sync_fifo.sv: -------------------------------------------------------------------------------- 1 | `ifndef LIBSV_FIFOS_SYNC_FIFO 2 | `define LIBSV_FIFOS_SYNC_FIFO 3 | 4 | module sync_fifo #( 5 | parameter int DATA_WIDTH /* verilator public_flat_rd */ = 8, 6 | parameter int FIFO_DEPTH /* verilator public_flat_rd */ = 4 7 | ) ( 8 | input logic i_clock, 9 | input logic i_aresetn, 10 | input logic i_wr_en, 11 | input logic i_rd_en, 12 | input logic [DATA_WIDTH-1:0] i_data, 13 | output logic [DATA_WIDTH-1:0] o_data, 14 | output logic o_full, 15 | output logic o_empty 16 | ); 17 | 18 | typedef enum logic [2:0] { 19 | EMPTY = 3'b001, 20 | BUSY = 3'b010, 21 | FULL = 3'b100 22 | } state_t; 23 | 24 | state_t state, next_state; 25 | logic write, read; // variables that hold if write or read is happening 26 | logic [$clog2(FIFO_DEPTH)-1:0] wr_addr, rd_addr; 27 | logic [$clog2(FIFO_DEPTH)-1:0] next_wr_addr, next_rd_addr; 28 | logic [FIFO_DEPTH-1:0][DATA_WIDTH-1:0] fifo_mem; 29 | 30 | // Logic for status outputs empty and full 31 | always_ff @(posedge i_clock, negedge i_aresetn) begin 32 | if (!i_aresetn) begin 33 | o_empty <= 1'b1; 34 | o_full <= 1'b0; 35 | end else begin 36 | o_empty <= next_state == EMPTY; 37 | o_full <= next_state == FULL; 38 | end 39 | end 40 | 41 | always_comb begin : next_state_logic 42 | write = i_wr_en && (state != FULL); 43 | read = i_rd_en && (state != EMPTY); 44 | 45 | unique case (state) 46 | EMPTY: begin 47 | next_state = EMPTY; 48 | if (write) next_state = BUSY; 49 | end 50 | BUSY: begin 51 | next_state = BUSY; 52 | if (read && !write && next_rd_addr == wr_addr) next_state = EMPTY; 53 | else if (!read && write && next_wr_addr == rd_addr) next_state = FULL; 54 | end 55 | FULL: begin 56 | next_state = FULL; 57 | if (read) next_state = BUSY; 58 | end 59 | default: next_state = EMPTY; 60 | endcase 61 | end : next_state_logic 62 | 63 | always_ff @(posedge i_clock, negedge i_aresetn) begin : current_state_logic 64 | if (!i_aresetn) begin 65 | state <= EMPTY; 66 | end else begin 67 | state <= next_state; 68 | end 69 | end : current_state_logic 70 | 71 | always_comb begin : next_wr_rd_addr_logic 72 | next_wr_addr = wr_addr == $bits(wr_addr)'(FIFO_DEPTH - 1) ? '0 : wr_addr + 1'b1; 73 | next_rd_addr = rd_addr == $bits(rd_addr)'(FIFO_DEPTH - 1) ? '0 : rd_addr + 1'b1; 74 | end : next_wr_rd_addr_logic 75 | 76 | always_ff @(posedge i_clock, negedge i_aresetn) begin : update_rd_wr_addr 77 | if (!i_aresetn) begin 78 | rd_addr <= '0; 79 | wr_addr <= '0; 80 | end else begin 81 | wr_addr <= write ? next_wr_addr : wr_addr; 82 | rd_addr <= read ? next_rd_addr : rd_addr; 83 | end 84 | end : update_rd_wr_addr 85 | 86 | always_ff @(posedge i_clock, negedge i_aresetn) begin : data_path_logic 87 | if (!i_aresetn) begin 88 | fifo_mem <= '0; 89 | end else begin 90 | if (write) fifo_mem[wr_addr] <= i_data; 91 | end 92 | end : data_path_logic 93 | 94 | assign o_data = fifo_mem[rd_addr]; 95 | 96 | endmodule 97 | 98 | `endif /* LIBSV_FIFOS_SYNC_FIFO */ 99 | -------------------------------------------------------------------------------- /tests/arbiters/test_ring_arbiter.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.clock import Clock 3 | from cocotb.triggers import FallingEdge 4 | from cocotb.log import SimLog 5 | from utils import pytest_cocotb_run_test 6 | 7 | 8 | def test_ring_arbiter(pytestconfig): 9 | """Pytest fixture for Ring Arbiter test""" 10 | pytest_cocotb_run_test(pytestconfig, __name__) 11 | 12 | 13 | @cocotb.test() 14 | async def cocotb_test_ring_arbiter(dut): 15 | """Ring Arbiter test""" 16 | 17 | log = SimLog("cocotb.test_ring_arbiter") 18 | 19 | ports = int(dut.PORTS) 20 | data_width = int(dut.DATA_WIDTH) 21 | 22 | cocotb.start_soon(Clock(dut.i_clock, 2).start()) 23 | 24 | # reset 25 | dut.i_aresetn.value = 0 26 | await FallingEdge(dut.i_clock) 27 | dut.i_aresetn.value = 1 28 | 29 | # setup i_data 30 | i_data_array = [i & (2**data_width - 1) for i in range(ports)] 31 | i_data = 0 32 | for i in range(ports): 33 | i_data |= i << (data_width * i) 34 | log.info("i_data = 0x" + format(i_data, f"0{ports*data_width//4}x")) 35 | log.info(f"i_data_array = {i_data_array}") 36 | dut.i_data.value = i_data 37 | dut.i_input_valid.value = 2**ports - 1 38 | 39 | # Scenario 1: Have all ports have data ready for the arbiter and 40 | # have output interface be ready to receive data from arbiter. This 41 | # will test the ring arbiter at max throughput 42 | dut.i_clear.value = 1 43 | await FallingEdge(dut.i_clock) 44 | dut.i_clear.value = 0 45 | dut.i_output_ready.value = 1 46 | for i in range(4 * ports): 47 | await FallingEdge(dut.i_clock) 48 | if i > 0: 49 | assert int(dut.o_data.value) == i_data_array[(i % len(i_data_array)) - 1] 50 | 51 | # Scenario 2: Have a port drop out but still maintain max throughput 52 | # of output interface 53 | dut.i_input_valid.value = 2**ports - 2 # port 0 drops out 54 | i_data_array_s2 = i_data_array[1:] 55 | for i in range(4 * (ports - 1)): 56 | await FallingEdge(dut.i_clock) 57 | assert int(dut.o_data.value) == i_data_array_s2[(i % len(i_data_array_s2)) - 1] 58 | 59 | # Scenario 3: Have the output interface backpressure the ring arbiter 60 | # which should cause it to fill up and backpressure the input interface 61 | dut.i_output_ready.value = 0 62 | prev_i_data = int(dut.sb.i_data.value) 63 | prev_o_data = int(dut.o_data.value) 64 | await FallingEdge(dut.i_clock) 65 | # check that output is being held even though input is still accepted 66 | current_i_data = int(dut.sb.i_data.value) 67 | assert current_i_data == i_data_array_s2[0] and current_i_data != prev_i_data 68 | assert int(dut.o_data.value) == prev_o_data 69 | prev_i_data = int(dut.sb.i_data.value) # update prev_i_data 70 | await FallingEdge(dut.i_clock) 71 | # check that now both input and output are being held 72 | assert int(dut.sb.i_data.value) == prev_i_data 73 | assert int(dut.o_data.value) == prev_o_data 74 | 75 | # Scenario 4: Have all ports drop out but release the backpressing of the 76 | # ring arbiter by the output interface and validate the ring arbiter empties 77 | dut.i_input_valid.value = 0 # all ports drop out 78 | dut.i_output_ready.value = 1 # re-enable output interface 79 | await FallingEdge(dut.i_clock) # ring arbiter empties skid buffer 80 | current_o_data = int(dut.o_data.value) 81 | assert current_o_data != prev_o_data and current_o_data == i_data_array_s2[-1] 82 | prev_o_data = current_o_data 83 | await FallingEdge(dut.i_clock) # ring arbiter empties last entry 84 | assert dut.o_output_valid.value == 0 85 | -------------------------------------------------------------------------------- /docs/source/ring_arbiter.rst: -------------------------------------------------------------------------------- 1 | .. _ring-arbiter: 2 | 3 | ============ 4 | Ring Arbiter 5 | ============ 6 | 7 | A ring arbiter is a many-to-one flow control block that accepts transactions from 2 or more input ports and 8 | provides that data to an output interface. If more than 1 input port is ready to send data, the ring arbiter 9 | will arbitrate between all active input ports by accepting 1 transaction from each input port, in port order, 10 | so that no input port is allowed to transmit two transactions in a row as long as there is at least one 11 | other input port that is waiting to send a transaction. 12 | 13 | This implementation is pipelined from input to output so that back-to-back transfers are achieved, thus 14 | allowing for sustained maximum data throughput to the output interface. As shown in the figure below, 15 | the core of the control path design of the ring arbiter consists of two :ref:`rotate` blocks, a 16 | :ref:`one-hot-priority-encoder`, and a rotate controller. The pre-:ref:`rotate` + 17 | :ref:`one-hot-priority-encoder` + post-:ref:`rotate` combination determines which input port will be serviced 18 | next while the rotate controller calculates the rotation amounts such that the last accepted input port is 19 | the lowest priority input port until a new transaction is accepted from a different input port, thus changing 20 | the last accepted input port. Therefore, the prev_accepted register is only written when the 21 | :ref:`skid-buffer` indicates that it has accepted a new transaction from an input port. 22 | 23 | The datapath for the ring arbiter consists of a :ref:`one-hot-mux` and a :ref:`skid-buffer`. The 24 | :ref:`one-hot-mux` selects the data from the input port that has been decided by the control path so that the 25 | :ref:`skid-buffer` sees the correct input port's data at its input. On the output, the :ref:`skid-buffer`'s 26 | output is connected directly to the output interface. The :ref:`skid-buffer` is used here because it provides 27 | the back-to-back transfers needed on a pipelined interface in a fairly lightweight manner. 28 | 29 | Note that the ``i_clock``, ``i_aresetn``, and ``i_clear`` signals are intentionally omitted from the block diagram 30 | below to make it easier to read and understand the ring arbiter design. 31 | 32 | .. image:: _static/ring_arbiter.svg 33 | :align: center 34 | :width: 80% 35 | :alt: Ring Arbiter Block Diagram 36 | 37 | Parameters 38 | ---------- 39 | - ``PORTS`` : number of input ports each of ``DATA_WIDTH`` bits 40 | - ``DATA_WIDTH`` : data width per input port in bits 41 | 42 | Ports 43 | ----- 44 | - ``i_clock`` : input clock 45 | - ``i_aresetn`` : asynchronous active-low reset 46 | - ``i_clear`` : synchronous clear 47 | - ``i_data`` : input data of all ports concatenated into a single vector (size is ``PORTS*DATA_WIDTH`` bits) 48 | - ``i_input_valid`` : valid signals from all input ports. Used with o_input_ready for input handshaking 49 | - ``i_output_ready`` : ready signal from output interface. Used with o_output_valid for output handshaking 50 | - ``o_data`` : output data (size is ``DATA_WIDTH`` bits) 51 | - ``o_output_valid`` : valid signal from output interface. Used with i_output_ready for output handshaking 52 | - ``o_input_ready`` : ready signals to all input ports. Used with i_input_valid for input handshaking 53 | - ``o_accept`` : accept status signal that indicates whenever input data is accepted by the ring arbiter 54 | - ``o_transmit`` : transmit status signal that indicates whenever output data is transmitted by the ring arbiter 55 | 56 | Source Code 57 | ----------- 58 | .. literalinclude:: ../../libsv/arbiters/ring_arbiter.sv 59 | :language: systemverilog 60 | :linenos: 61 | :caption: ring_arbiter.sv 62 | -------------------------------------------------------------------------------- /libsv/fifos/skid_buffer.sv: -------------------------------------------------------------------------------- 1 | `ifndef LIBSV_FIFOS_SKID_BUFFER 2 | `define LIBSV_FIFOS_SKID_BUFFER 3 | 4 | module skid_buffer #( 5 | parameter int DATA_WIDTH /* verilator public_flat_rd */ = 32 6 | ) ( 7 | input logic i_clock, 8 | input logic i_aresetn, 9 | input logic i_clear, 10 | input logic [DATA_WIDTH-1:0] i_data, 11 | input logic i_input_valid, 12 | input logic i_output_ready, 13 | output logic [DATA_WIDTH-1:0] o_data, 14 | output logic o_output_valid, 15 | output logic o_input_ready, 16 | output logic o_accept, 17 | output logic o_transmit 18 | ); 19 | 20 | // CONTROL PATH ----------------------------- 21 | 22 | typedef enum logic [2:0] { 23 | EMPTY = 3'b001, 24 | BUSY = 3'b010, 25 | FULL = 3'b100 26 | } state_t; 27 | 28 | state_t state, next_state; // state variables 29 | logic accept, transmit; // handshake flags on each interface 30 | logic [DATA_WIDTH-1:0] buffer; // the "skid" buffer 31 | 32 | assign o_accept = accept; 33 | assign o_transmit = transmit; 34 | 35 | always_comb begin : next_state_logic 36 | accept = i_input_valid && o_input_ready; // check for input handshake 37 | transmit = o_output_valid && i_output_ready; // check for output handshake 38 | next_state = EMPTY; 39 | unique case (state) 40 | EMPTY: begin 41 | next_state = EMPTY; 42 | if (accept) next_state = BUSY; 43 | end 44 | BUSY: begin 45 | next_state = BUSY; 46 | if (accept && !transmit) next_state = FULL; 47 | else if (!accept && transmit) next_state = EMPTY; 48 | end 49 | FULL: begin 50 | next_state = FULL; 51 | if (transmit) next_state = BUSY; 52 | end 53 | default: next_state = EMPTY; 54 | endcase 55 | end : next_state_logic 56 | 57 | always_ff @(posedge i_clock, negedge i_aresetn) begin : update_state_logic 58 | if (!i_aresetn || i_clear) begin 59 | state <= EMPTY; 60 | o_input_ready <= 1'b0; 61 | o_output_valid <= 1'b0; 62 | end else begin 63 | state <= next_state; 64 | o_input_ready <= next_state != FULL; 65 | o_output_valid <= next_state != EMPTY; 66 | end 67 | end : update_state_logic 68 | 69 | logic buffer_write_en, o_data_write_en; 70 | always_comb begin : write_en_logic 71 | buffer_write_en = state == BUSY && accept && !transmit; 72 | o_data_write_en = (state == EMPTY && accept && !transmit) 73 | || (state == BUSY && accept && transmit) 74 | || (state == FULL && !accept && transmit); 75 | end : write_en_logic 76 | 77 | // END OF CONTROL PATH ---------------------- 78 | 79 | // DATA PATH -------------------------------- 80 | 81 | always_ff @(posedge i_clock, negedge i_aresetn) begin : o_data_and_buffer_logic 82 | if (!i_aresetn || i_clear) begin 83 | o_data <= '0; 84 | buffer <= '0; 85 | end else begin 86 | 87 | if (o_data_write_en) begin 88 | if (state == FULL) o_data <= buffer; 89 | else o_data <= i_data; 90 | end 91 | 92 | if (buffer_write_en) begin 93 | buffer <= i_data; 94 | end 95 | 96 | end 97 | end : o_data_and_buffer_logic 98 | 99 | // END OF DATA PATH ------------------------- 100 | 101 | endmodule 102 | 103 | `endif /* LIBSV_FIFOS_SKID_BUFFER */ 104 | -------------------------------------------------------------------------------- /libsv/arbiters/ring_arbiter.sv: -------------------------------------------------------------------------------- 1 | `ifndef LIBSV_ARBITERS_RING_ARBITER 2 | `define LIBSV_ARBITERS_RING_ARBITER 3 | 4 | `include "libsv/bit_ops/rotate.sv" 5 | `include "libsv/coders/onehot_priority_encoder.sv" 6 | `include "libsv/muxes/onehot_mux.sv" 7 | `include "libsv/fifos/skid_buffer.sv" 8 | 9 | module ring_arbiter #( 10 | parameter int PORTS /* verilator public_flat_rd */ = 4, 11 | parameter int DATA_WIDTH /* verilator public_flat_rd */ = 8 12 | ) ( 13 | input logic i_clock, 14 | input logic i_aresetn, 15 | input logic i_clear, 16 | // This is a bummer. Would much rather do: 17 | // input logic [PORTS-1:0][DATA_WIDTH-1:0] i_data 18 | // and have a 2D packed array but verilator doesn't 19 | // support 2D packed arrays using VPI which is what 20 | // cocotb uses. See https://github.com/verilator/verilator/issues/2812. 21 | input logic [PORTS*DATA_WIDTH-1:0] i_data, 22 | input logic [ PORTS-1:0] i_input_valid, 23 | input logic i_output_ready, 24 | output logic [ DATA_WIDTH-1:0] o_data, 25 | output logic o_output_valid, 26 | output logic [ PORTS-1:0] o_input_ready, 27 | output logic o_accept, 28 | output logic o_transmit 29 | ); 30 | 31 | logic [$clog2(PORTS)-1:0] pre_rotate_amt; 32 | logic [ PORTS-1:0] pre_rotate_out; 33 | logic [ PORTS-1:0] ohpe_out; 34 | logic ohpe_valid; 35 | logic [$clog2(PORTS)-1:0] post_rotate_amt; 36 | logic [ PORTS-1:0] post_rotate_out; 37 | logic [ PORTS-1:0] prev_accepted; 38 | logic accept, transmit; 39 | logic is_any_input_valid; 40 | logic input_ready; 41 | logic [DATA_WIDTH-1:0] ohm_out; 42 | 43 | // CONTROL PATH ------------------------------- 44 | 45 | // pre-rotate 46 | rotate #(PORTS) pre_rotate ( 47 | .i_data(i_input_valid), 48 | .i_amt (pre_rotate_amt), 49 | .o_data(pre_rotate_out) 50 | ); 51 | 52 | // onehot priority encoder selects the current highest priority input port 53 | onehot_priority_encoder #(PORTS) ohpe ( 54 | .i_data(pre_rotate_out), 55 | .o_data(ohpe_out) 56 | ); 57 | 58 | // post-rotate (undo the pre-rotate) 59 | rotate #(PORTS) post_rotate ( 60 | .i_data(ohpe_out), 61 | .i_amt (post_rotate_amt), 62 | .o_data(post_rotate_out) 63 | ); 64 | 65 | always_ff @(posedge i_clock, negedge i_aresetn) begin : prev_accepted_logic 66 | if (!i_aresetn || i_clear) begin 67 | prev_accepted <= '0; 68 | end else begin 69 | if (accept) prev_accepted <= post_rotate_out; 70 | else prev_accepted <= prev_accepted; 71 | end 72 | end : prev_accepted_logic 73 | 74 | always_comb begin : rotate_controller 75 | pre_rotate_amt = '0; 76 | post_rotate_amt = '0; 77 | for (int i = 0; i < $bits(prev_accepted); ++i) begin 78 | if (prev_accepted[i]) begin 79 | pre_rotate_amt = $bits(pre_rotate_amt)'(PORTS - i - 1); 80 | post_rotate_amt = $bits(post_rotate_amt)'(i + 1); 81 | end 82 | end 83 | end : rotate_controller 84 | 85 | assign is_any_input_valid = |i_input_valid; 86 | assign o_input_ready = {PORTS{input_ready}} & post_rotate_out; 87 | assign o_accept = accept; 88 | assign o_transmit = transmit; 89 | 90 | // END OF CONTROL PATH ------------------------ 91 | 92 | // DATA PATH ---------------------------------- 93 | 94 | onehot_mux #( 95 | .PORTS (PORTS), 96 | .DATA_WIDTH(DATA_WIDTH) 97 | ) ohm ( 98 | .i_data (i_data), 99 | .i_select(post_rotate_out), 100 | .o_data (ohm_out) 101 | ); 102 | 103 | skid_buffer #(DATA_WIDTH) sb ( 104 | .i_clock (i_clock), 105 | .i_aresetn (i_aresetn), 106 | .i_clear (i_clear), 107 | .i_data (ohm_out), 108 | .i_input_valid (is_any_input_valid), 109 | .i_output_ready(i_output_ready), 110 | .o_data (o_data), 111 | .o_output_valid(o_output_valid), 112 | .o_input_ready (input_ready), 113 | .o_accept (accept), 114 | .o_transmit (transmit) 115 | ); 116 | 117 | // END OF DATA PATH --------------------------- 118 | 119 | endmodule : ring_arbiter 120 | 121 | `endif /* LIBSV_ARBITERS_RING_ARBITER */ 122 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - "**" 7 | tags: 8 | - "v*" 9 | 10 | jobs: 11 | # This job builds and updates the docker images on the LibSV's Docker Hub. It will create and push a 12 | # new image if any of the following conditions apply: 13 | # 1. This is a new commit to the 'main' branch. 14 | # 2. If a tag for this branch does not already exist on LibSV's Docker Hub. 15 | # 3. If the Dockerfile in the top project directory changed in the most recent commit. 16 | docker: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | fetch-depth: 2 23 | 24 | - name: Check if Dockerfile changed in last commit 25 | run: | 26 | echo "DOCKERFILE_CHANGED=$(git diff --name-only HEAD~1 HEAD | grep -c Dockerfile)" >> $GITHUB_ENV 27 | 28 | - name: Docker meta 29 | id: docker_meta 30 | uses: docker/metadata-action@v3 31 | with: 32 | images: bensampson5/libsv 33 | 34 | - name: Check if docker image already exists 35 | run: | 36 | echo '{"experimental": "enabled"}' > ~/.docker/config.json; 37 | if [[ $(docker manifest inspect ${{ steps.docker_meta.outputs.tags }} 2>/dev/null) ]]; then 38 | echo "DOCKER_IMAGE_EXISTS=1" >> $GITHUB_ENV; 39 | else 40 | echo "DOCKER_IMAGE_EXISTS=0" >> $GITHUB_ENV; 41 | fi; 42 | 43 | - name: Set up QEMU 44 | if: env.DOCKERFILE_CHANGED == '1' || env.DOCKER_IMAGE_EXISTS == '0' || github.ref == 'refs/heads/main' 45 | uses: docker/setup-qemu-action@v1 46 | 47 | - name: Set up Docker Buildx 48 | if: env.DOCKERFILE_CHANGED == '1' || env.DOCKER_IMAGE_EXISTS == '0' || github.ref == 'refs/heads/main' 49 | uses: docker/setup-buildx-action@v1 50 | 51 | - name: Cache Docker layers 52 | if: env.DOCKERFILE_CHANGED == '1' || env.DOCKER_IMAGE_EXISTS == '0' || github.ref == 'refs/heads/main' 53 | uses: actions/cache@v2 54 | with: 55 | path: /tmp/.buildx-cache 56 | key: ${{ runner.os }}-buildx-${{ github.sha }} 57 | restore-keys: | 58 | ${{ runner.os }}-buildx- 59 | 60 | - name: Login to Docker Hub 61 | if: github.event_name != 'pull_request' && (env.DOCKERFILE_CHANGED == '1' || env.DOCKER_IMAGE_EXISTS == '0' || github.ref == 'refs/heads/main') 62 | uses: docker/login-action@v1 63 | with: 64 | username: ${{ secrets.DOCKERHUB_USERNAME }} 65 | password: ${{ secrets.DOCKERHUB_TOKEN }} 66 | 67 | - name: Build and push 68 | if: env.DOCKERFILE_CHANGED == '1' || env.DOCKER_IMAGE_EXISTS == '0' || github.ref == 'refs/heads/main' 69 | id: docker_build 70 | uses: docker/build-push-action@v2 71 | with: 72 | context: . 73 | file: ./Dockerfile 74 | platforms: linux/amd64 75 | push: ${{ github.event_name != 'pull_request' }} 76 | tags: ${{ steps.docker_meta.outputs.tags }} 77 | labels: ${{ steps.docker_meta.outputs.labels }} 78 | cache-from: type=local,src=/tmp/.buildx-cache 79 | cache-to: type=local,dest=/tmp/.buildx-cache 80 | 81 | - name: Image digest 82 | if: env.DOCKERFILE_CHANGED == '1' || env.DOCKER_IMAGE_EXISTS == '0' || github.ref == 'refs/heads/main' 83 | run: echo ${{ steps.docker_build.outputs.digest }} 84 | 85 | # This job runs all the LibSV builds, tests, and checks 86 | libsv: 87 | needs: [docker] 88 | runs-on: ubuntu-latest 89 | steps: 90 | - name: Checkout 91 | uses: actions/checkout@v2 92 | 93 | - name: Docker meta 94 | id: docker_meta 95 | uses: docker/metadata-action@v3 96 | with: 97 | images: bensampson5/libsv 98 | 99 | - name: Setup GitHub environment 100 | run: echo "DOCKER_RUN=docker run --rm -v $(pwd):/code bensampson5/libsv:${{ steps.docker_meta.outputs.version }}" >> $GITHUB_ENV; 101 | 102 | - name: Pull docker image 103 | run: docker pull bensampson5/libsv:${{ steps.docker_meta.outputs.version }} 104 | 105 | - name: Build and run tests 106 | run: ${{ env.DOCKER_RUN }} /code/tools/precommit.py --test 107 | 108 | - name: Check format 109 | run: ${{ env.DOCKER_RUN }} /code/tools/precommit.py --check-format 110 | 111 | - name: Lint 112 | run: ${{ env.DOCKER_RUN }} /code/tools/precommit.py --lint 113 | 114 | - name: Build docs 115 | run: ${{ env.DOCKER_RUN }} /code/tools/precommit.py --docs 116 | 117 | - name: Build package 118 | run: ${{ env.DOCKER_RUN }} /code/tools/precommit.py --build-package 119 | 120 | - name: Publish package 121 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 122 | uses: pypa/gh-action-pypi-publish@release/v1 123 | with: 124 | user: __token__ 125 | password: ${{ secrets.PYPI_API_TOKEN }} 126 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. image:: _static/libsv_logo.svg 2 | :align: center 3 | :height: 200 4 | :alt: LibSV 5 | 6 | ------------------------------------------------------------------------------------------------------------------------ 7 | 8 | Welcome to LibSV's documentation! 9 | ================================= 10 | 11 | LibSV is an open source, parameterized SystemVerilog digital hardware IP library. 12 | While similar libraries may already exist, LibSV is unique in that it takes advantage 13 | of open-source, state-of-the-art development best practices and tools from across the 14 | software and digital design community, including: 15 | 16 | * Trivial installation. `LibSV is hosted on PyPI `_ and can easily be installed using 17 | `pip `_ or whichever Python package manager of your choice. 18 | * Easy-to-use. Simply add ```include "libsv///.sv"`` to where you want to use a LibSV module and then add the 19 | ``site-packages/`` folder, where LibSV was installed, to the include path when building your project. 20 | * Automated testbenches, written in Python, that use `pytest `_ to run 21 | `Cocotb `_ + `Verilator `_ under the hood for 22 | simple and fast logic simulation 23 | * All testbenches output waveform files in FST format for viewing with `GTKWave `_ 24 | * `Extensive documention `_ using `Sphinx `_ 25 | * Automated formatting and lint checks using `Verible `_ 26 | * `Continuous integration (CI) workflows `_ integrated with 27 | `Docker `_ 28 | * `LibSV Docker images `_ published to 29 | `Docker Hub `_ 30 | 31 | Getting Started 32 | --------------- 33 | 34 | LibSV is very easy to use. First, install the ``libsv`` package from PyPI: 35 | 36 | .. code-block:: bash 37 | 38 | pip install libsv 39 | 40 | We recommend using a Python virtual environment so that the installation is project-specific and 41 | isolated from the rest of your system. 42 | 43 | Then add the ``site-packages/`` folder, where LibSV was just installed, to your include path when building your 44 | project so that your design tools can find LibSV. 45 | 46 | Finally, at the top of your design file where you want to use LibSV modules, for each module you want to use, add: 47 | 48 | .. code-block:: SystemVerilog 49 | 50 | `include "libsv///.sv" 51 | 52 | Running Testbenches 53 | ------------------- 54 | 55 | Running the LibSV testbenches require `Cocotb `_, 56 | `Verilator `_, and a number of other dependencies to be installed. 57 | Instead of trying to install everything manually on your machine, the easier and recommended way to run the 58 | LibSV testbenches is to use the pre-built 59 | `LibSV Docker images on Docker Hub `__ that have the 60 | complete set of LibSV developer tools already installed. 61 | 62 | To use a LibSV Docker image, first you’ll need to install `Docker `__, 63 | if you don’t already have it. 64 | 65 | Next, pull the latest LibSV Docker image: 66 | 67 | .. code-block:: bash 68 | 69 | docker build --pull -f Dockerfile.dev \ 70 | --build-arg UID=$(id -u) \ 71 | --build-arg GID=$(id -g) \ 72 | -t libsv . 73 | 74 | Then, start a new Docker container using the LibSV image and mount the project folder to the container: 75 | 76 | .. code-block:: bash 77 | 78 | docker run --rm -it -v $(pwd):/code libsv 79 | 80 | Finally, within the Docker container, run ``pytest``: 81 | 82 | .. code-block:: bash 83 | 84 | pytest 85 | 86 | This will run all the LibSV testbenches for the entire library (*Warning: This may take a while!*). 87 | 88 | Instead, to list all the available LibSV testbenches, run: 89 | 90 | .. code-block:: bash 91 | 92 | pytest --co 93 | 94 | Then, you can run an individual or subset of testbenches using the ``-k`` flag which will only run tests which 95 | match the given substring expression: 96 | 97 | .. code-block:: bash 98 | 99 | pytest -k EXPRESSION 100 | 101 | Each testbench generates an associated ``.fst`` waveform file that is written to the ``build/`` directory and can be 102 | viewed using `GTKWave `_. 103 | 104 | Bugs/Feature Requests 105 | --------------------- 106 | 107 | Please use `LibSV's GitHub issue tracker `_ to submit bugs or request features. 108 | 109 | Contributing 110 | ------------ 111 | 112 | Contributions are much welcomed and appreciated! Take a look at the :doc:`contributing` page to get started. 113 | 114 | License 115 | ------- 116 | 117 | Distributed under the terms of the `MIT `_ license, LibSV is free 118 | and open source software. 119 | 120 | .. toctree:: 121 | :maxdepth: 1 122 | :caption: Table of Contents: 123 | 124 | modules 125 | contributing 126 | license 127 | useful_links 128 | contact 129 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://raw.githubusercontent.com/bensampson5/libsv/main/docs/source/_static/libsv_logo.svg 2 | :align: center 3 | :height: 150 4 | :alt: LibSV 5 | 6 | ------------------------------------------------------------------------------------------------------------------------ 7 | 8 | .. image:: https://img.shields.io/pypi/v/libsv 9 | :target: https://pypi.org/project/libsv/ 10 | :alt: PyPI 11 | 12 | .. image:: https://github.com/bensampson5/libsv/actions/workflows/ci.yml/badge.svg 13 | :target: https://github.com/bensampson5/libsv/actions/workflows/ci.yml 14 | 15 | .. image:: https://readthedocs.org/projects/libsv/badge/?version=latest 16 | :target: https://libsv.readthedocs.io/en/latest/?badge=latest 17 | :alt: Documentation Status 18 | 19 | Welcome to LibSV! `Click here to go to LibSV’s 20 | documentation `_. 21 | 22 | LibSV is an open source, parameterized SystemVerilog digital hardware IP library. 23 | While similar libraries may already exist, LibSV is unique in that it takes advantage 24 | of open-source, state-of-the-art development best practices and tools from across the 25 | software and digital design community, including: 26 | 27 | * Trivial installation. `LibSV is hosted on PyPI `_ and can easily be installed using 28 | `pip `_ or whichever Python package manager of your choice. 29 | * Easy-to-use. Simply add ```include "libsv///.sv"`` to where you want to use a LibSV module and then add the 30 | ``site-packages/`` folder, where LibSV was installed, to the include path when building your project. 31 | * Automated testbenches, written in Python, that use `pytest `_ to run 32 | `Cocotb `_ + `Verilator `_ under the hood for 33 | simple and fast logic simulation 34 | * All testbenches output waveform files in FST format for viewing with `GTKWave `_ 35 | * `Extensive documention `_ using `Sphinx `_ 36 | * Automated formatting and lint checks using `Verible `_ 37 | * `Continuous integration (CI) workflows `_ integrated with 38 | `Docker `_ 39 | * `LibSV Docker images `_ published to 40 | `Docker Hub `_ 41 | 42 | Getting Started 43 | --------------- 44 | 45 | LibSV is very easy to use. First, install the ``libsv`` package from PyPI: 46 | 47 | .. code-block:: bash 48 | 49 | pip install libsv 50 | 51 | We recommend using a Python virtual environment so that the installation is project-specific and 52 | isolated from the rest of your system. 53 | 54 | Then add the ``site-packages/`` folder, where LibSV was just installed, to your include path when building your 55 | project so that your design tools can find LibSV. 56 | 57 | Finally, at the top of your design file where you want to use LibSV modules, for each module you want to use, add: 58 | 59 | .. code-block:: SystemVerilog 60 | 61 | `include "libsv///.sv" 62 | 63 | Running Testbenches 64 | ------------------- 65 | 66 | Running the LibSV testbenches require `Cocotb `_, 67 | `Verilator `_, and a number of other dependencies to be installed. 68 | Instead of trying to install everything manually on your machine, the easier and recommended way to run the 69 | LibSV testbenches is to use the pre-built 70 | `LibSV Docker images on Docker Hub `__ that have the 71 | complete set of LibSV developer tools already installed. 72 | 73 | To use a LibSV Docker image, first you’ll need to install `Docker `__, 74 | if you don’t already have it. 75 | 76 | Next, pull the latest LibSV Docker image: 77 | 78 | .. code-block:: bash 79 | 80 | docker build --pull -f Dockerfile.dev \ 81 | --build-arg UID=$(id -u) \ 82 | --build-arg GID=$(id -g) \ 83 | -t libsv . 84 | 85 | Then, start a new Docker container using the LibSV image and mount the project folder to the container: 86 | 87 | .. code-block:: bash 88 | 89 | docker run --rm -it -v $(pwd):/code libsv 90 | 91 | Finally, within the Docker container, run ``pytest``: 92 | 93 | .. code-block:: bash 94 | 95 | pytest 96 | 97 | This will run all the LibSV testbenches for the entire library (*Warning: This may take a while!*). 98 | 99 | Instead, to list all the available LibSV testbenches, run: 100 | 101 | .. code-block:: bash 102 | 103 | pytest --co 104 | 105 | Then, you can run an individual or subset of testbenches using the ``-k`` flag which will only run tests which 106 | match the given substring expression: 107 | 108 | .. code-block:: bash 109 | 110 | pytest -k EXPRESSION 111 | 112 | Each testbench generates an associated ``.fst`` waveform file that is written to the ``build/`` directory and can be 113 | viewed using `GTKWave `_. 114 | 115 | Bugs/Feature Requests 116 | --------------------- 117 | 118 | Please use `LibSV's GitHub issue tracker `_ to submit bugs or request features. 119 | 120 | Contributing 121 | ------------ 122 | 123 | Contributions are much welcomed and appreciated! Take a look at the 124 | `Contributing `_ page to get started. 125 | 126 | License 127 | ------- 128 | 129 | Distributed under the terms of the `MIT `_ license, LibSV is free 130 | and open source software. 131 | -------------------------------------------------------------------------------- /.verible-verilog-format.yaml: -------------------------------------------------------------------------------- 1 | # Yaml configuration file for verible-verilog-format. 2 | # Generated from `verible-verilog-format --helpfull` output and 3 | # then formatted into a yaml file for convenience. 4 | 5 | # assignment_statement_alignment - Format various assignments: {align,flush-left,preserve,infer}; 6 | # default: infer; 7 | assignment_statement_alignment: align 8 | 9 | # case_items_alignment - Format case items: {align,flush-left,preserve,infer}; default: infer; 10 | case_items_alignment: align 11 | 12 | # class_member_variables_alignment - Format class member variables: 13 | # {align,flush-left,preserve,infer}; default: infer; 14 | class_member_variable_alignment: align 15 | 16 | # column_limit (Target line length limit to stay under when formatting.); default: 100; 17 | column_limit: 100 18 | 19 | # compact_indexing_and_selections - Use compact binary expressions inside indexing / bit selection 20 | # operators; default: true; 21 | compact_indexing_and_selections: true 22 | 23 | # distribution_items_alignment Align distribution items: {align,flush-left,preserve,infer}); 24 | # default: infer; 25 | distribution_items_alignment: align 26 | 27 | # enum_assignment_statement_alignment (Format assignments with enums: {align,flush-left,preserve,infer}); 28 | # default: infer; 29 | enum_assignment_statement_alignment: align 30 | 31 | # expand_coverpoints (If true, always expand coverpoints.); default: false; 32 | expand_coverpoints: false 33 | 34 | # failsafe_success - If true, always exit with 0 status, even if there were input errors or 35 | # internal errors. In all error conditions, the original text is always preserved. This is 36 | # useful in deploying services where fail-safe behaviors should be considered a success.; 37 | # default: true; 38 | failsafe_success: true 39 | 40 | # formal_parameters_alignment - Format formal parameters: {align,flush-left,preserve,infer}; 41 | # default: infer; 42 | formal_parameters_alignment: align 43 | 44 | # formal_parameters_indentation - Indent formal parameters: {indent,wrap}; default: wrap; 45 | formal_parameters_indentation: wrap 46 | 47 | # indentation_spaces (Each indentation level adds this many spaces.); default: 2; 48 | indentation_spaces: 4 49 | 50 | # line_break_penalty (Penalty added to solution for each introduced line break.); default: 2; 51 | line_break_penalty: 2 52 | 53 | # max_search_states - Limits the number of search states explored during line wrap 54 | # optimization.; default: 100000; 55 | max_search_states: 100000 56 | 57 | # module_net_variable_alignment (Format net/variable declarations: {align,flush-left,preserve,infer}); 58 | # default: infer; 59 | module_net_variable_alignment: align 60 | 61 | # named_parameter_alignment - Format named actual parameters: {align,flush-left,preserve,infer}; 62 | # default: infer; 63 | named_parameter_alignment: align 64 | 65 | # named_parameter_indentation - Indent named parameter assignments: {indent,wrap}; 66 | # default: wrap; 67 | named_parameter_indentation: wrap 68 | 69 | # named_port_alignment - Format named port connections: {align,flush-left,preserve,infer}; 70 | # default: infer; 71 | named_port_alignment: align 72 | 73 | # named_port_indentation - Indent named port connections: {indent,wrap}; default: wrap; 74 | named_port_indentation: wrap 75 | 76 | # over_column_limit_penalty (For penalty minimization, this represents the baseline penalty value 77 | # of exceeding the column limit. Additional penalty of 1 is incurred for each character over this 78 | # limit); default: 100; 79 | over_column_limit_penalty: 100 80 | 81 | # port_declarations_alignment - Format port declarations: {align,flush-left,preserve,infer}; 82 | # default: infer; 83 | port_declarations_alignment: align 84 | 85 | # port_declarations_indentation - Indent port declarations: {indent,wrap}; default: wrap; 86 | port_declarations_indentation: wrap 87 | 88 | # port_declarations_right_align_packed_dimensions (If true, packed dimensions in contexts 89 | # with enabled alignment are aligned to the right.); default: false; 90 | port_declarations_right_align_packed_dimensions: false 91 | 92 | # port_declarations_right_align_unpacked_dimensions (If true, unpacked dimensions in contexts 93 | # with enabled alignment are aligned to the right.); default: false; 94 | port_declarations_right_align_unpacked_dimensions: false 95 | 96 | # show_equally_optimal_wrappings - If true, print when multiple optimal solutions are found 97 | # (stderr), but continue to operate normally.; default: false; 98 | show_equally_optimal_wrappings: false 99 | 100 | # show_inter_token_info - If true, along with show_token_partition_tree, include inter-token 101 | # information such as spacing and break penalties.; default: false; 102 | show_inter_token_info: false 103 | 104 | # show_largest_token_partitions - If > 0, print token partitioning and then exit without 105 | # formatting output.; default: 0; 106 | show_largest_token_partitions: 0 107 | 108 | # show_token_partition_tree - If true, print diagnostics after token partitioning and then 109 | # exit without formatting output.; default: false; 110 | show_token_partition_tree: false 111 | 112 | # struct_union_members_alignment (Format struct/union members: {align,flush-left,preserve,infer}); 113 | # default: infer; 114 | struct_union_members_alignment: align 115 | 116 | # try_wrap_long_lines - If true, let the formatter attempt to optimize line wrapping decisions 117 | # where wrapping is needed, else leave them unformatted. This is a short-term measure to reduce 118 | # risk-of-harm.; default: false; 119 | try_wrap_long_lines: false 120 | 121 | # verify_convergence - If true, and not incrementally formatting with --lines, verify that 122 | # re-formatting the formatted output yields no further changes, i.e. formatting is convergent.; 123 | # default: true; 124 | verify_convergence: true 125 | 126 | # wrap_spaces (Each wrap level adds this many spaces. This applies when the first element after 127 | # an open-group section is wrapped. Otherwise, the indentation level is set to the column position 128 | # of the open-group operator.); default: 4; 129 | wrap_spaces: 4 -------------------------------------------------------------------------------- /docs/source/_static/libsv_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 28 | 32 | 36 | 40 | 44 | 47 | 54 | 55 | 56 | 75 | 77 | 78 | 80 | image/svg+xml 81 | 83 | 84 | 85 | 86 | 87 | 92 | 139 | LibSV 152 | 155 | 160 | 165 | 170 | 176 | 188 | 193 | 194 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /tools/precommit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import click 4 | from pathlib import Path 5 | import shutil 6 | import subprocess 7 | import yaml 8 | from difflib import unified_diff 9 | import colorama 10 | from colorama import Fore, Style 11 | 12 | PROJECT_ROOT = Path("/code") 13 | SRC_DIR = PROJECT_ROOT / "libsv" 14 | DOCS_DIR = PROJECT_ROOT / "docs" 15 | FLUSH = True 16 | 17 | 18 | def in_docker(): 19 | """Returns: True if running in a docker container, else False""" 20 | return Path("/.dockerenv").exists() 21 | 22 | 23 | def run(cmd, cwd=PROJECT_ROOT, check_exit=True, print_output=True): 24 | p = subprocess.Popen( 25 | cmd, 26 | cwd=cwd, 27 | stdout=subprocess.PIPE, 28 | stderr=subprocess.STDOUT, 29 | universal_newlines=True, 30 | ) 31 | 32 | while True: 33 | stdout = p.stdout.readline() 34 | if stdout == "" and p.poll() is not None: 35 | break 36 | if stdout: 37 | if print_output: 38 | print(stdout, end="", flush=FLUSH) 39 | 40 | if check_exit and p.returncode != 0: 41 | raise subprocess.CalledProcessError(p.returncode, " ".join(cmd)) 42 | 43 | 44 | def run_test(): 45 | """Run tests""" 46 | cmd = ["pytest"] 47 | run(cmd) 48 | 49 | 50 | def find_sv_files(dir=SRC_DIR): 51 | # Add all SystemVerilog files within project 52 | hdl_search_patterns = ["**/*.sv"] 53 | hdl_files = [] 54 | for sp in hdl_search_patterns: 55 | hdl_files += dir.glob(sp) 56 | return hdl_files 57 | 58 | 59 | def run_check_format(): 60 | """Check formatting in all files""" 61 | run_check_format_hdl() 62 | run_check_format_python() 63 | 64 | 65 | def run_check_format_hdl(): 66 | """Check formatting in HDL files""" 67 | 68 | print("\nChecking HDL formatting...\n", flush=FLUSH) 69 | 70 | hdl_files = find_sv_files() 71 | 72 | # Make a copy of original HDL code 73 | hdl_file_code_original = [] 74 | for hdl_file in hdl_files: 75 | with open(hdl_file, "r") as f: 76 | hdl_file_code_original.append(f.readlines()) 77 | 78 | # Run hdl formatter 79 | run_fix_format_hdl(print_output=False) 80 | 81 | # Make a copy of the formatted HDL code 82 | hdl_file_code_formatted = [] 83 | for hdl_file in hdl_files: 84 | with open(hdl_file, "r") as f: 85 | hdl_file_code_formatted.append(f.readlines()) 86 | 87 | # Do a diff and print diff output in pretty colors 88 | colorama.init() 89 | is_diff = False 90 | for i in range(len(hdl_file_code_original)): 91 | original = hdl_file_code_original[i] 92 | formatted = hdl_file_code_formatted[i] 93 | fname = str(hdl_files[i]) 94 | diff = list( 95 | unified_diff(original, formatted, fromfile=fname, tofile=fname, n=5) 96 | ) 97 | if diff: 98 | is_diff = True 99 | print_unified_diff_in_color(diff) 100 | colorama.deinit() 101 | 102 | # Restore original HDL code 103 | for i in range(len(hdl_files)): 104 | with open(hdl_files[i], "w") as f: 105 | f.write("".join(hdl_file_code_original[i])) 106 | 107 | if is_diff: 108 | raise RuntimeError("HDL format check failed") 109 | 110 | 111 | def print_unified_diff_in_color(diff): 112 | for line in diff: 113 | if line.startswith("---") or line.startswith("+++"): 114 | line = f"{Style.BRIGHT}{line}{Style.RESET_ALL}" 115 | elif line.startswith("@@"): 116 | line = f"{Fore.CYAN}{line}{Style.RESET_ALL}" 117 | elif line.startswith("-"): 118 | line = f"{Fore.RED}{line}{Style.RESET_ALL}" 119 | elif line.startswith("+"): 120 | line = f"{Fore.GREEN}{line}{Style.RESET_ALL}" 121 | print(line, end="", flush=FLUSH) 122 | print() 123 | 124 | 125 | def run_check_format_python(): 126 | """Check formatting in Python files""" 127 | print("\nChecking Python formatting...\n", flush=FLUSH) 128 | cmd = ["black", "--diff", "--check", "--color", "."] 129 | run(cmd) 130 | 131 | 132 | def run_fix_format(): 133 | """Fix formatting in all files""" 134 | print("\nFixing HDL formatting...\n", flush=FLUSH) 135 | run_fix_format_hdl() 136 | run_fix_format_python() 137 | 138 | 139 | def run_fix_format_hdl(print_output=True): 140 | """Fix formatting in HDL files""" 141 | 142 | # Use --inplace flag to overwrite existing files 143 | cmd = ["verible-verilog-format", "--inplace"] 144 | 145 | # Add options from .verible-verilog-format.yaml if specified 146 | verible_verilog_format_yaml = PROJECT_ROOT / ".verible-verilog-format.yaml" 147 | yaml_data = None 148 | if verible_verilog_format_yaml.exists(): 149 | with open(verible_verilog_format_yaml, "r") as f: 150 | yaml_data = yaml.safe_load(f.read()) 151 | 152 | format_args = [] 153 | for k, v in yaml_data.items(): 154 | format_args.append(f"--{k}={v}") 155 | 156 | cmd += format_args 157 | 158 | hdl_files = find_sv_files() 159 | cmd += [str(f) for f in hdl_files] 160 | 161 | run(cmd, print_output=print_output) 162 | 163 | 164 | def run_fix_format_python(): 165 | """Fix formatting in Python files""" 166 | 167 | print("\nFixing Python formatting...\n", flush=FLUSH) 168 | cmd = ["black", "."] 169 | run(cmd) 170 | 171 | 172 | def run_lint(): 173 | run_lint_hdl() 174 | run_lint_python() 175 | 176 | 177 | def run_lint_hdl(): 178 | """Run HDL linter""" 179 | 180 | print("\nLinting HDL...\n", flush=FLUSH) 181 | 182 | cmd = ["verible-verilog-lint"] 183 | 184 | hdl_files = find_sv_files() 185 | cmd += [str(f) for f in hdl_files] 186 | 187 | run(cmd) 188 | 189 | 190 | def run_lint_python(): 191 | """Run Python linter""" 192 | 193 | print("\nLinting Python...\n", flush=FLUSH) 194 | cmd = ["flake8", "."] 195 | run(cmd) 196 | 197 | 198 | def run_docs(): 199 | """Make documentation""" 200 | 201 | DOCS_BUILD_DIR = DOCS_DIR / "build" 202 | 203 | # Delete entire docs build directory if it exists 204 | if DOCS_BUILD_DIR.exists(): 205 | shutil.rmtree(DOCS_BUILD_DIR) 206 | 207 | # Create new docs build directory 208 | DOCS_BUILD_DIR.mkdir() 209 | 210 | cmd = ["make", "html"] 211 | run(cmd, cwd=DOCS_DIR) 212 | 213 | 214 | def run_build_package(): 215 | """Builds PyPI package""" 216 | 217 | cmd = ["poetry", "build"] 218 | run(cmd) 219 | 220 | 221 | @click.command() 222 | @click.option("--test", is_flag=True, help="Run tests") 223 | @click.option("--check-format", is_flag=True, help="Check formatting") 224 | @click.option("--fix-format", is_flag=True, help="Fix formatting") 225 | @click.option("--lint", is_flag=True, help="Run linting") 226 | @click.option("--docs", is_flag=True, help="Build documentation") 227 | @click.option("--build-package", is_flag=True, help="Build package") 228 | def precommit(test, check_format, fix_format, lint, docs, build_package): 229 | """Precommit tool for LibSV. If no options are provided, this 230 | tool will run all precommit steps except for --fix-format. If one or more 231 | options are specified then only those precommit steps will be run.""" 232 | 233 | # if no flags are provided, then run default configuration 234 | if not any([test, check_format, fix_format, lint, docs, build_package]): 235 | test = True 236 | check_format = True 237 | fix_format = False 238 | lint = True 239 | docs = True 240 | build_package = True 241 | 242 | # Check if in docker container 243 | if not in_docker(): 244 | raise OSError( 245 | "Not in a docker container. This script must be run from within a docker" 246 | " container. See README.md for instructions." 247 | ) 248 | else: 249 | 250 | # Resolve project root directory before proceeding 251 | if not PROJECT_ROOT.is_dir(): 252 | raise FileNotFoundError( 253 | f"Cannot find project root directory: {PROJECT_ROOT}" 254 | ) 255 | 256 | if test: 257 | print("\nRunning tests...\n", flush=FLUSH) 258 | run_test() 259 | 260 | if check_format: 261 | print("\nChecking formatting...\n", flush=FLUSH) 262 | run_check_format() 263 | 264 | if fix_format: 265 | print("\nFixing formatting...\n", flush=FLUSH) 266 | run_fix_format() 267 | 268 | if lint: 269 | print("\nLinting...\n", flush=FLUSH) 270 | run_lint() 271 | 272 | if docs: 273 | print("\nBuilding documentation...\n", flush=FLUSH) 274 | run_docs() 275 | 276 | if build_package: 277 | print("\nBuilding package...\n") 278 | run_build_package() 279 | 280 | 281 | if __name__ == "__main__": 282 | precommit() 283 | -------------------------------------------------------------------------------- /docs/source/contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Contributions are much welcomed and appreciated! 5 | 6 | .. contents:: 7 | :depth: 2 8 | :backlinks: none 9 | 10 | 11 | .. _submitfeedback: 12 | 13 | Feature Requests and Feedback 14 | ----------------------------- 15 | 16 | We'd like to hear about your propositions and suggestions. Feel free to 17 | `submit them as issues `_ and: 18 | 19 | * Explain in detail how they should work. 20 | * Keep the scope as narrow as possible. This will make it easier to implement. 21 | 22 | Or create/join a discussion in the `LibSV discussion page `_. 23 | 24 | 25 | .. _report_bugs: 26 | 27 | Report Bugs 28 | ----------- 29 | 30 | Report bugs for LibSV in the `issue tracker `_. 31 | 32 | If you are reporting a bug, please include: 33 | 34 | * Your operating system name and version. 35 | * Any details about your local setup that might be helpful in troubleshooting, 36 | specifically the Python interpreter version, installed libraries & packages 37 | (i.e. cocotb, verilator, pytest), and whether or not you're using a LibSV Docker 38 | image. 39 | * Detailed steps to reproduce the bug. 40 | 41 | If you can write a demonstration test that currently fails but should pass 42 | (xfail), that is a very useful commit to make as well, even if you cannot 43 | fix the bug itself. 44 | 45 | 46 | .. _setting_up_developers_environment: 47 | 48 | Setting Up the LibSV Developer's Environment 49 | -------------------------------------------- 50 | 51 | For anything beyond feature requests, reporting bugs, or very simple fixes, it is *strongly* recommended to setup the LibSV 52 | developer's environment. Especially if you plan to write any SystemVerilog source code, write and/or run Python testbenches, 53 | build the html documentation from source, or modify project workflows/tools/configurations. 54 | 55 | LibSV provides pre-built Docker images, specifically for this purpose, which are available on 56 | `LibSV's Docker Hub page `_. If you don't already have 57 | `Docker `_ installed on your machine, you will need to install it by following the instructions 58 | `here `_. 59 | 60 | Once you have Docker installed you can build and pull the LibSV Docker image by running: 61 | 62 | .. code-block:: bash 63 | 64 | docker build --pull -f Dockerfile.dev \ 65 | --build-arg UID=$(id -u) \ 66 | --build-arg GID=$(id -g) \ 67 | -t libsv . 68 | 69 | By default, this will pull the LibSV Docker image associated with the ``main`` branch, however you can pull a different branch's 70 | LibSV Docker image (or any tagged version) by adding the ``TAG`` build argument: 71 | 72 | .. code-block:: bash 73 | 74 | docker build --pull -f Dockerfile.dev \ 75 | --build-arg UID=$(id -u) \ 76 | --build-arg GID=$(id -g) \ 77 | --build-arg TAG=TAG_NAME \ 78 | -t libsv . 79 | 80 | Then, start a new Docker container using the LibSV image and mount the project folder to the container: 81 | 82 | .. code-block:: bash 83 | 84 | docker run --rm -it -v $(pwd):/code libsv 85 | 86 | 87 | .. _fix_bugs: 88 | 89 | Fix Bugs 90 | -------- 91 | 92 | Look through the `issue tracker for bugs `_. 93 | 94 | If you're interested in fixing a bug but are unsure about how you can fix it, leave a comment in the specific issue so that 95 | developers can help you find a solution. To indicate that you are going to work on a particular issue, add a comment to that 96 | effect in the issue. 97 | 98 | 99 | .. _implement_features: 100 | 101 | Implement Features 102 | ------------------ 103 | 104 | Look through the `issue tracker for enhancements `_. 105 | 106 | If you're interested in implementing a feature but are unsure about how you can do it, leave a comment in the specific issue 107 | so that developers can help you find a solution. To indicate that you are going to work on a particular feature, add a comment 108 | to that effect in the issue. 109 | 110 | 111 | .. _write_documentation: 112 | 113 | Write Documentation 114 | ------------------- 115 | 116 | LibSV could always use more documentation. What exactly is needed? 117 | 118 | * Online documentation 119 | * Code comments (including both SystemVerilog source files and python testbenches) 120 | 121 | You can also edit documentation files directly in the GitHub web interface, 122 | without using a local copy. This can be convenient for small fixes. 123 | 124 | .. note:: 125 | Build the documentation locally by running the following bash command in a LibSV Docker container 126 | from the top-level project directory: 127 | 128 | .. code-block:: bash 129 | 130 | ./tools/precommit.py --docs 131 | 132 | The built documentation should be available in ``docs/build/html/``. 133 | 134 | 135 | .. _pull_requests: 136 | 137 | Preparing Pull Requests 138 | ----------------------- 139 | 140 | Pull requests inform the project's core developers about the 141 | changes you want to review and merge. Pull requests are stored on 142 | `GitHub servers `_. 143 | Once you send a pull request, we can discuss its potential modifications and 144 | even add more commits to it later on. There's an excellent tutorial on how Pull 145 | Requests work in the 146 | `GitHub Help Center `_. 147 | 148 | To prepare a pull request: 149 | 150 | #. Fork the repository. 151 | #. As you make changes and before you commit, run the 152 | `precommit `_ script by 153 | invoking ``./tools/precommit.py`` from the top-level project directory when you're running 154 | in a LibSV Docker container. To see all precommit script options run: ``./tools/precommit.py --help``. 155 | #. Testbenches are run using either ``pytest`` or ``./tools/precommit.py --test``. This will run all 156 | LibSV testbenches. To run only a single testbench, we recommend using ``pytest`` with the ``-k`` flag 157 | 158 | .. code-block:: bash 159 | 160 | pytest -k TESTBENCH_NAME 161 | 162 | #. If you are adding a new SystemVerilog module to the library, you must complete the following checklist: 163 | 164 | * The new SystemVerilog module should be a single ``.sv`` file with a single ``module`` inside that 165 | is added to the right directory within ``libsv/``. File naming convention is all lower-case and 166 | underscores (i.e. ``example_module.sv``). 167 | * The SystemVerilog module should have a corresponding Python testbench that has the same name as 168 | the SystemVerilog source file with a ``test_`` prefix. (i.e. ``test_example_module.sv``). Similarly 169 | to before, this testbench must be added to the right directory within ``tests/``. Take a look 170 | at `existing LibSV testbenches `_ for examples 171 | on how to write a testbench for LibSV. 172 | * Write a testbench that exercises the SystemVerilog module and checks whether the module meets the 173 | functional specifications. 174 | * The SystemVerilog module should have a corresponding ``.rst`` documentation file that has the same 175 | name as the SystemVerilog source file (i.e. ``example_module.rst``). Once again, this documentation 176 | file must be added to the right directory within ``docs/source/``. Take a look at 177 | `existing LibSV docs `_ for examples on 178 | how write documentation for LibSV. 179 | 180 | #. Unless your change is a trivial or a documentation fix (e.g., a typo or reword of a small section), 181 | please add yourself as a contributor to the ``AUTHORS`` file, in alphabetical order, so we can credit 182 | you for your work! 183 | #. Commit and push once the precommit script passes and you are happy with your changes: 184 | 185 | .. code-block:: bash 186 | 187 | git commit -a -m "" 188 | git push -u 189 | 190 | #. Finally, submit a pull request through GitHub using this data: 191 | 192 | .. code-block:: text 193 | 194 | head repository: YOUR_GITHUB_USERNAME/libsv 195 | compare: your-branch-name 196 | 197 | base repository: bensampson5/libsv 198 | base: main 199 | 200 | 201 | .. _closing_issues: 202 | 203 | Closing Issues 204 | -------------- 205 | 206 | When a pull request is submitted to fix an issue, add text like ``Closes #ABC`` to the PR description and/or commits (where ``ABC`` is the 207 | issue number). See the 208 | `GitHub docs `_ 209 | for more information. -------------------------------------------------------------------------------- /tests/coders/test_encoder_8b10b.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.clock import Clock 3 | from cocotb.triggers import FallingEdge 4 | from typing import Tuple 5 | import re 6 | from utils import pytest_cocotb_run_test 7 | 8 | 9 | def test_8b10b_encoder(pytestconfig): 10 | """Pytest fixture for 8B/10B Encoder test""" 11 | pytest_cocotb_run_test(pytestconfig, __name__) 12 | 13 | 14 | def encode_5b6b(i_5b: int, i_disp: int, i_ctrl: bool) -> Tuple[int, int]: 15 | """5b/6b encoder. 16 | 17 | Args: 18 | i_5b (int): input 5b value 19 | i_disp (int): input running disparity (-1 or 1) 20 | i_ctrl (bool): intput control symbol flag 21 | 22 | Returns: 23 | tuple[int, int]: tuple of output 6b value and output running disparity 24 | """ 25 | 26 | # lookup table maps EDBCA to iedcba for RD = -1 27 | lut_5b6b_rdm1 = { 28 | # EDCBA: iedcba 29 | 0b00000: 0b111001, # D.00- 30 | 0b00001: 0b101110, # D.01- 31 | 0b00010: 0b101101, # D.02- 32 | 0b00011: 0b100011, # D.03- 33 | 0b00100: 0b101011, # D.04- 34 | 0b00101: 0b100101, # D.05- 35 | 0b00110: 0b100110, # D.06- 36 | 0b00111: 0b000111, # D.07- 37 | 0b01000: 0b100111, # D.08- 38 | 0b01001: 0b101001, # D.09- 39 | 0b01010: 0b101010, # D.10- 40 | 0b01011: 0b001011, # D.11- 41 | 0b01100: 0b101100, # D.12- 42 | 0b01101: 0b001101, # D.13- 43 | 0b01110: 0b001110, # D.14- 44 | 0b01111: 0b111010, # D.15- 45 | 0b10000: 0b110110, # D.16- 46 | 0b10001: 0b110001, # D.17- 47 | 0b10010: 0b110010, # D.18- 48 | 0b10011: 0b010011, # D.19- 49 | 0b10100: 0b110100, # D.20- 50 | 0b10101: 0b010101, # D.21- 51 | 0b10110: 0b010110, # D.22- 52 | 0b10111: 0b010111, # D.23- 53 | 0b11000: 0b110011, # D.24- 54 | 0b11001: 0b011001, # D.25- 55 | 0b11010: 0b011010, # D.26- 56 | 0b11011: 0b011011, # D.27- 57 | 0b11100: 0b011100, # D.28- 58 | 0b11101: 0b011101, # D.29- 59 | 0b11110: 0b011110, # D.30- 60 | 0b11111: 0b110101, # D.31- 61 | } 62 | 63 | o_6b = lut_5b6b_rdm1[i_5b] # lookup 5b/6b RD- value 64 | 65 | # K.28 is unique from the rest of the table 66 | if i_ctrl and i_5b == 28: 67 | if i_disp == -1: 68 | o_6b = 0b111100 69 | else: 70 | o_6b = 0b000011 71 | 72 | elif i_disp == 1: # may have to look up RD+ value 73 | 74 | # check if RD+ value is different than RD- value 75 | if i_5b in [0, 1, 2, 4, 7, 8, 15, 16, 23, 24, 27, 29, 30, 31]: 76 | o_6b = (~o_6b) & 0x3F 77 | 78 | # update running disparity 79 | string_6b = format(o_6b, "06b") 80 | num_ones = string_6b.count("1") 81 | num_zeros = string_6b.count("0") 82 | o_disp = i_disp + num_ones - num_zeros 83 | 84 | # Check for invalid output running disparity 85 | try: 86 | assert o_disp == -1 or o_disp == 1 87 | except AssertionError as e: 88 | i_5b_str = format(i_5b, "#07b") 89 | o_6b_str = format(o_6b, "#08b") 90 | print( 91 | f"Error: Invalid output running disparity value\n" 92 | f" i_5b = {i_5b_str}, i_disp = {i_disp}, i_ctrl = {i_ctrl}\n" 93 | f" o_6b = {o_6b_str}, o_disp = {o_disp}\n" 94 | ) 95 | raise e 96 | 97 | return o_6b, o_disp 98 | 99 | 100 | def encode_3b4b( 101 | i_3b: int, i_disp: int, i_ctrl: bool, use_alternate: bool 102 | ) -> Tuple[int, int]: 103 | """3b/4b encoder. 104 | 105 | Args: 106 | i_3b (int): input 3b value 107 | i_disp (int): input running disparity (-1 or 1) 108 | i_ctrl (bool): input control symbol flag 109 | use_alternate (bool): Use alternate flag (for D.x.A7) 110 | 111 | Returns: 112 | tuple[int, int]: tuple of output 4b value and output running disparity 113 | """ 114 | 115 | # lookup table maps HGF to jhgf for RD = -1 (data) 116 | lut_3b4b_data_rdm1 = { 117 | # HGF: jhgf 118 | 0b000: 0b1101, # D.x.0- 119 | 0b001: 0b1001, # D.x.1- 120 | 0b010: 0b1010, # D.x.2- 121 | 0b011: 0b0011, # D.x.3- 122 | 0b100: 0b1011, # D.x.4- 123 | 0b101: 0b0101, # D.x.5- 124 | 0b110: 0b0110, # D.x.6- 125 | 0b111: 0b0111, # D.x.P7- 126 | } 127 | 128 | # lookup table maps HGF to jhgf for RD = -1 (control) 129 | lut_3b4b_control_rdm1 = { 130 | # HGF: jhgf 131 | 0b000: 0b1101, # K.x.0- 132 | 0b001: 0b0110, # K.x.1- 133 | 0b010: 0b0101, # K.x.2- 134 | 0b011: 0b0011, # K.x.3- 135 | 0b100: 0b1011, # K.x.4- 136 | 0b101: 0b1010, # K.x.5- 137 | 0b110: 0b1001, # K.x.6- 138 | 0b111: 0b1110, # K.x.7- 139 | } 140 | 141 | # lookup 3b/4b RD- value 142 | o_4b = None 143 | if i_ctrl: 144 | o_4b = lut_3b4b_control_rdm1[i_3b] 145 | else: 146 | if use_alternate and i_3b == 0b111: 147 | o_4b = 0b1110 # RD- value for D.x.A7 148 | else: 149 | o_4b = lut_3b4b_data_rdm1[i_3b] 150 | 151 | # Invert bits to get RD+ value if needed 152 | if i_disp == 1 and (i_ctrl or i_3b in [0, 3, 4, 7]): 153 | o_4b = (~o_4b) & 0xF 154 | 155 | # update running disparity 156 | string_4b = format(o_4b, "04b") 157 | num_ones = string_4b.count("1") 158 | num_zeros = string_4b.count("0") 159 | o_disp = i_disp + num_ones - num_zeros 160 | 161 | # Check for invalid output running disparity 162 | try: 163 | assert o_disp == -1 or o_disp == 1 164 | except AssertionError as e: 165 | i_3b_str = format(i_3b, "#05b") 166 | o_4b_str = format(o_4b, "#06b") 167 | print( 168 | f"Error: Invalid output running disparity value\n" 169 | f" i_3b = {i_3b_str}, i_disp = {i_disp}, i_ctrl = {i_ctrl}\n" 170 | f" use_alternate = {use_alternate}\n" 171 | f" o_4b = {o_4b_str}, o_disp = {o_disp}\n" 172 | ) 173 | raise e 174 | 175 | return o_4b, o_disp 176 | 177 | 178 | def encode_8b10b(i_8b: int, i_disp: int, i_ctrl: bool) -> Tuple[int, int, bool]: 179 | """8b/10b encoder. 180 | 181 | Args: 182 | i_8b (int): input 8b value 183 | i_disp (int): input running disparity (-1 or 1) 184 | i_ctrl (bool): input control symbol flag 185 | 186 | Returns: 187 | Tuple[int, int, bool]: Tuple containing output 10b value, output running 188 | disparity, and output code error flag 189 | """ 190 | 191 | i_5b = i_8b & 0x1F 192 | i_3b = (i_8b >> 5) & 0x7 193 | 194 | # Step 1: Lookup 5b/6b code 195 | o_6b, o_disp_6b = encode_5b6b(i_5b, i_disp, i_ctrl) 196 | 197 | # Step 2: Select alternate 3b/4b encoding to avoid run of 198 | # five consecutive 0s or 1s when combined with the preceding 199 | # 5b/6b code. D.x.A7 is used only when: 200 | # 1. RD = -1: for x = 17, 18, and 20 201 | # 2. RD = +1: for x = 11, 13, and 14 202 | use_alternate = (o_disp_6b == -1 and i_5b in [17, 18, 20]) or ( 203 | o_disp_6b == 1 and i_5b in [11, 13, 14] 204 | ) 205 | 206 | # Step 3: Lookup 3b/4b code 207 | o_4b, o_disp_4b = encode_3b4b(i_3b, o_disp_6b, i_ctrl, use_alternate) 208 | 209 | # Combine 6b and 4b codes to a 10b code 210 | o_10b = (o_4b << 6) | o_6b 211 | o_disp = o_disp_4b 212 | 213 | try: 214 | # Check that there isn't a string of 0's or 1's longer than 5 215 | o_10b_s = format(o_10b, "010b") 216 | assert not re.search("0{6,}|1{6,}", o_10b_s) 217 | except AssertionError as e: 218 | i_8b_str = format(i_8b, "#05b") 219 | o_10b_str = format(o_10b, "#012b") 220 | print( 221 | f"Error: 10b value has a string of 0's or 1's longer than 5\n" 222 | f" i_8b = {i_8b_str}, i_rd = {i_disp}, i_ctrl = {i_ctrl}\n" 223 | f" use_alternate = {use_alternate}\n" 224 | f" o_10b = {o_10b_str}, o_rd = {o_disp}\n" 225 | ) 226 | raise e 227 | 228 | # Look up illegal codes 229 | o_code_err = False 230 | legal_control_symbols = [ 231 | 0b00011100, # K.28.0 232 | 0b00111100, # K.28.1 233 | 0b01011100, # K.28.2 234 | 0b01111100, # K.28.3 235 | 0b10011100, # K.28.4 236 | 0b10111100, # K.28.5 237 | 0b11011100, # K.28.6 238 | 0b11111100, # K.28.7 239 | 0b11110111, # K.23.7 240 | 0b11111011, # K.27.7 241 | 0b11111101, # K.29.7 242 | 0b11111110, # K.30.7 243 | ] 244 | if i_ctrl and i_8b not in legal_control_symbols: 245 | o_code_err = True 246 | 247 | return o_10b, o_disp, o_code_err 248 | 249 | 250 | @cocotb.test() 251 | async def cocotb_test_encoder_8b10b(dut): 252 | """8B/10B Encoder test""" 253 | 254 | # Create clock 255 | cocotb.start_soon(Clock(dut.i_clk, 2).start()) 256 | 257 | # Assert reset and check output and 258 | # internal running disparity 259 | dut.i_reset_n.value = 0 260 | await FallingEdge(dut.i_clk) 261 | assert int(dut.o_10b) == 0 262 | assert int(dut.o_code_err) == 0 263 | assert int(dut.rd) == 0 264 | 265 | # Check that enable doesn't change outputs when disabled 266 | dut.i_reset_n.value = 1 # de-assert reset 267 | dut.i_en.value = 1 # enable 8b10b encoder 268 | dut.i_8b.value = 0 # D.00.0 269 | dut.i_ctrl.value = 0 # data symbol 270 | await FallingEdge(dut.i_clk) # 1 clock tick 271 | prev_o_10b = int(dut.o_10b) # capture 10b output 272 | prev_o_code_err = int(dut.o_code_err) # capture code error 273 | prev_rd = int(dut.rd) # capture running disparity 274 | dut.i_en.value = 0 # disable 8b10b encoder 275 | 276 | # Pick a value that after the first 8b value would change both 277 | # the output 10b value, internal running disparity, and generate 278 | # a code error 279 | dut.i_ctrl.value = 1 # control symbol 280 | dut.i_8b.value = 0b00000011 # K.03.0 281 | 282 | await FallingEdge(dut.i_clk) # 1 clock tick 283 | assert prev_o_10b == int(dut.o_10b) # output should not have changed 284 | assert prev_o_code_err == int(dut.o_code_err) # code error should not have changed 285 | assert prev_rd == int(dut.rd) # running disparity should not have changed 286 | 287 | # Test 8b/10b encoding look-up table 288 | dut.i_reset_n.value = 1 289 | dut.i_en.value = 1 290 | for i in range(2**10): 291 | 292 | # Parse out input values 293 | i_8b = i & 0xFF 294 | i_disp = (i >> 8) & 1 295 | i_ctrl = (i >> 9) & 1 296 | 297 | # Run the software 8b10b encoder 298 | py_i_8b = i_8b 299 | py_i_disp = (i_disp * 2) - 1 300 | py_i_ctrl = bool(i_ctrl) 301 | py_o_10b, py_o_disp, py_o_code_err = encode_8b10b(py_i_8b, py_i_disp, py_i_ctrl) 302 | 303 | # Calculate expected dut outputs using software 304 | # 8b/10b encoder results 305 | o_10b = py_o_10b 306 | o_disp = (py_o_disp + 1) // 2 307 | o_code_err = int(py_o_code_err) 308 | 309 | # Push inputs to dut 310 | dut.i_8b.value = i_8b 311 | dut.i_ctrl.value = i_ctrl 312 | 313 | # Force internal running disparity to specific value 314 | dut.rd.value = i_disp 315 | 316 | d_prev_rd = int(dut.rd) # save previous RD value 317 | 318 | # Step 1 clock tick 319 | await FallingEdge(dut.i_clk) # 1 clock tick 320 | 321 | # Check actual outputs vs expected outputs 322 | try: 323 | assert int(dut.o_10b) == o_10b 324 | assert int(dut.rd) == o_disp 325 | assert int(dut.o_code_err) == o_code_err 326 | except AssertionError as e: 327 | i_8b_s = format(i_8b, "#010b") 328 | i_ctrl_s = format(i_ctrl, "#03b") 329 | d_i_8b_s = "0b" + str(dut.i_8b.value) 330 | d_i_ctrl_s = "0b" + str(dut.i_ctrl.value) 331 | d_prev_rd_s = format(d_prev_rd, "#03b") 332 | d_rd_s = "0b" + str(dut.rd.value) 333 | d_o_10b_s = "0b" + str(dut.o_10b.value) 334 | d_o_code_err_s = "0b" + str(dut.o_code_err.value) 335 | o_10b_s = format(o_10b, "#012b") 336 | o_code_err_s = format(o_code_err, "#03b") 337 | print( 338 | f"Error: Actual outputs do not match expected outputs\n" 339 | f" i_8b = {i_8b_s}, i_ctrl = {i_ctrl_s}\n" 340 | f" dut.i_8b = {d_i_8b_s}, dut.i_ctrl = {d_i_ctrl_s}\n" 341 | f" previous dut.rd = {d_prev_rd_s}, current dut.rd = {d_rd_s}\n" 342 | f" dut.o_10b = {d_o_10b_s}, dut.o_code_err = {d_o_code_err_s}\n" 343 | f" o_10b = {o_10b_s}, o_code_err = {o_code_err_s}\n" 344 | ) 345 | raise e 346 | -------------------------------------------------------------------------------- /docs/source/encoder_8b10b.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | 8B/10B Encoder 3 | ============== 4 | 5 | 8B/10B is a line code that maps 8-bit words to 10-bit symbols to achieve DC-balance and bounded 6 | disparity, while at the same time provide enough state changes to allow reasonable clock recovery. This 7 | 8B/10B encoder design implements 8 | `IBM's 8B/10B coding scheme `_. 9 | 10 | DC-free 8b/10b coding requires that the long-term ratio of ones and zeros transmitted is exactly 50%. To 11 | achieve this, the difference between the number of 1s and 0s transmitted is always limited to ±2, so 12 | the difference at the end of each symbol will always be either +1 or -1. This difference is known 13 | as the *running disparity* (RD). The IBM implementation needs only two states for the running disparity, 14 | -1 and +1, where the RD always starts at -1. 15 | 16 | To simplify the coding algorithm, the IBM 8b/10b coding implementation breaks down the 8b/10b coding 17 | into 5b/6b and 3b/4b subcodings. Then, the running disparity is evaluated over each 6b or 4b code as 18 | it is transmitted or received. The rules for calculating running disparity are: 19 | 20 | 1. If the disparity of the 6b or 4b codeword is 0 (equal number of 1s and 0s) then the output running 21 | disparity is equal to the input running disparity (i.e. -1 -> -1, +1 -> +1). 22 | 2. If the disparity of the 6b or 4b codeword is not 0 (i.e. ±2, ±4, ±6) then the output running disparity 23 | is equal to the complement of the input running disparity (i.e. -1 -> +1, +1 -> -1) 24 | 25 | This core keeps track of the running disparity internally so the user does not need to implement any 26 | additional logic to determine it. The user can, however, control the running disparity by asserting 27 | the active-low reset signal ``i_reset_n`` to reset the running disparity to -1. 28 | 29 | Additional control and error status features are provided with this design and are described below: 30 | 31 | * ``i_en`` is an input enable signal that controls whether or not to perform 8b/10b 32 | encoding. While deasserted, the core will ignore any further input, maintain current outputs, 33 | and maintain the current running disparity. 34 | * ``i_ctrl`` is an input control symbol select signal that will have the encoder output a 35 | control symbol (K.x.y) if high, otherwise it will output a data symbol (D.x.y). 36 | * ``o_code_err`` is an error status signal that indicates when the user encoded an illegal 8b/10b 37 | code. As all data symbols are valid codes, this signal will only be asserted if an illegal 38 | control symbol is encoded because only 12 control symbols are allowed to be sent. For more 39 | information on the allowed control symbols see the :ref:`Control Symbols table `. 40 | 41 | IBM's 8B/10B implementation is defined by partitioning the coder into 5B/6B and 3B/4B subordinate coders 42 | as described in the tables below. Using these tables, a given input 8-bit value, ``HGFEDCBA``, along 43 | with the current running disparity and a control symbol select signal, can be encoded to its corresponding 44 | 10-bit value, ``jhgfiedcba``. 45 | 46 | 47 | .. table:: 5B/6B Coding Table 48 | 49 | +--------------+---------+----------+----------------+---------+----------+ 50 | | Input | RD = −1 | RD = +1 | Input | RD = −1 | RD = +1 | 51 | +======+=======+=========+==========+========+=======+=========+==========+ 52 | | Code | EDCBA | abcdei | Code | EDCBA | abcdei | 53 | +------+-------+---------+----------+--------+-------+---------+----------+ 54 | | D.00 | 00000 | 100111 | 011000 | D.16 | 10000 | 011011 | 100100 | 55 | +------+-------+---------+----------+--------+-------+---------+----------+ 56 | | D.01 | 00001 | 011101 | 100010 | D.17 | 10001 | 100011 | 57 | +------+-------+---------+----------+--------+-------+--------------------+ 58 | | D.02 | 00010 | 101101 | 010010 | D.18 | 10010 | 010011 | 59 | +------+-------+---------+----------+--------+-------+--------------------+ 60 | | D.03 | 00011 | 110001 | D.19 | 10011 | 110010 | 61 | +------+-------+---------+----------+--------+-------+--------------------+ 62 | | D.04 | 00100 | 110101 | 001010 | D.20 | 10100 | 001011 | 63 | +------+-------+---------+----------+--------+-------+--------------------+ 64 | | D.05 | 00101 | 101001 | D.21 | 10101 | 101010 | 65 | +------+-------+--------------------+--------+-------+--------------------+ 66 | | D.06 | 00110 | 011001 | D.22 | 10110 | 011010 | 67 | +------+-------+---------+----------+--------+-------+---------+----------+ 68 | | D.07 | 00111 | 111000 | 000111 | D.23 † | 10111 | 111010 | 000101 | 69 | +------+-------+---------+----------+--------+-------+---------+----------+ 70 | | D.08 | 01000 | 111001 | 000110 | D.24 | 11000 | 110011 | 001100 | 71 | +------+-------+---------+----------+--------+-------+---------+----------+ 72 | | D.09 | 01001 | 100101 | D.25 | 11001 | 100110 | 73 | +------+-------+--------------------+--------+-------+--------------------+ 74 | | D.10 | 01010 | 010101 | D.26 | 11010 | 010110 | 75 | +------+-------+--------------------+--------+-------+---------+----------+ 76 | | D.11 | 01011 | 110100 | D.27 † | 11011 | 110110 | 001001 | 77 | +------+-------+--------------------+--------+-------+---------+----------+ 78 | | D.12 | 01100 | 001101 | D.28 | 11100 | 001110 | 79 | +------+-------+--------------------+--------+-------+---------+----------+ 80 | | D.13 | 01101 | 101100 | D.29 † | 11101 | 101110 | 010001 | 81 | +------+-------+--------------------+--------+-------+---------+----------+ 82 | | D.14 | 01110 | 011100 | D.30 † | 11110 | 011110 | 100001 | 83 | +------+-------+---------+----------+--------+-------+---------+----------+ 84 | | D.15 | 01111 | 010111 | 101000 | D.31 | 11111 | 101011 | 010100 | 85 | +------+-------+---------+----------+--------+-------+---------+----------+ 86 | | | K.28 ‡ | 11100 | 001111 | 110000 | 87 | +-----------------------------------+--------+-------+---------+----------+ 88 | 89 | † *also used for the 5b/6b code of K.x.7* 90 | 91 | ‡ *exclusively used for the 5b/6b code of K.28.y* 92 | 93 | 94 | .. table:: 3B/4B Coding Table 95 | 96 | +----------------+---------+----------+---------------+---------+----------+ 97 | | Input | RD = −1 | RD = +1 | Input | RD = −1 | RD = +1 | 98 | +----------+-----+---------+----------+---------+-----+---------+----------+ 99 | | Code | HGF | fghj | Code | HGF | fghj | 100 | +==========+=====+=========+==========+=========+=====+=========+==========+ 101 | | D.x.0 | 000 | 1011 | 0100 | K.x.0 | 000 | 1011 | 0100 | 102 | +----------+-----+---------+----------+---------+-----+---------+----------+ 103 | | D.x.1 | 001 | 1001 | K.x.1 ‡ | 001 | 0110 | 1001 | 104 | +----------+-----+--------------------+---------+-----+---------+----------+ 105 | | D.x.2 | 010 | 0101 | K.x.2 | 010 | 1010 | 0101 | 106 | +----------+-----+---------+----------+---------+-----+---------+----------+ 107 | | D.x.3 | 011 | 1100 | 0011 | K.x.3 | 011 | 1100 | 0011 | 108 | +----------+-----+---------+----------+---------+-----+---------+----------+ 109 | | D.x.4 | 100 | 1101 | 0010 | K.x.4 | 100 | 1101 | 0010 | 110 | +----------+-----+---------+----------+---------+-----+---------+----------+ 111 | | D.x.5 | 101 | 1010 | K.x.5 ‡ | 101 | 0101 | 1010 | 112 | +----------+-----+--------------------+---------+-----+---------+----------+ 113 | | D.x.6 | 110 | 0110 | K.x.6 | 110 | 1001 | 0110 | 114 | +----------+-----+---------+----------+---------+-----+---------+----------+ 115 | | D.x.P7 † | | 1110 | 0001 | K.x.7 ‡ | 111 | 0111 | 1000 | 116 | +----------+ 111 +---------+----------+---------+-----+---------+----------+ 117 | | D.x.A7 † | | 0111 | 1000 | | 118 | +----------+-----+---------+----------+------------------------------------+ 119 | 120 | † *For D.x.7, either the Primary (D.x.P7), or the Alternate (D.x.A7) encoding must be selected* 121 | *in order to avoid a run of five consecutive 0s or 1s when combined with the preceding 5b/6b code.* 122 | *Sequences of exactly five identical bits are used in comma symbols for synchronization issues.* 123 | *D.x.A7 is used only when:* 124 | 125 | * *RD = −1: for x = 17, 18 and 20* 126 | * *RD = +1: for x = 11, 13 and 14* 127 | 128 | *With x = 23, x = 27, x = 29, and x = 30, the 3b/4b code portion used for control symbols K.x.7 is* 129 | *the same as that for D.x.A7. Any other D.x.A7 code can't be used as it would result in chances for* 130 | *misaligned comma sequences.* 131 | 132 | ‡ *Only K.28.1, K.28.5, and K.28.7 generate comma symbols, that contain a bit sequence of five 0s or* 133 | *1s. The symbol has the format 110000 01xx or 001111 10xx.* 134 | 135 | .. _control_symbols: 136 | 137 | Control symbols are used in 8b/10b coding for low-level control functions such as comma symbols for 138 | synchronization, loop arbitration, fill words, link resets, etc. The way in which the control 139 | symbols are used is defined by the protocol standard (i.e. Ethernet, Fibre Channel, PCIe) but 140 | 8b/10b coding only allows 12 control symbols to be sent. 141 | 142 | 143 | .. table:: Control Symbols 144 | 145 | +----------------------------------+-------------+-------------+ 146 | | Input | RD = −1 | RD = +1 | 147 | +----------+-----+-----+-----------+-------------+-------------+ 148 | | Symbol | DEC | HEX | HGF EDCBA | abcdei fghj | abcdei fghj | 149 | +==========+=====+=====+===========+=============+=============+ 150 | | K.28.0 | 28 | 1C | 000 11100 | 001111 0100 | 110000 1011 | 151 | +----------+-----+-----+-----------+-------------+-------------+ 152 | | K.28.1 † | 60 | 3C | 001 11100 | 001111 1001 | 110000 0110 | 153 | +----------+-----+-----+-----------+-------------+-------------+ 154 | | K.28.2 | 92 | 5C | 010 11100 | 001111 0101 | 110000 1010 | 155 | +----------+-----+-----+-----------+-------------+-------------+ 156 | | K.28.3 | 124 | 7C | 011 11100 | 001111 0011 | 110000 1100 | 157 | +----------+-----+-----+-----------+-------------+-------------+ 158 | | K.28.4 | 156 | 9C | 100 11100 | 001111 0010 | 110000 1101 | 159 | +----------+-----+-----+-----------+-------------+-------------+ 160 | | K.28.5 † | 188 | BC | 101 11100 | 001111 1010 | 110000 0101 | 161 | +----------+-----+-----+-----------+-------------+-------------+ 162 | | K.28.6 | 220 | DC | 110 11100 | 001111 0110 | 110000 1001 | 163 | +----------+-----+-----+-----------+-------------+-------------+ 164 | | K.28.7 ‡ | 252 | FC | 111 11100 | 001111 1000 | 110000 0111 | 165 | +----------+-----+-----+-----------+-------------+-------------+ 166 | | K.23.7 | 247 | F7 | 111 10111 | 111010 1000 | 000101 0111 | 167 | +----------+-----+-----+-----------+-------------+-------------+ 168 | | K.27.7 | 251 | FB | 111 11011 | 110110 1000 | 001001 0111 | 169 | +----------+-----+-----+-----------+-------------+-------------+ 170 | | K.29.7 | 253 | FD | 111 11101 | 101110 1000 | 010001 0111 | 171 | +----------+-----+-----+-----------+-------------+-------------+ 172 | | K.30.7 | 254 | FE | 111 11110 | 011110 1000 | 100001 0111 | 173 | +----------+-----+-----+-----------+-------------+-------------+ 174 | 175 | † *Within the control symbols, K.28.1, K.28.5, and K.28.7 are "comma symbols". Comma symbols* 176 | *are used for synchronization (finding the alignment of the 8b/10b codes within a bit-stream).* 177 | *If K.28.7 is not used, the unique comma sequences 00111110 or 11000001 cannot be found at any* 178 | *bit position within any combination of normal codes.* 179 | 180 | ‡ *If K.28.7 is allowed in the actual coding, a more complex definition of the synchronization* 181 | *pattern than suggested by † needs to be used, as a combination of K.28.7 with several other* 182 | *codes forms a false misaligned comma symbol overlapping the two codes. A sequence of multiple* 183 | *K.28.7 codes is not allowable in any case, as this would result in undetectable misaligned* 184 | *comma symbols. K.28.7 is the only comma symbol that cannot be the result of a single bit error* 185 | *in the data stream.* 186 | 187 | 188 | Parameters 189 | ---------- 190 | - None 191 | 192 | Ports 193 | ----- 194 | - ``i_clk`` : input clock 195 | - ``i_reset_n`` : input asynchronous active-low reset 196 | - ``i_en`` : input active-high enable 197 | - ``i_8b`` : input 8-bit value (bit-order is ``HGFEDCBA`` where ``A`` is the lsb) 198 | - ``i_ctrl`` : input control symbol select (``0`` = data symbol, ``1`` = control symbol) 199 | - ``o_10b`` : output 10-bit value (bit-order is ``jhgfiedcba`` where ``a`` is the lsb) 200 | - ``o_code_err`` : output code error 201 | 202 | Source Code 203 | ----------- 204 | .. literalinclude:: ../../libsv/coders/encoder_8b10b.sv 205 | :language: systemverilog 206 | :linenos: 207 | :caption: encoder_8b10b.sv 208 | -------------------------------------------------------------------------------- /docs/source/decoder_8b10b.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | 8B/10B Decoder 3 | ============== 4 | 5 | 8B/10B is a line code that maps 8-bit words to 10-bit symbols to achieve DC-balance and bounded 6 | disparity, while at the same time provide enough state changes to allow reasonable clock recovery. This 7 | 8B/10B decoder design implements 8 | `IBM's 8B/10B coding scheme `_. 9 | 10 | DC-free 8b/10b coding requires that the long-term ratio of ones and zeros transmitted is exactly 50%. To 11 | achieve this, the difference between the number of 1s and 0s transmitted is always limited to ±2, so 12 | the difference at the end of each symbol will always be either +1 or -1. This difference is known 13 | as the *running disparity* (RD). The IBM implementation needs only two states for the running disparity, 14 | -1 and +1, where the RD always starts at -1. 15 | 16 | To simplify the coding algorithm, the IBM 8b/10b coding implementation breaks down the 8b/10b coding 17 | into 5b/6b and 3b/4b subcodings. Then, the running disparity is evaluated over each 6b or 4b code as 18 | it is transmitted or received. The rules for calculating running disparity are: 19 | 20 | 1. If the disparity of the 6b or 4b codeword is 0 (equal number of 1s and 0s) then the output running 21 | disparity is equal to the input running disparity (i.e. -1 -> -1, +1 -> +1). 22 | 2. If the disparity of the 6b or 4b codeword is not 0 (i.e. ±2, ±4, ±6) then the output running disparity 23 | is equal to the complement of the input running disparity (i.e. -1 -> +1, +1 -> -1) 24 | 25 | This core keeps track of the running disparity internally so the user does not need to implement any 26 | additional logic to determine it. The user can, however, control the running disparity by asserting 27 | the active-low reset signal ``i_reset_n`` to reset the running disparity to -1. 28 | 29 | Additional control and error status features are provided with this design and are described below: 30 | 31 | * ``i_en`` is an input enable signal that controls whether or not to perform 8b/10b 32 | decoding. While deasserted, the core will ignore any further input, maintain current outputs, 33 | and maintain the current running disparity. 34 | * ``o_ctrl`` is an output control symbol flag which the decoder uses to indicate whether a received 35 | 10b value is a control symbol (K.x.y, ``o_ctrl`` = 1) or a data symbol (D.x.y, ``o_ctrl`` = 0). 36 | * ``o_code_err`` is an error status signal that indicates when an illegal 8b/10b code is received by 37 | the decoder. 38 | * ``o_disp_err`` is an error status signal that indicates when a disparity error is detected in the 39 | 10b value received by the decoder. 40 | 41 | Two error output signals, ``o_code_err`` and ``o_disp_err``, are provided for the decoder as there are 42 | certain input combinations that may only generate one of the two error types in decoding 10b values - 43 | so both are provided to the user to allow them to handle the two error types separately should they want 44 | to. 45 | 46 | IBM's 8B/10B implementation is defined by partitioning the coder into 5B/6B and 3B/4B subordinate coders 47 | as described in the tables below. Using these tables, a given input 10-bit value, ``jhgfiedcba``, along 48 | with the current running disparity, can be decoded to its corresponding 8-bit value, ``HGFEDCBA``, along 49 | with an indication if the received symbol is a control or data symbol. 50 | 51 | 52 | .. table:: 5B/6B Coding Table 53 | 54 | +--------------+---------+----------+----------------+---------+----------+ 55 | | Input | RD = −1 | RD = +1 | Input | RD = −1 | RD = +1 | 56 | +======+=======+=========+==========+========+=======+=========+==========+ 57 | | Code | EDCBA | abcdei | Code | EDCBA | abcdei | 58 | +------+-------+---------+----------+--------+-------+---------+----------+ 59 | | D.00 | 00000 | 100111 | 011000 | D.16 | 10000 | 011011 | 100100 | 60 | +------+-------+---------+----------+--------+-------+---------+----------+ 61 | | D.01 | 00001 | 011101 | 100010 | D.17 | 10001 | 100011 | 62 | +------+-------+---------+----------+--------+-------+--------------------+ 63 | | D.02 | 00010 | 101101 | 010010 | D.18 | 10010 | 010011 | 64 | +------+-------+---------+----------+--------+-------+--------------------+ 65 | | D.03 | 00011 | 110001 | D.19 | 10011 | 110010 | 66 | +------+-------+---------+----------+--------+-------+--------------------+ 67 | | D.04 | 00100 | 110101 | 001010 | D.20 | 10100 | 001011 | 68 | +------+-------+---------+----------+--------+-------+--------------------+ 69 | | D.05 | 00101 | 101001 | D.21 | 10101 | 101010 | 70 | +------+-------+--------------------+--------+-------+--------------------+ 71 | | D.06 | 00110 | 011001 | D.22 | 10110 | 011010 | 72 | +------+-------+---------+----------+--------+-------+---------+----------+ 73 | | D.07 | 00111 | 111000 | 000111 | D.23 † | 10111 | 111010 | 000101 | 74 | +------+-------+---------+----------+--------+-------+---------+----------+ 75 | | D.08 | 01000 | 111001 | 000110 | D.24 | 11000 | 110011 | 001100 | 76 | +------+-------+---------+----------+--------+-------+---------+----------+ 77 | | D.09 | 01001 | 100101 | D.25 | 11001 | 100110 | 78 | +------+-------+--------------------+--------+-------+--------------------+ 79 | | D.10 | 01010 | 010101 | D.26 | 11010 | 010110 | 80 | +------+-------+--------------------+--------+-------+---------+----------+ 81 | | D.11 | 01011 | 110100 | D.27 † | 11011 | 110110 | 001001 | 82 | +------+-------+--------------------+--------+-------+---------+----------+ 83 | | D.12 | 01100 | 001101 | D.28 | 11100 | 001110 | 84 | +------+-------+--------------------+--------+-------+---------+----------+ 85 | | D.13 | 01101 | 101100 | D.29 † | 11101 | 101110 | 010001 | 86 | +------+-------+--------------------+--------+-------+---------+----------+ 87 | | D.14 | 01110 | 011100 | D.30 † | 11110 | 011110 | 100001 | 88 | +------+-------+---------+----------+--------+-------+---------+----------+ 89 | | D.15 | 01111 | 010111 | 101000 | D.31 | 11111 | 101011 | 010100 | 90 | +------+-------+---------+----------+--------+-------+---------+----------+ 91 | | | K.28 ‡ | 11100 | 001111 | 110000 | 92 | +-----------------------------------+--------+-------+---------+----------+ 93 | 94 | † *also used for the 5b/6b code of K.x.7* 95 | 96 | ‡ *exclusively used for the 5b/6b code of K.28.y* 97 | 98 | 99 | .. table:: 3B/4B Coding Table 100 | 101 | +----------------+---------+----------+---------------+---------+----------+ 102 | | Input | RD = −1 | RD = +1 | Input | RD = −1 | RD = +1 | 103 | +----------+-----+---------+----------+---------+-----+---------+----------+ 104 | | Code | HGF | fghj | Code | HGF | fghj | 105 | +==========+=====+=========+==========+=========+=====+=========+==========+ 106 | | D.x.0 | 000 | 1011 | 0100 | K.x.0 | 000 | 1011 | 0100 | 107 | +----------+-----+---------+----------+---------+-----+---------+----------+ 108 | | D.x.1 | 001 | 1001 | K.x.1 ‡ | 001 | 0110 | 1001 | 109 | +----------+-----+--------------------+---------+-----+---------+----------+ 110 | | D.x.2 | 010 | 0101 | K.x.2 | 010 | 1010 | 0101 | 111 | +----------+-----+---------+----------+---------+-----+---------+----------+ 112 | | D.x.3 | 011 | 1100 | 0011 | K.x.3 | 011 | 1100 | 0011 | 113 | +----------+-----+---------+----------+---------+-----+---------+----------+ 114 | | D.x.4 | 100 | 1101 | 0010 | K.x.4 | 100 | 1101 | 0010 | 115 | +----------+-----+---------+----------+---------+-----+---------+----------+ 116 | | D.x.5 | 101 | 1010 | K.x.5 ‡ | 101 | 0101 | 1010 | 117 | +----------+-----+--------------------+---------+-----+---------+----------+ 118 | | D.x.6 | 110 | 0110 | K.x.6 | 110 | 1001 | 0110 | 119 | +----------+-----+---------+----------+---------+-----+---------+----------+ 120 | | D.x.P7 † | | 1110 | 0001 | K.x.7 ‡ | 111 | 0111 | 1000 | 121 | +----------+ 111 +---------+----------+---------+-----+---------+----------+ 122 | | D.x.A7 † | | 0111 | 1000 | | 123 | +----------+-----+---------+----------+------------------------------------+ 124 | 125 | † *For D.x.7, either the Primary (D.x.P7), or the Alternate (D.x.A7) encoding must be selected* 126 | *in order to avoid a run of five consecutive 0s or 1s when combined with the preceding 5b/6b code.* 127 | *Sequences of exactly five identical bits are used in comma symbols for synchronization issues.* 128 | *D.x.A7 is used only when:* 129 | 130 | * *RD = −1: for x = 17, 18 and 20* 131 | * *RD = +1: for x = 11, 13 and 14* 132 | 133 | *With x = 23, x = 27, x = 29, and x = 30, the 3b/4b code portion used for control symbols K.x.7 is* 134 | *the same as that for D.x.A7. Any other D.x.A7 code can't be used as it would result in chances for* 135 | *misaligned comma sequences.* 136 | 137 | ‡ *Only K.28.1, K.28.5, and K.28.7 generate comma symbols, that contain a bit sequence of five 0s or* 138 | *1s. The symbol has the format 110000 01xx or 001111 10xx.* 139 | 140 | 141 | Control symbols are used in 8b/10b coding for low-level control functions such as comma symbols for 142 | synchronization, loop arbitration, fill words, link resets, etc. The way in which the control 143 | symbols are used is defined by the protocol standard (i.e. Ethernet, Fibre Channel, PCIe) but 144 | 8b/10b coding only allows 12 control symbols to be sent. 145 | 146 | 147 | .. table:: Control Symbols 148 | 149 | +----------------------------------+-------------+-------------+ 150 | | Input | RD = −1 | RD = +1 | 151 | +----------+-----+-----+-----------+-------------+-------------+ 152 | | Symbol | DEC | HEX | HGF EDCBA | abcdei fghj | abcdei fghj | 153 | +==========+=====+=====+===========+=============+=============+ 154 | | K.28.0 | 28 | 1C | 000 11100 | 001111 0100 | 110000 1011 | 155 | +----------+-----+-----+-----------+-------------+-------------+ 156 | | K.28.1 † | 60 | 3C | 001 11100 | 001111 1001 | 110000 0110 | 157 | +----------+-----+-----+-----------+-------------+-------------+ 158 | | K.28.2 | 92 | 5C | 010 11100 | 001111 0101 | 110000 1010 | 159 | +----------+-----+-----+-----------+-------------+-------------+ 160 | | K.28.3 | 124 | 7C | 011 11100 | 001111 0011 | 110000 1100 | 161 | +----------+-----+-----+-----------+-------------+-------------+ 162 | | K.28.4 | 156 | 9C | 100 11100 | 001111 0010 | 110000 1101 | 163 | +----------+-----+-----+-----------+-------------+-------------+ 164 | | K.28.5 † | 188 | BC | 101 11100 | 001111 1010 | 110000 0101 | 165 | +----------+-----+-----+-----------+-------------+-------------+ 166 | | K.28.6 | 220 | DC | 110 11100 | 001111 0110 | 110000 1001 | 167 | +----------+-----+-----+-----------+-------------+-------------+ 168 | | K.28.7 ‡ | 252 | FC | 111 11100 | 001111 1000 | 110000 0111 | 169 | +----------+-----+-----+-----------+-------------+-------------+ 170 | | K.23.7 | 247 | F7 | 111 10111 | 111010 1000 | 000101 0111 | 171 | +----------+-----+-----+-----------+-------------+-------------+ 172 | | K.27.7 | 251 | FB | 111 11011 | 110110 1000 | 001001 0111 | 173 | +----------+-----+-----+-----------+-------------+-------------+ 174 | | K.29.7 | 253 | FD | 111 11101 | 101110 1000 | 010001 0111 | 175 | +----------+-----+-----+-----------+-------------+-------------+ 176 | | K.30.7 | 254 | FE | 111 11110 | 011110 1000 | 100001 0111 | 177 | +----------+-----+-----+-----------+-------------+-------------+ 178 | 179 | † *Within the control symbols, K.28.1, K.28.5, and K.28.7 are "comma symbols". Comma symbols* 180 | *are used for synchronization (finding the alignment of the 8b/10b codes within a bit-stream).* 181 | *If K.28.7 is not used, the unique comma sequences 00111110 or 11000001 cannot be found at any* 182 | *bit position within any combination of normal codes.* 183 | 184 | ‡ *If K.28.7 is allowed in the actual coding, a more complex definition of the synchronization* 185 | *pattern than suggested by † needs to be used, as a combination of K.28.7 with several other* 186 | *codes forms a false misaligned comma symbol overlapping the two codes. A sequence of multiple* 187 | *K.28.7 codes is not allowable in any case, as this would result in undetectable misaligned* 188 | *comma symbols. K.28.7 is the only comma symbol that cannot be the result of a single bit error* 189 | *in the data stream.* 190 | 191 | 192 | Parameters 193 | ---------- 194 | - None 195 | 196 | Ports 197 | ----- 198 | - ``i_clk`` : input clock 199 | - ``i_reset_n`` : input asynchronous active-low reset 200 | - ``i_en`` : input active-high enable 201 | - ``i_10b`` : input 10-bit binary value (bit-order is ``jhgfiedcba`` where ``a`` is the lsb) 202 | - ``o_8b`` : output 8-bit binary value (bit-order is ``HGFEDCBA`` where ``A`` is the lsb) 203 | - ``o_ctrl`` : output control symbol indicator flag (``0`` = data symbol, ``1`` = control symbol) 204 | - ``o_code_err`` : output code error bit (``0`` = no code error, ``1`` = code error) 205 | - ``o_disp_err`` : output disparity error bit (``0`` = no disparity error, ``1`` = disparity error) 206 | 207 | Source Code 208 | ----------- 209 | .. literalinclude:: ../../libsv/coders/decoder_8b10b.sv 210 | :language: systemverilog 211 | :linenos: 212 | :caption: decoder_8b10b.sv 213 | -------------------------------------------------------------------------------- /tests/coders/test_decoder_8b10b.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.clock import Clock 3 | from cocotb.triggers import FallingEdge 4 | from typing import Tuple 5 | from utils import pytest_cocotb_run_test 6 | 7 | 8 | def test_8b10b_decoder(pytestconfig): 9 | """Pytest fixture for 8B/10B Decoder test""" 10 | pytest_cocotb_run_test(pytestconfig, __name__) 11 | 12 | 13 | def decode_5b6b(i_6b: int, i_disp: int) -> Tuple[int, int, bool, bool]: 14 | """5b/6b decoder. 15 | 16 | Args: 17 | i_6b (int): input 6b value 18 | i_disp (int): input disparity (-1 or 1) 19 | 20 | Returns: 21 | tuple[int, int, bool, bool]: tuple of output 5b value, output disparity, 22 | control symbol flag, code error flag, and disparity error flag 23 | """ 24 | 25 | # lookup table maps iedcba to EDBCA 26 | lut_6b5b = { 27 | # iebcba: EDCBA 28 | 0b000000: None, # Illegal code 29 | 0b000001: None, # Illegal code 30 | 0b000010: None, # Illegal code 31 | 0b000011: 0b11100, # K.28+ 32 | 0b000100: None, # Illegal code 33 | 0b000101: 0b01111, # D.15+ 34 | 0b000110: 0b00000, # D.0+ 35 | 0b000111: 0b00111, # D.7- 36 | 0b001000: None, # Illegal code 37 | 0b001001: 0b10000, # D.16+ 38 | 0b001010: 0b11111, # D.31+ 39 | 0b001011: 0b01011, # D.11 40 | 0b001100: 0b11000, # D.24+ 41 | 0b001101: 0b01101, # D.13 42 | 0b001110: 0b01110, # D.14 43 | 0b001111: None, # Illegal code (not used) 44 | 0b010000: None, # Illegal code 45 | 0b010001: 0b00001, # D.01+ 46 | 0b010010: 0b00010, # D.02+ 47 | 0b010011: 0b10011, # D.19 48 | 0b010100: 0b00100, # D.04+ 49 | 0b010101: 0b10101, # D.21 50 | 0b010110: 0b10110, # D.22 51 | 0b010111: 0b10111, # D.23- 52 | 0b011000: 0b01000, # D.08+ 53 | 0b011001: 0b11001, # D.25 54 | 0b011010: 0b11010, # D.26 55 | 0b011011: 0b11011, # D.27- 56 | 0b011100: 0b11100, # D.28 57 | 0b011101: 0b11101, # D.29- 58 | 0b011110: 0b11110, # D.30- 59 | 0b011111: None, # Illegal code 60 | 0b100000: None, # Illegal code 61 | 0b100001: 0b11110, # D.30+ 62 | 0b100010: 0b11101, # D.29+ 63 | 0b100011: 0b00011, # D.03 64 | 0b100100: 0b11011, # D.27+ 65 | 0b100101: 0b00101, # D.05 66 | 0b100110: 0b00110, # D.06 67 | 0b100111: 0b01000, # D.08- 68 | 0b101000: 0b10111, # D.23+ 69 | 0b101001: 0b01001, # D.09 70 | 0b101010: 0b01010, # D.10 71 | 0b101011: 0b00100, # D.04- 72 | 0b101100: 0b01100, # D.12 73 | 0b101101: 0b00010, # D.02- 74 | 0b101110: 0b00001, # D.01- 75 | 0b101111: None, # Illegal code 76 | 0b110000: None, # Illegal code (not used) 77 | 0b110001: 0b10001, # D.17 78 | 0b110010: 0b10010, # D.18 79 | 0b110011: 0b11000, # D.24- 80 | 0b110100: 0b10100, # D.20 81 | 0b110101: 0b11111, # D.31- 82 | 0b110110: 0b10000, # D.16- 83 | 0b110111: None, # Illegal code 84 | 0b111000: 0b00111, # D.07+ 85 | 0b111001: 0b00000, # D.00- 86 | 0b111010: 0b01111, # D.15- 87 | 0b111011: None, # Illegal code 88 | 0b111100: 0b11100, # K.28- 89 | 0b111101: None, # Illegal code 90 | 0b111110: None, # Illegal code 91 | 0b111111: None, # Illegal code 92 | } 93 | 94 | o_5b = lut_6b5b[i_6b] # lookup 5b/6b value 95 | o_disp = 0 # initialize output disparity 96 | o_code_err = False # initialize code error flag 97 | o_disp_err = False # initialize disparity error flag 98 | 99 | if o_5b is None: # check if illegal code 100 | o_code_err = True 101 | o_5b = 0 102 | 103 | # calculate the output disparity 104 | string_6b = format(i_6b, "06b") 105 | num_ones = string_6b.count("1") 106 | num_zeros = string_6b.count("0") 107 | codeword_disp = num_ones - num_zeros 108 | 109 | if codeword_disp != 0: 110 | 111 | # regardless of whether there is a disparity error or not 112 | # the output disparity should always get flipped if it's 113 | # not equal to zero 114 | o_disp = -i_disp 115 | 116 | # Check for disparity error 117 | if ( 118 | codeword_disp >= 3 119 | or codeword_disp <= -3 120 | or (i_disp == -1 and codeword_disp == -2) 121 | or (i_disp == 1 and codeword_disp == 2) 122 | ): 123 | o_disp_err = True 124 | 125 | else: # if codeword disparity is 0 then output disparity is the input disparity 126 | o_disp = i_disp 127 | 128 | return o_5b, o_disp, o_code_err, o_disp_err 129 | 130 | 131 | def decode_3b4b(i_4b: int, i_disp: int, i_ctrl: bool) -> Tuple[int, int]: 132 | """3b/4b decoder. 133 | 134 | Args: 135 | i_4b (int): input 4b value 136 | i_disp (int): input disparity (-1 or 1) 137 | i_ctrl (bool): input control symbol flag 138 | 139 | Returns: 140 | tuple[int, int]: tuple of output 3b value and output disparity 141 | """ 142 | 143 | # lookup table maps jhgf to HGF 144 | lut_4b3b_data = { 145 | # jhgf: HGF 146 | 0b0000: None, # Illegal code 147 | 0b0001: 0b111, # D.x.A7+ 148 | 0b0010: 0b000, # D.x.0+ 149 | 0b0011: 0b011, # D.x.3- 150 | 0b0100: 0b100, # D.x.4+ 151 | 0b0101: 0b101, # D.x.5 152 | 0b0110: 0b110, # D.x.6 153 | 0b0111: 0b111, # D.x.P7- 154 | 0b1000: 0b111, # D.x.P7+ 155 | 0b1001: 0b001, # D.x.1 156 | 0b1010: 0b010, # D.x.2 157 | 0b1011: 0b100, # D.x.4- 158 | 0b1100: 0b011, # D.x.3+ 159 | 0b1101: 0b000, # D.x.0- 160 | 0b1110: 0b111, # D.x.A7- 161 | 0b1111: None, # Illegal code 162 | } 163 | 164 | lut_4b3b_control = { 165 | # jhgf: HGF 166 | 0b0000: None, # Illegal code 167 | 0b0001: 0b111, # K.x.7+ 168 | 0b0010: 0b000, # K.x.0+ 169 | 0b0011: 0b011, # K.x.3- 170 | 0b0100: 0b100, # K.x.4+ 171 | 0b0101: -1, # K.x.5+ / K.x.2- (must be handled using input disparity) 172 | 0b0110: -1, # K.x.6+ / K.x.1- (must be handled using input disparity) 173 | 0b0111: None, # Illegal code 174 | 0b1000: None, # Illegal code 175 | 0b1001: -1, # K.x.6- / K.x.1+ (must be handled using input disparity) 176 | 0b1010: -1, # K.x.5- / K.x.2+ (must be handled using input disparity) 177 | 0b1011: 0b100, # K.x.4- 178 | 0b1100: 0b011, # K.x.3+ 179 | 0b1101: 0b000, # K.x.0- 180 | 0b1110: 0b111, # K.x.7- 181 | 0b1111: None, # Illegal code 182 | } 183 | 184 | # lookup 3b/4b value 185 | o_3b = 0 186 | if i_ctrl: 187 | o_3b = lut_4b3b_control[i_4b] 188 | 189 | if i_4b == 0b0101: # check weird case #1 190 | if i_disp == -1: 191 | o_3b = 0b010 # K.x.2- 192 | else: 193 | o_3b = 0b101 # K.x.5+ 194 | elif i_4b == 0b0110: # check weird case #2 195 | if i_disp == -1: 196 | o_3b = 0b001 # K.x.1- 197 | else: # K.x.6+ 198 | o_3b = 0b110 199 | elif i_4b == 0b1001: # check weird case #3 200 | if i_disp == -1: 201 | o_3b = 0b110 # K.x.6- 202 | else: 203 | o_3b = 0b001 # K.x.1+ 204 | elif i_4b == 0b1010: # check weird case #4 205 | if i_disp == -1: 206 | o_3b = 0b101 # K.x.5- 207 | else: 208 | o_3b = 0b010 # K.x.2+ 209 | else: 210 | o_3b = lut_4b3b_data[i_4b] 211 | 212 | o_disp = 0 # initialize output disparity 213 | o_code_err = False # initialize code error flag 214 | o_disp_err = False # initialize disparity error flag 215 | 216 | if o_3b is None: # check if illegal code 217 | o_code_err = True 218 | o_3b = 0 219 | 220 | # calculate the output disparity 221 | string_4b = format(i_4b, "04b") 222 | num_ones = string_4b.count("1") 223 | num_zeros = string_4b.count("0") 224 | codeword_disp = num_ones - num_zeros 225 | 226 | if codeword_disp != 0: 227 | 228 | # regardless of whether there is a disparity error or not 229 | # the output disparity should always get flipped if it's 230 | # not equal to zero 231 | o_disp = -i_disp 232 | 233 | # Check for disparity error 234 | if ( 235 | codeword_disp >= 3 236 | or codeword_disp <= -3 237 | or (i_disp == -1 and codeword_disp == -2) 238 | or (i_disp == 1 and codeword_disp == 2) 239 | ): 240 | o_disp_err = True 241 | 242 | else: # if codeword disparity is 0 then output disparity is the input disparity 243 | o_disp = i_disp 244 | 245 | return o_3b, o_disp, o_code_err, o_disp_err 246 | 247 | 248 | def decode_8b10b(i_10b, i_disp): 249 | 250 | # Determine if control symbol 251 | control_symbols_rdm1 = [ 252 | # jhgfiedcba 253 | 0b0010111100, # K.28.0- 254 | 0b1001111100, # K.28.1- 255 | 0b1010111100, # K.28.2- 256 | 0b1100111100, # K.28.3- 257 | 0b0100111100, # K.28.4- 258 | 0b0101111100, # K.28.5- 259 | 0b0110111100, # K.28.6- 260 | 0b0001111100, # K.28.7- 261 | 0b0001010111, # K.23.7- 262 | 0b0001011011, # K.27.7- 263 | 0b0001011101, # K.29.7- 264 | 0b0001011110, # K.30.7- 265 | ] 266 | control_symbols = control_symbols_rdm1 + [ 267 | (~s) & 0x3FF for s in control_symbols_rdm1 268 | ] 269 | is_ctrl = i_10b in control_symbols 270 | 271 | # Parse out 6b and 4b inputs 272 | i_6b = i_10b & 0x3F 273 | i_4b = (i_10b >> 6) & 0xF 274 | 275 | # Decode 6b 276 | o_5b, o_disp_5, o_code_err_5, o_disp_err_5 = decode_5b6b(i_6b, i_disp) 277 | 278 | # Decode 4b 279 | o_3b, o_disp_3, o_code_err_3, o_disp_err_3 = decode_3b4b(i_4b, o_disp_5, is_ctrl) 280 | 281 | o_8b = (o_3b << 5) | o_5b 282 | o_disp = o_disp_3 283 | o_ctrl = is_ctrl 284 | o_code_err = o_code_err_3 | o_code_err_5 285 | o_disp_err = o_disp_err_3 | o_disp_err_5 286 | 287 | return o_8b, o_disp, o_ctrl, o_code_err, o_disp_err 288 | 289 | 290 | @cocotb.test() 291 | async def cocotb_test_decoder_8b10b(dut): 292 | """8B/10B Decoder test""" 293 | 294 | # Create clock 295 | cocotb.start_soon(Clock(dut.i_clk, 2).start()) 296 | 297 | # Assert reset and check output and 298 | # internal running disparity 299 | dut.i_reset_n.value = 0 300 | await FallingEdge(dut.i_clk) 301 | assert int(dut.o_8b) == 0 302 | assert int(dut.o_ctrl) == 0 303 | assert int(dut.o_code_err) == 0 304 | assert int(dut.o_disp_err) == 0 305 | assert int(dut.rd) == 0 306 | 307 | # Check that enable doesn't change outputs when disabled 308 | dut.i_reset_n.value = 1 # de-assert reset 309 | dut.i_en.value = 1 # enable 8b10b decoder 310 | dut.i_10b.value = 0b1001001101 # D.13.1 311 | await FallingEdge(dut.i_clk) # 1 clock tick 312 | prev_o_8b = int(dut.o_8b) # capture 8b output 313 | prev_o_ctrl = int(dut.o_ctrl) # capture control symbol flag 314 | prev_o_code_err = int(dut.o_code_err) # capture code error 315 | prev_o_disp_err = int(dut.o_disp_err) # capture disparity error 316 | prev_rd = int(dut.rd) # capture running disparity 317 | dut.i_en.value = 0 # disable 8b10b decoder 318 | 319 | # Pick a value that after the first 10b value would change both 320 | # the output 8b value, internal running disparity, and generate 321 | # both a code error and disparity error 322 | dut.i_10b.value = 0b0000000111 # Illegal code 323 | 324 | await FallingEdge(dut.i_clk) # 1 clock tick 325 | assert prev_o_8b == int(dut.o_8b) # output should not have changed 326 | assert prev_o_ctrl == int(dut.o_ctrl) # control flag should not have changed 327 | assert prev_o_code_err == int(dut.o_code_err) # code error should not have changed 328 | assert prev_o_disp_err == int(dut.o_disp_err) # disp error should not have changed 329 | assert prev_rd == int(dut.rd) # running disparity should not have changed 330 | 331 | # Test 8b/10b decoding look-up table 332 | dut.i_reset_n.value = 1 333 | dut.i_en.value = 1 334 | for i in range(2**11): 335 | 336 | # Parse out input values 337 | i_10b = i & 0x3FF 338 | i_disp = (i >> 10) & 1 339 | 340 | # Run the software 8b10b encoder 341 | py_i_10b = i_10b 342 | py_i_disp = (i_disp * 2) - 1 343 | py_o_8b, py_o_disp, py_o_ctrl, py_o_code_err, py_o_disp_err = decode_8b10b( 344 | py_i_10b, py_i_disp 345 | ) 346 | 347 | # Calculate expected dut outputs using software 348 | # 8b/10b decoder results 349 | o_8b = py_o_8b 350 | o_ctrl = int(py_o_ctrl) 351 | o_code_err = int(py_o_code_err) 352 | o_disp_err = int(py_o_disp_err) 353 | 354 | # Push input to dut 355 | dut.i_10b.value = i_10b 356 | 357 | # Force internal running disparity to specific value 358 | dut.rd.value = i_disp 359 | 360 | d_prev_rd = int(dut.rd) # save previous RD value 361 | 362 | # Step 1 clock tick 363 | await FallingEdge(dut.i_clk) # 1 clock tick 364 | 365 | # Check actual outputs vs expected outputs 366 | try: 367 | assert int(dut.o_8b) == o_8b 368 | assert int(dut.o_ctrl) == o_ctrl 369 | assert int(dut.o_code_err) == o_code_err 370 | assert int(dut.o_disp_err) == o_disp_err 371 | except AssertionError as e: 372 | i_10b_s = format(i_10b, "#012b") 373 | d_i_10b_s = "0b" + str(dut.i_10b.value) 374 | d_prev_rd_s = format(d_prev_rd, "#03b") 375 | d_rd_s = "0b" + str(dut.rd.value) 376 | d_o_8b_s = "0b" + str(dut.o_8b.value) 377 | d_o_ctrl_s = "0b" + str(dut.o_ctrl.value) 378 | d_o_code_err_s = "0b" + str(dut.o_code_err.value) 379 | d_o_disp_err_s = "0b" + str(dut.o_disp_err.value) 380 | d_o_code_err_s = "0b" + str(dut.o_code_err.value) 381 | o_8b_s = format(o_8b, "#010b") 382 | o_ctrl_s = format(o_ctrl, "#03b") 383 | o_code_err_s = format(o_code_err, "#03b") 384 | o_disp_err_s = format(o_disp_err, "#03b") 385 | 386 | print( 387 | f"Error: Actual outputs do not match expected outputs\n" 388 | f" i_10b = {i_10b_s}\n" 389 | f" dut.i_10b = {d_i_10b_s}\n" 390 | f" dut.o_8b = {d_o_8b_s}, dut.o_ctrl = {d_o_ctrl_s}, " 391 | f"dut.o_code_err = {d_o_code_err_s}, " 392 | f"dut.o_disp_err = {d_o_disp_err_s}\n" 393 | f" previous dut.rd = {d_prev_rd_s}, current dut.rd = {d_rd_s}\n" 394 | f" o_8b = {o_8b_s}, o_ctrl = {o_ctrl_s}, " 395 | f"o_code_err = {o_code_err_s}, " 396 | f"o_disp_err = {o_disp_err_s}\n" 397 | ) 398 | raise e 399 | --------------------------------------------------------------------------------