Sat Jun 28 09:50:05 AM CEST 2025
Everything is still about OpenGL
Didn't occur to me until just now, after seeing glm library keep referencing back to GLU library. Frankly I don't remember what GLU libray does either, but some memory is coming back.
So there's a bunch of transformation supported by GLU, like lookAt, perspective and so on. Of course with legacy GL, you don't get to deal with matrices directly, it's all covered with some functions. With modern 3D API, you have to do it yourself, so GLU becomes GLM which exposes the same feature set, except as matrices instead, that you can send over to vertex fragments.
GLM is short for OpenGL Mathematics. Who knew? I could have if I bothered to read README.
Wed Jun 25 04:29:19 PM CEST 2025
Money has come. I can finally buy toothpaste again!
Aaand I forgot to buy it, yet remembered Limoncello. Brain oh brain.
Tue Jun 24 08:19:08 PM CEST 2025
Clipping space!
Three hours just to put this guy in the middle of the "map"
And that's with all the working code already there (in learnopengl repo).
Though the first mistake was using wrong primitive type (triangle strip instead of list) which messes up the model pretty badly (and also UV texture).
But the bigger problem, and it also took me a lot of time before, was clipping space. The lack of projection matrix, and the size of the model, means that some vertices are going to be outside the default clipping space from -1.0 to 1.0. Meaning very strange clipping. How come I keep thinking about depth testing, sigh...
It's also very strange that with wrong (identity) projection matrix, then suddenly draw order matters. Which ever is drawn last, the ground or the character, wins. Maybe the depth value is too big without projection matrix that it doesn't matter anymore. Who knows. I know I don't.
Maths are hard!
Mon Jun 23 02:22:28 PM CEST 2025
Minor characters in Frasier
Doctor Perry Cox plays a plumber. Karen Walker is a kitchen designer.
Sun Jun 22 10:27:11 PM CEST 2025
Behind the scene of article 9080
(from Starship Troopers)
After the court-martial was over and Ted had been taken away, he stayed behind and said to Captain Frankel, "May I speak with the Battalion Commander, sir?"
"Certainly. I was intending to ask you to stay behind for a word. Sit down."
Zim flicked his eyes my way and the Captain looked at me and I didn't have to be told to get out. I faded. There was nobody in the outer office, just a couple of civilian clerks. I didn't dare go outside because the Captain might want me; I found a chair back of a row of files and sat down.
I could hear them talking, through the partition I had my head against. BHQ was a building rather than a tent, since it housed permanent communication and recording equipment, but it was a "minimum field building", a shack; the inner partitions weren't much. I doubt if the civilians could hear as they each were wearing transcriber phones and were bent over types --- besides, they didn't matter. I didn't mean to eavesdrop. Uh, well, maybe I did.
Zim said: "Sir, I request transfer to a combat team."
Frankel answered: "I can't hear you, Charlie. My tin ear is bothering me again."
Zim: "I'm quite serious, sir. This isn't my sort of duty."
Frankel said testily: "Quit bellyaching your troubles to me, Sergeant. At least wait until we've disposed of duty matters. What in the world happened?"
Zim said stiffly, "Captain, the boy doesn't rate ten lashes."
Frankel answered, "Of course he doesn't. You know who goofed --- and so do I."
"Yes, sir. I know."
"Well? You know even better than I do that these kids are wild animals at this stage. You know when it's safe to turn your back on them and when it isn't. You know the doctrine and the standing orders about article nine-oh-eight-oh --- you must never give the a chance to violate it. Of course some of them are going to try it --- if they weren't aggressive they wouldn't be material for the M.I. They're docile in ranks; it's safe enough to turn your back when they're eating, or sleeping, or sitting on their tails and being lectured. But get them out in the field in a combat exercise, or anything that gets them keyed up and full of adrenaline, and they're as explosive as a hatful of mercury fulminate. You know that, all you instructors know that; you're trained --- trained to watch for it, trained to snuff it out before it happens. Explain to me how it was possible for an untrained recruit to hang a mouse on your eye? He should never have laid hand on you; you should have knocked him cold when you saw what he was up to. So why weren't you on the bounce? Are you slowing down?"
"I don't know", Zim answered slowly. "I guess I must be."
"Hmm! If true, combat team is the last place for you. But it's not true. Or wasn't true the last time you and I worked out together, three days ago. So what slipped?"
Zim was slow in answering. "I think I had him tagged in my mind as one of the safe ones."
"There are no such."
"Yes, sir. But he was so earnest, so doggedly determined to sweat it out --- he didn't have any aptitude but he kept on trying --- that I must have done that, subconsciously." Zim was silent, then added, "I guess it was because I liked him."
Frankel snorted. "An instructor can't afford to like a man."
"I know it, sir. But I do. They're a nice bunch of kinds. We've dumped all the real twerps by now --- Hendrick's only shortcoming, aside from being clumsy, was that he thought he knew all the answers. I didn't mind that; I knew it all at that age myself. The twerps have gone home and those that are left are eager, anxious to please, and on the bounce --- as cute as a litter of collie pups. A lot of them will make soldiers."
"So that was the soft spot. You liked him... so you failed to clip him in time. So he winds up with a court and the whips and a B.C.D. Sweet."
Zim said earnestly, "I wish to heaven there were some way for me to take that flogging myself, sir."
"You'd have to take your turn, I outrank you. What do you think I've been wishing the past hour? What do you think I was afraid of from the moment I saw you come in here sporting a shiner? I did my best to brush it off with administrative punishment and the young fool wouldn't let me well enough alone. But I never thought he would be crazy enough to blurt it out that he'd hung one on you --- he's stupid; you should have eased him out of the outfit weeks ago... instead of nursing him along until he got into trouble. But blurt it out he did, to me, in front of witnesses, forcing me to take official notice of it --- and that licked us. No way to get it off the record, no way to avoid a court... just go through the whole dreary mess and take our medicine, and wind up with one more civilian who'll be against us the rest of his days. Because he has to be flogged; neither you nor I can take it for him, even though the fault was ours. Because the regiment has to see what happens when nine-oh-eight-oh is violated. Our fault... but his lumps."
"My fault, Captain. That's why I want to be transferred. Uh, sir, I think it's best for the outfit."
"You do, eh? But I decide what's best for my battalion, not you, Sergeant. Charlie, who do you think pulled your name out of the hat? And why? Think back twelve years. You were a corporal, remember? Where were you?"
"Here, as you know quite well, Captain. Right here on this same godforsaken prairie --- and I wish I had never come back to it!"
"Don't we all. But it happens to be the most important and most delicate work in the Army --- turning unspanked your cubs into soldiers. Who was the worst unspanked young cub in your section?"
"Mmm..." Zim answered slowly. "I wouldn't go so far as to say you were the worst, Captain."
"You wouldn't, eh? But you'd have to think hard to name another candidate. I hated your guts, 'Corporal' Zim."
Zim sounded surprised, and a little hurt. "You did, Captain? I didn't hate you --- I rather liked you."
"So? Well, 'hate' is the other luxury an instructor can never afford. We must not hate them, we must not like them; we must teach them. But if you liked me then --- mmm, it seemd to me that you had very strange ways of showing it. Do you still like me? Don't answer that; I don't care whether you do or not --- or, rather, I don't want to know, whichever it is. Never mind; I dispised you then and I used to dream about ways to get you. But you were always on the bounce and never gave me a chance to buy a nine-oh-eight-oh court of my own. So here I am, thanks to you. Now to handle your request: You used to have one order that you gave me over and over again when I was a boot. I got so loathed it almost more than anything else you did or said. Do you remember it? I do and now I'll give it back to you: 'Soldier, shut up and soldier!'"
"Yes, sir."
"Don't go yet. This weary mess isn't all loss; any regiment of boots needs a stern lesson in the meaning of nine-oh-eight-oh, as we both know. They haven't learned to think, they won't read, and they rarely listen --- but they can see... and young Hendrick's misfortune may save one of this mates, someday, from swinging by the neck until he's dead, dead, dead. But I'm sorry the object lesson had to come from my battalion and I certainly don't intend to let this battalion supply another one. You get your instructors together and warn them. For about tweny-four hours those kids will be in a state of shock. Then they'll turn sullen and the tension will build. Along about Thursday or Friday some boy who is about to flunk out anyhow will start thinking over the fact that Hendrick didn't get so very much, not even the number of lashes for drunken driving... and he's going to start brooding that it might be worth it, to take a swing at the instructor he hates the most. Sergeant, that blow must never land! Understand me?"
"Yes, sir."
"I want them to be eight times as cautious as they have been. I want them to keep their distance, I want them to have their eyes in the backs of their heads. I want them to be as alert as a mouse at a cat show. Bronski --- you have a special word with Bronski; he has a tendency to fraternize."
"I'll straighten Bronski out, sir."
"See that you do. Because when the next kid starts swinging, it's got to be stop-punched --- not muffed, like today. The boy has got to be knocked cold and the instructor must do so without ever being touched himself --- or I'll damned well break him for incompetence. Let them know that. They've got to teach those kids that it's not merely expensive but impossible to violate nine-oh-eight-oh... that even trying it wins a short nap, a bucket of water in the face, and a very sore jaw --- and nothing else."
"Yes, sir. It'll be done."
"It had better be done. I will not only break the instructor who slips, I will peronally take him 'way out on the prairie and give him lumps... because I will not have another of my boy strung up to that whipping post through sloppiness on the part of his teachers. Dismissed."
Sun Jun 22 06:54:13 PM CEST 2025
Multi-clock RISC-V CPU
ultraembedded/riscv is a tiny bit toooo advanced, so let's go back to something simpler, ultraembedded/core_uriscv.
This shows how multiple stage CPU is implemented, but a lot more obvious: state machine.
// Current state
reg [STATE_W-1:0] state_q;
//-----------------------------------------------------------------
// Next State Logic
//-----------------------------------------------------------------
reg [STATE_W-1:0] next_state_r;
always @ *
begin
next_state_r = state_q;
case (state_q)
// RESET - First cycle after reset
STATE_RESET:
begin
next_state_r = STATE_FETCH_WB;
end
// FETCH_WB - Writeback / Fetch next isn
STATE_FETCH_WB :
begin
if (opcode_fetch_w)
next_state_r = SUPPORT_BRAM_REGFILE ? STATE_DECODE : STATE_EXEC;
end
// DECODE - Used to access register file if SUPPORT_BRAM_REGFILE=1
STATE_DECODE:
begin
if (mem_i_valid_i)
next_state_r = STATE_EXEC;
end
// EXEC - Execute instruction (when ready)
STATE_EXEC :
begin
// Instruction ready
if (opcode_valid_w)
begin
if (exception_w)
next_state_r = STATE_FETCH_WB;
else if (type_load_w || type_store_w)
next_state_r = STATE_MEM;
// Multiplication / division - stay in exec state until result ready
else if (muldiv_inst_w)
;
else
next_state_r = STATE_FETCH_WB;
end
else if (muldiv_ready_w)
next_state_r = STATE_FETCH_WB;
end
// MEM - Perform load or store
STATE_MEM :
begin
// Memory access complete
if (mem_d_ack_i)
next_state_r = STATE_FETCH_WB;
end
default:
;
endcase
if (!enable_w)
next_state_r = STATE_RESET;
end
// Update state
always @ (posedge clk_i )
if (rst_i)
state_q <= STATE_RESET;
else
state_q <= next_state_r;
The rest is just act on the right state, e.g.
always @ (posedge clk_i )
if (rst_i)
opcode_q <= 32'b0;
else if (state_q == STATE_DECODE)
opcode_q <= mem_i_inst_i;
Fri Jun 20 09:38:34 AM CEST 2025
Real RISC-V in Verilog
Let's look at ultraembedded/riscv which can actually boot up Linux. Boy it's mightily complicated.
How instruction is decoded and delivered to ALU. Start at riscv_core.v which is supposed to be connected to some external memory bus interface, probably, then goes through a few modules (mmu, fetch, decode, issue, exec)
module riscv_core
(
,input [ 31:0] mem_i_inst_i
)
riscv_mmu
u_mmu
(
,.fetch_out_inst_i(mem_i_inst_i)
,.fetch_in_inst_o(mmu_ifetch_inst_w)
)
riscv_fetch
u_fetch
(
,.icache_inst_i(mmu_ifetch_inst_w)
,.fetch_instr_o(fetch_dec_instr_w)
)
riscv_decode
u_decode
(
,.fetch_in_instr_i(fetch_dec_instr_w)
,.fetch_out_instr_o(fetch_instr_w)
)
riscv_issue
u_issue
(
,.fetch_instr_i(fetch_instr_w)
,.opcode_opcode_o(opcode_opcode_w)
)
riscv_exec
u_exec
(
,.opcode_opcode_i(opcode_opcode_w)
)
riscv_exec.v decodes and delivers to riscv_alu, for example for "add"
module riscv_exec
(
,input [ 31:0] opcode_opcode_i
,input [ 31:0] opcode_ra_operand_i
,input [ 31:0] opcode_rb_operand_i
,output [ 31:0] writeback_value_o
)
reg [3:0] alu_func_r;
reg [31:0] alu_input_a_r;
reg [31:0] alu_input_b_r;
always @ *
begin
if ((opcode_opcode_i & `INST_ADD_MASK) == `INST_ADD) // add
begin
alu_func_r = `ALU_ADD;
alu_input_a_r = opcode_ra_operand_i;
alu_input_b_r = opcode_rb_operand_i;
end
end
wire [31:0] alu_p_w;
riscv_alu
u_alu
(
.alu_op_i(alu_func_r),
.alu_a_i(alu_input_a_r),
.alu_b_i(alu_input_b_r),
.alu_p_o(alu_p_w)
);
reg [31:0] result_q;
always @ (posedge clk_i or posedge rst_i)
if (rst_i)
result_q <= 32'b0;
else if (~hold_i)
result_q <= alu_p_w;
assign writeback_value_o = result_q;
and writeback_value_o goes back to issue:
riscv_exec
u_exec
(
,.writeback_value_o(writeback_exec_value_w)
);
riscv_issue
u_issue
(
,.writeback_exec_value_i(writeback_exec_value_w)
,.opcode_rd_idx_o(opcode_rd_idx_w)
)
There is a pipeline in riscv_issue.v!
riscv_pipe_ctrl
u_pipe_ctrl
(
// Execution stage 1: ALU result
,.alu_result_e1_i(writeback_exec_value_i)
// Execution stage 1
// Execution stage 2: Other results
// Execution stage 2
,.result_e2_o(pipe_result_e2_w)
// Out of pipe: Divide Result
// Commit
)
Naturally, I have no idea what's going on anymore. And I've been completely ignoring clk_i. And a pipeline means an instruction will be spread over multiple clocks. Exciting!
Thu Jun 19 06:38:36 PM CEST 2025
Multiplication can be synthesized
Which begs a question, can multiplication be done (in FPGA) in just one cycle? Oh yes. Today I learned.
Obviously something like x * 2
can be converted to x << 1
. And
shifting is very much synthesizable.
Let's move on to some more complicated operation. What about RHS
that's not 2^n? What about x * 5
? It's (x * 4) + (x * 1)
, so
(x << 2) + (x << 0)
. Still synthesizable.
What about any arbitrary RHS? Just run it through a big if/else, checking for every bit.
a * b =
(b & 1 ? a : 0) +
(b & 2 ? (a << 1) : 0) +
....
Won't be super fast. But it can be done. So yeah multiplication isn't that mysterious thing in hardware.
Now division... Still can be done, but not exactly in a reasonable amount of time and complexity.
Wed Jun 18 08:20:05 PM CEST 2025
RISC-V in Verilog
How is it so simple. Random project from github: ash-olakangal/RISC-V-Processor
Start with PROCESSOR.v which is probably usually called a "top" module. The entire CPU has two inputs, clock and reset and a zero output, which sets to one if the result of an operation is zero. This typically can be used for branching instructions.
module PROCESSOR(
input clock,
input reset,
output zero
);
All the side effects will be in registers. And there's no instruction for storing in memory in this code. Which explains why the output is so simple.
For that matter, there isn't a really input from memory either, which would requires some bus implementation. IFU.v holds a "PC" register that increments each clock, and return an instruction from "INST_MEM".
module IFU(
input clock,reset,
output [31:0] Instruction_Code
);
reg [31:0] PC = 32'b0; // 32-bit program counter is initialized to zero
// Initializing the instruction memory block
INST_MEM instr_mem(PC,reset,Instruction_Code);
always @(posedge clock, posedge reset)
begin
if(reset == 1) //If reset is one, clear the program counter
PC <= 0;
else
PC <= PC+4; // Increment program counter on positive clock edge
end
endmodule
INST_MEM has all instructions hard coded (no memory!) based on PC.
OK back to PROCESSOR.v, the main body is
wire [31:0] instruction_code;
wire [3:0] alu_control;
wire regwrite;
IFU IFU_module(clock, reset, instruction_code);
CONTROL control_module(instruction_code[31:25], instruction_code[14:12],instruction_code[6:0], alu_control, regwrite);
DATAPATH datapath_module(instruction_code[19:15], instruction_code[24:20], instruction_code[11:7], alu_control, regwrite, clock, reset, zero);
CONTROl would "decode" the instruction and decide what ALU (or what operation) to use. The actual decoding isn't necessary because of RISC-V ISA:
- Bits 0 to 6 are opcode
- Bits 12 to 14 are "funct3"
- Bits 25 to 31 are "funct7"
And register names in some other bits that we will revisit later. Imagine doing in this with x86... Anyway, with all that as input, we have CONTROL.v
module CONTROL(
input [6:0] funct7,
input [2:0] funct3,
input [6:0] opcode,
output reg [3:0] alu_control,
output reg regwrite_control
);
always @(funct3 or funct7 or opcode)
begin
if (opcode == 7'b0110011) begin // R-type instructions
regwrite_control = 1;
case (funct3)
0: begin
if(funct7 == 0)
alu_control = 4'b0010; // ADD
else if(funct7 == 32)
alu_control = 4'b0100; // SUB
end
6: alu_control = 4'b0001; // OR
7: alu_control = 4'b0000; // AND
1: alu_control = 4'b0011; // SLL
5: alu_control = 4'b0101; // SRL
2: alu_control = 4'b0110; // MUL
4: alu_control = 4'b0111; // XOR
endcase
end
end
endmodule
We can see here, all register-related instructions have the same opcode pattern x11xx11, all writes to a new register. And you can see here the code support ADD, SUB, OR, AND, SLL, SRL, MUL and XOR (MUL can be implemented this simple?)
OK with that we can look at ALU.v which does all the heavy lifting that a CPU is supposed to do. It has two values (will be bound to two registers later), alu_control that decides what operation to run and of course a output register
module ALU (
input [31:0] in1,in2,
input[3:0] alu_control,
output reg [31:0] alu_result,
output reg zero_flag
);
The rest is pretty much straight forward. It's really surprising that Verilog supports integer multiplication natively. How is that even done in FPGA, hard IP?
always @(*)
begin
// Operating based on control input
case(alu_control)
4'b0000: alu_result = in1&in2;
4'b0001: alu_result = in1|in2;
4'b0010: alu_result = in1+in2;
4'b0100: alu_result = in1-in2;
4'b1000: begin
if(in1<in2)
alu_result = 1;
else
alu_result = 0;
end
4'b0011: alu_result = in1<<in2;
4'b0101: alu_result = in1>>in2;
4'b0110: alu_result = in1*in2;
4'b0111: alu_result = in1^in2;
endcase
// Setting Zero_flag if ALU_result is zero
if (alu_result == 0)
zero_flag = 1'b1;
else
zero_flag = 1'b0;
end
endmodule
OK one last bit, the DATAPATH to connect ALU and registers. From PROCESSOR.v:
DATAPATH datapath_module(instruction_code[19:15], instruction_code[24:20], instruction_code[11:7], alu_control, regwrite, clock, reset, zero);
Here we can see how to "decode" the register operands in an instruction, which is also dead simple by design:
- Bits 15 to 19 is the index of the first input register
- Bits 20 to 24 is the index of the first second register
- Bits 7 to 11 is the index of the output register
Then we just pass on alu_control, produced by CONTROL.v.
DATAPATH.v is dead simple, just wiring the register file in REG_FILE.v and ALU.v basically
module DATAPATH(
input [4:0]read_reg_num1,
input [4:0]read_reg_num2,
input [4:0]write_reg,
input [3:0]alu_control,
input regwrite,
input clock,
input reset,
output zero_flag
);
// Declaring internal wires that carry data
wire [31:0]read_data1;
wire [31:0]read_data2;
wire [31:0]write_data;
// Instantiating the register file
REG_FILE reg_file_module(
read_reg_num1,
read_reg_num2,
write_reg,
write_data,
read_data1,
read_data2,
regwrite,
clock,
reset
);
// Instanting ALU
ALU alu_module(read_data1, read_data2, alu_control, write_data, zero_flag);
endmodule
And of course the register file. No explanation needed.
module REG_FILE(
input [4:0] read_reg_num1,
input [4:0] read_reg_num2,
input [4:0] write_reg,
input [31:0] write_data,
output [31:0] read_data1,
output [31:0] read_data2,
input regwrite,
input clock,
input reset
);
reg [31:0] reg_memory [31:0]; // 32 memory locations each 32 bits wide
integer i=0;
// When reset is triggered, we initialize the registers with some values
always @(posedge reset)
begin
// Bear with me for now, I tried using loops, but it won't work
// Just duct-taping this for now
reg_memory[0] = 32'h0;
reg_memory[1] = 32'h1;
reg_memory[2] = 32'h2;
reg_memory[3] = 32'h3;
reg_memory[4] = 32'h4;
reg_memory[5] = 32'h5;
reg_memory[6] = 32'h6;
reg_memory[7] = 32'h7;
reg_memory[8] = 32'h8;
reg_memory[9] = 32'h9;
reg_memory[10] = 32'h10;
reg_memory[11] = 32'h11;
reg_memory[12] = 32'h12;
reg_memory[13] = 32'h13;
reg_memory[14] = 32'h14;
reg_memory[15] = 32'h15;
reg_memory[16] = 32'h16;
reg_memory[17] = 32'h17;
reg_memory[18] = 32'h18;
reg_memory[19] = 32'h19;
reg_memory[20] = 32'h20;
reg_memory[21] = 32'h21;
reg_memory[22] = 32'h22;
reg_memory[23] = 32'h23;
reg_memory[24] = 32'h24;
reg_memory[25] = 32'h25;
reg_memory[26] = 32'h26;
reg_memory[27] = 32'h27;
reg_memory[28] = 32'h28;
reg_memory[29] = 32'h29;
reg_memory[30] = 32'h30;
reg_memory[31] = 32'h31;
end
// The register file will always output the vaules corresponding to read register numbers
// It is independent of any other signal
assign read_data1 = reg_memory[read_reg_num1];
assign read_data2 = reg_memory[read_reg_num2];
// If clock edge is positive and regwrite is 1, we write data to specified register
always @(posedge clock)
begin
if (regwrite) begin
reg_memory[write_reg] = write_data;
end
end
endmodule
No actually it looks weird because x0 is supposed to be hardwired to zero and cannot be changed. But it does not look like the case here. There isn't any special treatment for x0 and the register file enve has 32 registers, not 31.
That's it!
Of course this isn't full ISA. Comparison shouldn't be much harder. Branching would be interesting, but isn't that much since we just need to manipulate PC with an ALU. Wiring (to PC which isn't in register file) would be interesting though. But maybe it could be in the register file as well, just not indexable from ISA.
Real load and store may require proper memory bus. And if an access requires multiple clocks (likely), things can get "interesting" real fast. Which begs a question, can multiplication be done (in FPGA) in just one cycle? That has to be one big and long cycle.
Then if you attempt something more advanced like Superscalar, out-of-order execution, the article that started this all, I can't even imagine what it's like.
But that's enough hardware design for today. Maybe I'll find a more advanced design to keep me distract from glBomb, or a VHDL version of this instead.