{"id":21899570,"url":"https://github.com/zephray/manjuu","last_synced_at":"2025-04-15T19:18:50.035Z","repository":{"id":185592349,"uuid":"610577032","full_name":"zephray/Manjuu","owner":"zephray","description":"Python preprocessor module library for Verilog","archived":false,"fork":false,"pushed_at":"2023-03-24T02:50:16.000Z","size":38,"stargazers_count":9,"open_issues_count":1,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-15T19:18:38.557Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zephray.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.GPL","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-03-07T03:33:33.000Z","updated_at":"2024-10-25T15:21:31.000Z","dependencies_parsed_at":null,"dependency_job_id":"204bf171-9199-445a-9166-7cd4802736c8","html_url":"https://github.com/zephray/Manjuu","commit_stats":null,"previous_names":["zephray/manjuu"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephray%2FManjuu","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephray%2FManjuu/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephray%2FManjuu/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephray%2FManjuu/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zephray","download_url":"https://codeload.github.com/zephray/Manjuu/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249135832,"owners_count":21218365,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-28T14:45:12.688Z","updated_at":"2025-04-15T19:18:50.015Z","avatar_url":"https://github.com/zephray.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Manjū\n\nManjuu is a python preprocessor module library for verilog. It allows python code to be inserted and evaluated as preprocessor code inside verilog module, for generating ports, wires, etc. Note Manjuu is not an HDL: Compare to other python-based HDLs, Manjuu provides python inside verilog, not verilog-like functionalities inside python.\n\nThe project is inspired by similar usage in OpenPiton. It's currently used in several of my projects such as RISu*64 RISC-V processors and the Servaru GPU. It's still under active development.\n\n## Introduction\n\nThe basic idea is that the Manjuu allows embedding python code inside verilog code. For example:\n\n```verilog\nreg [31:0] counter;\nalways @(posedge clk) begin\n    \u003c% print(\"counter \u003c= counter + 1;\") %\u003e\nend\n```\n\nWould be expanded to\n\n```verilog\nreg [31:0] counter;\nalways @(posedge clk) begin\n    counter \u003c= counter + 1;\nend\n```\n\nObviously this is not very interesting or useful by its own. Usually, the Manjuu libraries are used together.\n\n## Data structures\n\nThe library works with several custom data structures. Usually the user would define some project specific data structures along with using provided ones.\n\n### Bundle\n\nThe bundle type is a bundle of a bunch of wires. It's similar to struct in SystemVerilog or bundle in Chisel. For example, to define a UART bundle:\n\n```python\nreq_t = [\n    [\"i\", \"data\", 8],\n    [\"i\", \"cmd\", 4]\n]\n```\n\nAs you could see it's really just a python list with special meanings. The general structure is\n\n```python\n[\n    [direction, name, width], # Entry with explict width\n    [direction, name], # Entry with width = 1\n    ... # More entries\n]\n```\n\nThe direction is a string, can be `i`, `o`, or `io`. The width is the width of the signal in bits. For example `input [7:0] data` becomes `[\"i\", \"data\", 8]`.\n\n## Base library\n\nThe base libraries provides several useful functions for generating Verilog code.\n\n### Bundle operation\n\nThere are 2 common bundle operations that allows modifying the bundle:\n\n- Reverse: reverse the direction of all signals in a bundle\n- Handshake: add ready valid handshaking signal to a bundle\n- Prefix: add prefix to signal names\n\nNote the handshake always add the signal from the perspective of receiver: valid is an input and ready is a output.\n\nPrefix is typically only used in building nested bundles. \n\n### Port, wire, and connections\n\nManually defining ports and connecting them is often a very repetitive and error-prone work in verilog. Manjuu could generate these with one single definition of the bundle.\n\nFor example, the following code (assuming previous `uart_t` definition):\n\n```verilog\nmodule A(\u003c% gen_port(\"in\", handshake(req_t), last_comma = False) %\u003e);\n    \u003c% gen_reg(\"cur\", req_t) %\u003e\n    always @(posedge clk) begin\n        if (in_valid) begin\n            \u003c% gen_capture(\"cur\", req_t, \"in\") %\u003e\n        end\n    end\n    /* Module implementation */\nendmodule\n\nmodule B(\u003c% gen_port(\"out\", reverse(handshake(req_t)), last_comma = False) %\u003e);\n    /* Module implementation */\nendmodule\n\nmodule Top();\n    \u003c% gen_wire(\"b_a\", handshake(req_t)) %\u003e\n    A a(\u003c% gen_connect(\"in\", req_t, \"b_a\") %\u003e);\n    B b(\u003c% gen_connect(\"out\", req_t, \"b_a\") %\u003e);\nendmodule\n```\n\nIt gets expanded to:\n\n```verilog\nmodule A (\n    input wire [7:0] in_data,\n    input wire [3:0] in_cmd,\n    input wire in_valid,\n    output reg in_ready\n);\n    reg [7:0] cur_data;\n    reg [3:0] cur_cmd;\n\n    always @(posedge clk) begin\n        if (in_valid) begin\n            cur_data \u003c= in_data;\n            cur_cmd \u003c= in_cmd;\n        end\n    end\n    /* Module implementation */\nendmodule\n\nmodule B (\n    output reg [7:0] out_data,\n    output reg [3:0] out_cmd,\n    output reg out_valid,\n    input wire out_ready\n);\n    /* Module implementation */\nendmodule\n\nmodule Top ();\n    wire [7:0] b_a_data;\n    wire [3:0] b_a_cmd;\n    wire b_a_valid;\n    wire b_a_ready;\n\n    A a (\n        .in_data(b_a_data),\n        .in_cmd(b_a_cmd),\n    );\n    B b (\n        .out_data(b_a_data),\n        .out_cmd(b_a_cmd),\n    );\nendmodule\n```\n\n### Data array\n\nIn parameterized designs, it's common for a module to have a configurable signal list. For example, a multi-ported RAM or a bus arbiter, would have parameterized port count. A straight-forward implementation would be to use an array in the module signal list:\n\n```verilog\nmodule dual_port_ram(\n    input wire [63:0] wdata [0:1],\n    output wire [63:0] rdata [0:1],\n    input wire [4:0] addr [0:1],\n    input wire wen [0:1]\n);\n```\n\nHowever not all tools support such syntax. For example, Vivado doesn't support using array inside port signal list. A common workaround is to concat things into a wider signal:\n\n```verilog\nmodule dual_port_ram(\n    input wire [127:0] wdata,\n    output wire [127:0] rdata,\n    input wire [9:0] addr,\n    input wire [1:0] wen\n);\n```\n\nWhich is a bit confusing and not particularly easy to work with. Manjuu supports turning bundles into an array directly as a parameter inside previously mentioned functions. For example, the following is a dual-port RAM model:\n\n```verilog\nmodule dual_port_ram(\n    input wire clk,\n    \u003c% gen_port(\"ram\", ram_req_t, reg=False, count=NR_PORTS, last_comma=False) %\u003e\n);\n    reg [63:0] ram [31:0];\n    always @(posedge clk) begin\n        \u003c%\n        for i in range(NR_PORTS):\n            print(f\"if (ram_wen{i}) begin\")\n            print(f\"    ram[ram_addr{i}] \u003c= ram_wdata{i};\")\n            print(f\"end\")\n            print(f\"ram_rdata{i} \u003c= ram[ram_addr{i}];\")\n        %\u003e\n    end\nendmodule\n```\n\nIt gets expanded to:\n\n```verilog\nmodule dual_port_ram (\n    input wire clk,\n    input wire [63:0] ram_wdata0,\n    output wire [63:0] ram_rdata0,\n    input wire [4:0] ram_addr0,\n    input wire ram_wen0,\n    input wire [63:0] ram_wdata1,\n    output wire [63:0] ram_rdata1,\n    input wire [4:0] ram_addr1,\n    input wire ram_wen1\n);\n    reg [63:0] ram[31:0];\n    always @(posedge clk) begin\n        if (ram_wen0) begin\n            ram[ram_addr0] \u003c= ram_wdata0;\n        end\n        ram_rdata0 \u003c= ram[ram_addr0];\n        if (ram_wen1) begin\n            ram[ram_addr1] \u003c= ram_wdata1;\n        end\n        ram_rdata1 \u003c= ram[ram_addr1];\n\n    end\nendmodule\n```\n\nIt gets a bit ugly, and connecting multiple ports could ended up being a daunting task. Luckily, generating and connecting wire for arrays can be done similarly as well:\n\n```verilog\n\u003c% gen_wire(\"ram\", ram_req_t, count=NR_PORTS) %\u003e\n\ndual_port_ram dual_port_ram(\n    .clk(clk),\n    \u003c% gen_connect(\"ram\", ram_req_t, count=NR_PORTS, last_comma=False) %\u003e\n);\n```\n\nIt would then be expanded accordingly.\n\n### Packing and unpacking\n\nVerilog doesn't really offer much support for generics. While it may be possible to use the port list generation to create copies of the same module with different post signal type, it's a lot of duplicated code. In some cases, it is simply easier to pack stuff into a big bit vector and later unpack them.\n\nFor example, to buffer a RGB565 signal through a FIFO:\n\n```python\nrgb_t = [[\"o\", \"r\", 5], [\"o\", \"g\", 6], [\"o\", \"b\", 5]]\n```\n\n```verilog\nfifo #(.WIDTH(\u003c% count_bits(rgb_t) %\u003e)) rgb_fifo (\n    .clk(clk),\n    .a_data(\u003c% gen_cat(rgb_t, \"in\") %\u003e),\n    .a_valid(in_valid),\n    .a_ready(in_ready),\n    .b_data(\u003c% gen_cat(rgb_t, \"out\") %\u003e),\n    .b_valid(out_valid),\n    .b_ready(out_ready)\n);\n```\n\nIt gets expanded to:\n\n```\nfifo #(\n    .WIDTH(16)\n) rgb_fifo (\n    .clk(clk),\n    .a_data({in_r, in_g, in_b}),\n    .a_valid(in_valid),\n    .a_ready(in_ready),\n    .b_data({out_r, out_g, out_b}),\n    .b_valid(out_valid),\n    .b_ready(out_ready)\n);\n```\n\nNote how `count_bits()` is used to determine the width of the resulting bit vector and sets the width of the fifo.\n\n### Defines\n\nSometimes there are some constants that's needed in both python code and verilog code, Manjuu provides a way to easily define them in both sides:\n\n```python\ndefine(\"CMD_INVALID\", \"3'd1\")\n```\n\nOr, if the number is only intended to be used in python environment, a simple assignment is good enough:\n\n```python\nDATA_WIDTH = 64\n```\n\nThen by calling the function `gen_defines()` it gets expanded to:\n\n```verilog\n`define CMD_INVALID 3'd1\n```\n\nAnd use the defined const (either by direct assignment or using the `define()` function):\n\n```python\nport = [[\"i\", \"dat\", DATA_WIDTH]]\n```\n\n## Why not xxx\n\nThere are tons of alternatives solutions that provides similar functionalities as the Manjuu, suiting different needs. Manjuu doesn't aim to replace any of these, and I won't say Manjuu is a good choice for everyone. Here is just a list of few differences comparing Manjuu to other alternatives:\n\n- Gradual adoption: start using Manjuu doesn't require rewriting any of the existing codebase, not even individual modules\n- Very little to learn: it doesn't introduce any extension to either Verilog or Python\n- Clear sepearation between circuit and generator: It's always clear if the code is describing a circuit (verilog code) or generator (python code)\n- It's dumb: it doesn't try to be smart to fill in any gaps, it only does what explicitly told to do, to avoid covering up bugs\n- Clean and human-readable generated code: the output is clean, with no random-ish generated labels or any directives\n- Pre-processing always gets run during build: avoid bugs caused by old dangling generated code\n- Easy to extend and modify: it's based on a very simple model so it's easy to pick up and add features as needed\n\n## License\n\nUnless otherwise specified, the source code is licensed under MIT.\n\nThe PyHP code preprocessor (pyhp.py) is licensed under GPLv2. The code processor is provided in the repo for convenience, but shouldn't be considered as part of the project (It's not dynamically or statically linked into other part of the program, rather functions as a text processor to consume other files.)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzephray%2Fmanjuu","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzephray%2Fmanjuu","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzephray%2Fmanjuu/lists"}